Unit tests for testing methods returning CompletableFuture always returns NullPointerException - spring-boot

I have a SpringBoot Component and few methods. It returns CompletableFuture and I want to cover those lines with Unit Tests. But it always ends up in NullPointer Exception.
#Component
Class AsyncClass {
// My implementation of RESTClient
#Autowired
RestClient httpClient;
#Async
public CompletableFuture<TestClass> getDetails() {
// Retrieves the headers
HttpHeaders headers = getHttpHeaders();
HttpEntity<String> request = new HttpEntity<>(headers);
InitiativeResponse initiativeResponse = httpClient.executeRequest(API_URL, HttpMethod.GET, request, Object.class).getBody();
return CompletableFuture.completedFuture(TestClass);
}
private HttpHeaders getHttpHeaders(String userId) {
HttpHeaders headers = new HttpHeaders();
headers.add(CONSUMER_ID, consumerId);
headers.add(USER_ID, userId);
return headers;
}
}
This works perfectly fine and I autowire this into my service class and get the results. The problem arises when I write unit test cases for this class.
Class TestAsyncClass {
#InjectMocks
AsyncClass asyncClass;
#BeforeEach
void setUp() {
openMocks(this);
}
#Test
void testGetDetails() {
asyncClass.getDetails();
}
}
The above unit test fails with NullPointer Exception. Can you please help me on what am I missing?
The stacktrace simple shows
ava.lang.NullPointerException
at com.example.services.helper.TestAsyncClass.testGetDetails(TestAsyncClass.java:11)

I am not very sure but do you have #RunWith(MockitoJUnitRunner.class) or MockitoAnnotations.initMocks(this) in your TestAsyncClass ? I have the gut feelings that the mocks are not initiated....

Related

How to mock external rest services when writing integration test in spring boot

I have a controller from which gateway(Spring integration) is being called. Inside gateway I have several flows where I'm doing some outboundgateway calls. I've written my integration test as below -
#Tag("integrationtest")
#ExtendWith(SpringExtension.class)
#SpringBootTest(
classes = MyWebApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
#LocalServerPort private int port;
TestRestTemplate testRestTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
#Test
void testEntireApplication() {
HttpEntity<LoanProvisionRequest> entity =
new HttpEntity(TestHelper.generateValidLionRequest(), headers);
ResponseEntity<LoanProvisionResponse> response =
testRestTemplate.exchange(
createURLWithPort("/provision"), HttpMethod.POST, entity, LionResponse.class);
assertEquals(1, response.getBody().getASMCreditScoreResultCd());
}
private String createURLWithPort(String uri) {
return "http://localhost:" + port + "/lion-service/v1/decisions" + uri;
}
}
It's running the application and proceeding through from controller to the gateway and running the flows as expected. But for the outboundgateway calls it's failing by saying Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://someurl" because it's not able to access the url that's used in the outboundgateway. I want to stub/mock those url somehow. How do I do that?
I tried doing something below in the same class to mock the url -
MockRestServiceServer mockServer;
#BeforeEach
void setUp() throws JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
DecisionResponse decisionResponse = new DecisionResponse();
creditDecisionResponse.setId("0013478");
creditDecisionResponse.setResponse(null);
creditDecisionResponse.setDescription("dummy Response");
mockServer
.expect(
requestTo(
"http://xyz-some-url:8080/some-other-service/v1/do-decisions/decision"))
.andExpect(method(HttpMethod.POST))
.andRespond(
withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(new ObjectMapper().writeValueAsString(decisionResponse )));
mockServer.verify();
}
But still the same error showing and somehow it's not getting called when it's hitting the outboundgateway call inside the gateway flows.
below is the controller code -
public ResponseEntity<LionResponse> getLionsNames(
#RequestBody final #Valid LionRequest req,
BindingResult bindingResult,
#RequestHeader HttpHeaders httpHeaders)
throws JsonProcessingException {
Long dbId = new SequenceGenerator().nextId();
lionsGateway.processLionRequest(
MessageBuilder.withPayload(req).build(),
dbId,
SourceSystem.ONE.getSourceSystemCode()));
below is the gateway -
#MessagingGateway
public interface LoansGateway {
#Gateway(requestChannel = "flow.input")
List<Object> processLoanRequest(
#Payload Message lionRequest,
#Header("dbID") Long dbID,
#Header("sourceSystemCode") String sourceSystemCode);
}
below is the SpringIntegrationConfiguration class -
#Bean
public IntegrationFlow flow() {
return flow ->
flow.handle(validatorService, "validateRequest")
.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(savingLionRequestToTheDB())
.recipientFlow(callingANativeMethod())
.recipientFlow(callingAExternalService()),
gatherer -> gatherer.outputProcessor(prepareCDRequest()))
.gateway(getDecision(), f -> f.errorChannel("lionDecisionErrorChannel"))
.to(getDataResp());
}
public IntegrationFlow callingAExternalService() {
return flow ->
flow.handle(
Http.outboundGateway(externalServiceURL)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.logAndReply("Cd response");
}
.... same way I have other flows that are using outboundgateway but I've not wired the Restemplate instance anywhere.
So, you do in your mock server setup:
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
And that's it. The mocked RestTemplate instance is not used anywhere.
The HttpRequestExecutingMessageHandler has a configuration based on the RestTemplate:
/**
* Create a handler that will send requests to the provided URI using a provided RestTemplate.
* #param uri The URI.
* #param restTemplate The rest template.
*/
public HttpRequestExecutingMessageHandler(String uri, RestTemplate restTemplate) {
So, you just need to instrument exactly that RestTemplate which you provide for your HTTP outbound gateway.
Right now your mocking code is dead end.

Spring Mockito test of RestTemplate.postForEntity throws IllegalArgumentException: URI is not absolute

My Controller calls the service to post information about a car like below and it works fine. However, my unit test fails with the IllegalArgumentException: URI is not absolute exception and none of the posts on SO were able to help with it.
Here is my controller
#RestController
#RequestMapping("/cars")
public class CarController {
#Autowired
CarService carService;
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CarResponse> getCar(#RequestBody CarRequest carRequest, #RequestHeader HttpHeaders httpHeaders) {
ResponseEntity<CarResponse> carResponse = carService.getCard(carRequest, httpHeaders);
return carResponse;
}
}
Here is my service class:
#Service
public class MyServiceImpl implements MyService {
#Value("${myUri}")
private String uri;
public void setUri(String uri) { this.uri = uri; }
#Override
public ResponseEntity<CarResponse> postCar(CarRequest carRequest, HttpHeaders httpHeaders) {
List<String> authHeader = httpHeaders.get("authorization");
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", authHeader.get(0));
HttpEntity<CarRequest> request = new HttpEntity<CarRequest>(carRequest, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<CarResponse> carResponse = restTemplate.postForEntity(uri, request, CarResponse.class);
return cardResponse;
}
}
However, I am having trouble getting my unit test to work. The below tests throws IllegalArgumentException: URI is not absolute exception:
public class CarServiceTest {
#InjectMocks
CarServiceImpl carServiceSut;
#Mock
RestTemplate restTemplateMock;
CardResponse cardResponseFake = new CardResponse();
#BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
cardResponseFake.setCarVin(12345);
}
#Test
final void test_GetCars() {
// Arrange
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", anyString());
ResponseEntity<CarResponse> carResponseEntity = new ResponseEntity(carResponseFake, HttpStatus.OK);
String uri = "http://FAKE/URI/myapi/cars";
carServiceSut.setUri(uri);
when(restTemplateMock.postForEntity(
eq(uri),
Mockito.<HttpEntity<CarRequest>> any(),
Mockito.<Class<CarResponse>> any()))
.thenReturn(carResponseEntity);
// Act
**// NOTE: Calling this requires real uri, real authentication,
// real database which is contradicting with mocking and makes
// this an integration test rather than unit test.**
ResponseEntity<CarResponse> carResponseMock = carServiceSut.getCar(carRequestFake, headers);
// Assert
assertEquals(carResponseEntity.getBody().getCarVin(), 12345);
}
}
UPDATE 1
I figured out why the "Uri is not absolute" exection is thrown. It is because in my carService above, I use #Value to inject uri from application.properties file, but in unit tests, that is not injected.
So, I added public property to be able to set it and updated the code above, but then I found that the uri has to be a real uri to a real backend, requiring a real database.
In other words, if the uri I pass is a fake uri, the call to carServiceSut.getCar above, will fail which means this turns the test into an integration test.
This contradicts with using mocking in unit tests.
I dont want to call real backend, the restTemplateMock should be mocked and injected into carServiceSut since they are annotated as #Mock and #InjectMock respectively. Therefore, it whould stay a unit test and be isolated without need to call real backend. I have a feeling that Mockito and RestTemplate dont work well together.
You need to construct your system under test properly.
Currently, MyServiceImpl.uri is null.
More importantly, your mock of RestTemplate is not injected anywhere, and you construct a new RestTemplate in method under test.
As Mockito has no support for partial injection, you need to construct the instance manually in test.
I would:
Use constructor injection to inject both restTemplate and uri:
#Service
public class MyServiceImpl implements MyService {
private RestTemplate restTemplate;
private String uri;
public MyServiceImpl(RestTemplate restTemplate, #Value("${myUri}") uri) {
this.restTemplate = restTemplate;
this.uri = uri;
}
Construct the instance manually:
drop #Mock and #InjectMocks
drop Mockito.initMocks call
use Mockito.mock and constructor in test
public class CarServiceTest {
public static String TEST_URI = "YOUR_URI";
RestTemplate restTemplateMock = Mockito.mock(RestTemplate.class);
CarServiceImpl carServiceSut = new CarServiceImpl(restTemplateMock, TEST_URI):
}
Remove creation of restTemplate in method under test.
If needed, add a config class providing RestTemplate bean (for the application, the test does not need that):
#Configuration
public class AppConfig {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Note that RestTemplate is thread-safe, one instance per app is enough: Is RestTemplate thread safe?
try to change the URI as
String uri = "http://some/fake/url";

Unable to mock RestTemplate using JUnit 5 in Spring Boot

Trying to mock restTemplate postForEntity() but it is returning null instead of ResponseEntity object I am passing inside thenReturn().
Service Impl class
public ResponseEntity<Object> getTransactionDataListByAccount(Transaction transaction) {
ResponseEntity<Object> transactionHistoryResponse = restTemplate.postForEntity(processLayerUrl, transaction, Object.class);
return new ResponseEntity<>(transactionHistoryResponse.getBody(), HttpStatus.OK);
}
Inside Test class
#SpringBootTest
#ActiveProfiles(profiles = "test")
public class TransactionServiceImplTest {
#MockBean
private RestTemplate mockRestTemplate;
#Autowired
private TransactionServiceImpl transactionService;
#Test
public void getTransactionDataListByAccountTest() throws Exception{
Transaction transaction = new Transaction();
transaction.setPosAccountNumber("40010020070401");
ArrayList<Object> mockResponseObj = new ArrayList<Object>(); //filled with data
ResponseEntity<Object> responseEntity = new ResponseEntity<Object>(mockResponseObj, HttpStatus.OK);
when(mockRestTemplate.postForEntity(
ArgumentMatchers.anyString(),
ArgumentMatchers.eq(Transaction.class),
ArgumentMatchers.eq(Object.class))).thenReturn(responseEntity);
// THROWING NullPointerException at this line.
ResponseEntity<Object> actualResponse = transactionService.getTransactionDataListByAccount(transaction);
System.out.println("--- Response ---");
System.out.println(actualResponse);
}
Error
While executing test case, actual service is getting called. When it tries to invoke resttemplate inside service impl class it is returning null.
Trying to call getBody() on transactionHistoryResponse throwing NullPointerException
In your mock setup, the matcher ArgumentMatchers.eq(Transaction.class) will only match if the argument you pass in is the Class<Transaction> object Transaction.class. This is not what you want; you want it to match anything of type Transaction. To do this, use ArgumentMatchers.any(Transaction.class).
This answer has a good explanation.

mock rest template for unit test

I want to mock a RestTemplate in Spring Boot, where I'm making a REST call within a method. To test the controller of the microservice I'm creating,
I want to test methods inside the controller of my micro service.
For example:
#GetMapping(value = "/getMasterDataView", produces = { MediaType.APPLICATION_JSON_VALUE })
#CrossOrigin(origins = { "http://192.1**********" }, maxAge = 3000)
public ResponseEntity<MasterDataViewDTO> getMasterDataView() throws IOException {
final String uri = "http://localhost:8089/*********";
RestTemplate restTemplate = new RestTemplate();
MasterDataViewDTO masterDataViewDTO = restTemplate.getForObject(uri, MasterDataViewDTO.class);
return new ResponseEntity<>(masterDataViewDTO, HttpStatus.OK);
}
how to I test this using mocking?
This is what I have so far:
#Test
public void testgetMasterDataView() throws IOException {
MasterDataViewDTO masterDataViewDTO= mock(MasterDataViewDTO.class);
//String uri = "http://localhost:8089/*********";
Mockito.when(restTemplate.getForObject(Mockito.anyString(),ArgumentMatchers.any(Class.class))).thenReturn(masterDataViewDTO);
assertEquals("OK",inquiryController.getMasterDataView().getStatusCode());
}
I am getting an error when I'm running the mock, the method getMasterDataView() is getting called and the REST call within it is also getting called and is throwing an error. How can I write my test so that the REST endpoint is not called? If it's possible, I'd like to do this with Mockito.
Before you start writing a test, you should change your code a bit. First of all, it would be a lot easier if you extracted that RestTemplate, and created a separate bean for it which you would inject within your controller.
To do that, add something like this within a #Configuration class or within your main class:
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Additionally, you have to remove the new RestTemplate() from your controller, and autowire it in stead, for example:
#Autowired
private RestTemplate restTemplate;
Now that you've done that, it's going to be a lot easier to inject a mock RestTemplate within your tests.
For your testing, you have two options:
Either mock RestTemplate and all the methods you are trying to access, using a mocking framework (eg. Mockito)
Or you can use MockRestServiceServer, which allows you to write tests that verify if the URLs are properly called, the request matches, and so on.
Testing with Mockito
To mock your RestTemplate with Mockito, you have to make sure that you add the following annotation to your tests:
#RunWith(MockitoJUnitRunner.class)
After that, you can do this:
#InjectMocks
private MyController controller;
#Mock
private RestTemplate restTemplate;
And now you can adjust your tests like this:
#Test
public void testgetMasterDataView() throws IOException {
MasterDataViewDTO dto = new MasterDataViewDTO();
when(restTemplate.getForObject("http://localhost:8089/*********", MasterDataViewDTO.class)).thenReturn(dto);
ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo(dto);
}
You could mock the DTO as you did within your test, but you don't have to, and I don't think there's any benefit from doing so. What you do have to mock is the restTemplate.getForObject(..) call.
Testing with MockRestServiceServer
Another approach is to use MockRestServiceServer. To do that, you have to use the following annotations for your test:
#RunWith(SpringRunner.class)
#RestClientTest
And then you'll have to autowire your controller and MockRestServiceServer, for example:
#Autowired
private MyController controller;
#Autowired
private MockRestServiceServer server;
And now you can write tests like this:
#Test
public void testgetMasterDataView() throws IOException {
server
.expect(once(), requestTo("http://localhost:8089/*********"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(new ClassPathResource("my-mocked-result.json"), MediaType.APPLICATION_JSON));
ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// TODO: Write assertions to see if the DTO matches the JSON structure
}
In addition to testing that your actual REST call matches, this also allows you to test if your JSON-to-DTO works as well.
You can achieve this by using #RestClientTest and MockRestServiceServer. An example provided in their documentation:
#RunWith(SpringRunner.class)
#RestClientTest(RemoteVehicleDetailsService.class)
public class ExampleRestClientTest {
#Autowired
private RemoteVehicleDetailsService service;
#Autowired
private MockRestServiceServer server;
#Test
public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
throws Exception {
this.server.expect(requestTo("/greet/details"))
.andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
create bean instead of using new RestTemplate() in your services.
use Profile("!test") for your bean that use only for non test profile.
create test class like this:
#SpringBootTest(properties = "spring.profiles.active:test")
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#Log4j2
#Transactional
public class QabzinoMockTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private QabzinoLogRepository qabzinoLogRepository;
private MockMvc mockMvc;
private final WebServiceStatus successStatus = new WebServiceStatus();
#Mock
private RestTemplate restTemplate;
private final Gson mapper = new Gson();
#TestConfiguration
static class Config {
#Bean
public RestTemplate rest() {
return Mockito.mock(RestTemplate.class);
}
}
#Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
this.restTemplate = (RestTemplate) this.webApplicationContext.getBean("rest");
LoginOutput responseModel = new LoginOutput();
responseModel.setStatus("200");
Mockito.when(
restTemplate.postForEntity(
Mockito.eq(LOGIN_URL),
Mockito.isA(HttpEntity.class),
Mockito.eq(String.class)
)
).thenReturn(ResponseEntity.ok().body(mapper.toJson(responseModel)));
}
}
in this sample we create bean with rest name in static class Config and mock it in setup method Before all tests.
and preferred test config maybe useful for you application-test.properties:
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE;IGNORECASE=TRUE;
spring.datasource.username=sa
spring.datasource.password=sa
and finally when we see restTemplate in our code, mock bean return our responseModel instead of real service call :)

How to send HTTP OPTIONS request with body using Spring rest template?

I am trying to call a RESTfull web service resource, this resource is provided by a third party, the resource is exposed with OPTIONS http verb.
To integrate with the service, I should send a request with a specific body, which identities by a provider, but when I did that I got a bad request. After that I trace my code then I recognized that the body of the request is ignored by rest template based on the below code:
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
my question, is there a standard way to override this behavior or I should use another tool?
The code you've pasted is from
SimpleClientHttpRequestFactory.prepareConnection(HttpURLConnection connection, String httpMethod)
I know because I've debugged that code few hours ago.
I had to do a HTTP GET with body using restTemplate. So I've extend SimpleClientHttpRequestFactory, override prepareConnection and create a new RestTemplate using the new factory.
public class SimpleClientHttpRequestWithGetBodyFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
if ("GET".equals(httpMethod)) {
connection.setDoOutput(true);
}
}
}
Create a new RestTemplate based on this factory
new RestTemplate(new SimpleClientHttpRequestWithGetBodyFactory());
A test to prove the solution is working using spring boot (#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT))
public class TestRestTemplateTests extends AbstractIntegrationTests {
#Test
public void testMethod() {
RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestWithBodyForGetFactory());
HttpEntity<String> requestEntity = new HttpEntity<>("expected body");
ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:18181/test", HttpMethod.GET, requestEntity, String.class);
assertThat(responseEntity.getBody()).isEqualTo(requestEntity.getBody());
}
#Controller("/test")
static class TestController {
#RequestMapping
public #ResponseBody String testMethod(HttpServletRequest request) throws IOException {
return request.getReader().readLine();
}
}
}

Resources