I'm trying to write a unit test for a method in the service layer of a WebFlux application which processes the response of another service that makes a REST call and returns a Mono of the result.
I'm already testing the nested service using WebTestClient in it's own unit tests so am trying to mock out the response from it using Mockito but am encountering a NullPointerException as if the result isn't being mocked.
I'm relatively new to async/reactive patterns so not sure if I'm doing this wrong or if it's Mockito not playing nicely with the async nature of react however it works fine if it's not a nested service call that is being mocked?
I've replicated it in a minimal example that show's it's purely the mocking of the nested service which isn't working as expected, where NestedService.doRestCall() returns a Mono<String>:
#Service
public class ExampleService {
private final NestedService nestedService;
#Autowired
public ExampleService(final NestedService nestedService) {
this.nestedService = nestedService;
}
public Mono<String> methodToTest() {
return nestedService.doRestCall()
.map(data -> data + "-test");
}
}
And the test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ExampleServiceTests {
#Mock
private NestedService nestedServiceMock;
#InjectMocks
private ExampleService exampleService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void test() {
when(nestedServiceMock.doRestCall()).thenReturn(Mono.just("foo"));
StepVerifier.create(exampleService.methodToTest())
.expect("foo-test")
.verifyComplete();
}
}
I get the following NullPointerException trace triggered from the .map() call in the ExampleService line, I've also tried using flatMap() but get the same error:
java.lang.NullPointerException
at com.example.service.ExampleService.methodToTest(ExampleService.java:18)
at com.example.service.ExampleServiceTests.test(ExampleServiceTests.java:33)
You initialize your mocks twice:
first by spring runner
then by MockitoAnnotations.initMocks(this);
Lets inspect the variables in the setup method with debugger:
Before initMocks:
nestedServiceMock = {NestedService$MockitoMock$267376363#5567} "nestedServiceMock"
exampleService = {ExampleService#5568}
After initMocks:
nestedServiceMock = {NestedService$MockitoMock$267376363#5661} "nestedServiceMock"
exampleService = {ExampleService#5568}
exampleService.nestedServiceMock = {NestedService$MockitoMock$267376363#5567} "nestedServiceMock"
You can clearly see that the nestedServiceMock was re-initialized, but the exampleService was not, and still holds reference to the old nested object.
To solve, get rid of Spring boot annotations.
Here, cleaned-up version with JUnit5:
#ExtendWith(MockitoExtension.class)
public class ExampleServiceTest {
#Mock
private NestedService nestedServiceMock;
#InjectMocks
private ExampleService exampleService;
#Test
public void test() {
when(nestedServiceMock.doRestCall()).thenReturn(Mono.just("foo"));
StepVerifier.create(exampleService.methodToTest())
.expectNext("foo-test")
.verifyComplete();
}
}
Related
I'm trying to Test a Method where I call a method from three different other Classes (adapterX, adapterY, adapterZ). I want to verify that only one of these Methods is getting executed with the verify(...) method from Mockito.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {MessageValidationImplTest.TestConfig.class})
class MessageValidationImplTest {
#TestConfiguration
public static class TestConfig {
// Creation of other Beans messageValidation depends on
}
#MockBean
private AdapterY adapterY;
#MockBean
private AdapterX adapterX;
#MockBean
private AdapterZ adapterZ;
#Test
void testJsonPayload() {
// Arrange
//...
//Act
this.messageValidation.messageValidation(message, parameter);
//Assert
verify(this.adapterX).sendMessage(messageDto);
verifyNoInteractions(this.adapterY);
verifyNoInteractions(this.adapterZ);
}
#Test
void testCsvPayload() {
// Arrange
// ...
//Act
this.messageValidation.messageValidation(message, parameter);
//Assert
verifyNoInteractions(this.adapterX);
verify(this.adapterY).sendMessage(messageDto);
verifyNoInteractions(this.adapterZ);
}
}
I am creating the mocks via #MockBean from Spring.
When running the Tests I get this error message in the Logs (I get the same Error for the other Adapter in the second Test):
org.mockito.exceptions.verification.NoInteractionsWanted:
No interactions wanted here:
-> at com.acme.example.moduletest.core.impl.MessageValidationImplTest.testJsonPayload(MessageValidationImplTest.java:155)
But found these interactions on mock 'com.acme.example.y.AdapterY#0 bean':
-> at com.acme.example.core.impl.MessageValidationImpl.validateMessage(MessageValidationImpl.java:61)
Actually, above is the only interaction with this mock.
I've already tried manually resetting the Mocks with clearInvocations(adapterX, adapterY, adapterZ) but that doesn't make any difference.
I am using simple converter for converting string to enum. Here is the custom converter:
#Component
public class SessionStateConverter implements Converter<String, UserSessionState> {
#Override
public UserSessionState convert(String source) {
try {
return UserSessionState.valueOf(source.toUpperCase());
} catch (Exception e) {
LOG.debug(String.format("Invalid UserSessionState value was provided: %s", source), e);
return null;
}
}
}
Currently I am using UserSessionState as PathVariable in my rest controller. The implementation works as expected. However when I try to unit test the rest controller it seems that conversion does not work and it does not hit the controller method.
#RunWith(MockitoJUnitRunner.class)
public class MyTest {
private MockMvc mockMvc;
#Mock
private FormattingConversionService conversionService;
#InjectMocks
private MynController controller;
#Before
public void setup() {
conversionService.addConverter(new SessionStateConverter());
mockMvc = MockMvcBuilders.standaloneSetup(controller).setConversionService(conversionService).build();
}
#Test
public void testSetLoginUserState() throws Exception {
mockMvc.perform(post("/api/user/login"));
}
}
In debug mode I get following error:
nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'rest.api.UserSessionState': no matching editors or conversion strategy found
In my opinion the mock for conversion service does not work.
Any ideas?
The conversionService is a mock.
So this:
conversionService.addConverter(new SessionStateConverter());
calls addConverter on mock. This does nothing useful for you.
I believe you want to use real FormattingConversionService: to do it you need to remove #Mock annotation from conversionService field and use a real instance of FormattingConversionService instead:
private FormattingConversionService conversionService = new FormattingConversionService();
If you need to track invocations on real objects as you would do on mock check out : #Spy
If anyone uses implements org.springframework.core.convert.converter.Converter<IN,OUT> and if you are getting similar error while using mockMvc, please follow the below method.
#Autowired
YourConverter yourConverter;
/** Basic initialisation before unit test fires. */
#Before
public void setUp() {
FormattingConversionService formattingConversionService=new FormattingConversionService();
formattingConversionService.addConverter(yourConverter); //Here
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(getController())
.setConversionService(formattingConversionService) // Add it to mockito
.build();
}
I try to test a #RestController within a integration test suite using MockMvc.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class WebControllerIT {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
The #RestController (WebController) calls an injected #Service (RestClientService) which uses RestTemplate to call another REST server. This leads to the following error when running the test.
org.springframework.web.client.ResourceAccessException: I/O error on
GET request for "http://test123.com/42/status": test123.com; nested
exception is java.net.UnknownHostException: test123.com
I used MockRestServiceServer for the integration test of the #Service itself but have no idea how to archieve this within the test of #RestController.
How can I simulate a correct REST call of the RestTemplate?
The #RestController class.
#RestController
public class WebController {
private final RestClientService service;
#Autowired
public WebController(RestClientService service) {this.service = service;}
#GetMapping("/status")
public String getStatus() {
// extract pid from database ...
int pid = 42;
return this.service.getStatus(42);
}
}
The #Serviceclass.
#Service
public class RestClientService {
private final RestTemplate restTemplate;
public RestClientService(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
}
Integration/Unit testing doesn't work that way.Objective of this kind of testing is to run through your code and make sure all the business requirement are met but not to hit other system or DB.Here in your case u shouldn't be hitting test123.com to get back data.What needs to done here is that you should mock that method.
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
So that control doesn't enter this method but return you back the mock data(Dummy data).
For example let say that there are 2 kind of status this method is returning and you need to do some business validation based on the string returned.In this case u need to write 2 integration test and make sure the mocking method returns 2 different value(Dummy value instead of hitting that end point)
Reason why we are writing unit testing/integration testing is to make sure your entire code is working as expected but not to hit other system from ur code.
If you want to only test your controller layer, you would do like this.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MockServletContext.class)
#WebAppConfiguration
public class WebControllerIT {
private MockMvc mockMvc;
private RestClientService service
#Mock
private RestTemplate restTemplate
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
service = new RestClientService(restTemplate);
WebController webController = new WebController(service);
mvc = MockMvcBuilders.standaloneSetup(webController).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
//Mock the behaviour of restTemplate.
doReturn("someString").when(restTemplate).getForObject(anyString(), anyString());
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
The Controller that I'm trying to write unit test case for is as follows -
#RequestMapping("${rest.base.path}/plugin")
public class Controller {
.
.
.
}
The Unit test case is setup -
#RunWith(MockitoJUnitRunner.class)
public class ControllerTest {
#Autowired
private MockMvc mvc;
#InjectMocks
Controller dataController;
#Mock
PluginService pluginService;
#Test
public void createFiles() throws Exception {
this.mvc = MockMvcBuilders.standaloneSetup(dataController).build();
mvc.perform(MockMvcRequestBuilders.get("/dc/plugin")
.contentType(MediaType.APPLICATION_JSON));
}
On running the unit test, it's unable to resolve the placeholder ${rest.base.path} as I'm not loading the Spring Context. I tried setting the System.setProperty("rest.base.path", "/api") without any success. Is there anyway I can assign value to that placeholder without removing #RunWith(MockitoJUnitRunner.class)?
The key here is to fill placeholder yourself calling StandaloneMockMvcBuilder.addPlaceholderValue
As the documentation states:
In a standalone setup there is no support for placeholder values embedded in request mappings. This method allows manually provided placeholder values so they can be resolved.
So, the following simple snippet should work for you
public class TestController {
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(new Controller()).addPlaceHolderValue("rest.base.path", "dc")
.setControllerAdvice(new ExceptionMapper())
.setMessageConverters(new MappingJackson2HttpMessageConverter(new ExtendedObjectMapper())).build();
}
#Test
public void testGet() throws Exception {
mockMvc.perform(get("/dc/plugin").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk());
}}
For sure, you can achieve the same autowiring your controller.
I have a spring-boot application which exposes a REST interface via a controller. This is an example of my controller:
#RestController
public class Controller {
#Autowired
private Processor processor;
#RequestMapping("/magic")
public void handleRequest() {
// process the POST request
processor.process();
}
}
I am trying to write unit tests for this class and I have to mock the processor (since the processing takes very long time and I am trying to avoid this step during testing the controller behavior). Please note, that the provided example is simplified for the sake of this question.
I am trying to use the mockito framework for this task:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = App.class)
#WebAppConfiguration
#ActiveProfiles("test")
public class ControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
Processor processor = Mockito.mock(Processor.class);
ReflectionTestUtils.setField(Controller.class, "processor", processor);
}
#Test
public void testControllerEmptyBody() throws Exception {
this.mockMvc.perform(post("/magic")).andExpect(status().isOk());
}
}
However, this fails with
java.lang.IllegalArgumentException: Could not find field [processor] of type [null] on target [class org.company.Controller]
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:112)
...
Could please someone give me a hint, how this mock could be injected in my controller?
Shouldn't you be passing an instance to set the field on, rather than the class, e.g.:
...
#Autowired
private Controller controller;
...
#Before
public void setUp() throws Exception {
...
Processor processor = Mockito.mock(Processor.class);
ReflectionTestUtils.setField(controller, "processor", processor);
}
I think that you can inject directly the mock like:
#InjectMocks
private ProcessorImpl processorMock;
And remove this line:
ReflectionTestUtils.setField(Controller.class, "processor", processor);
See Injection of a mock object into an object to be tested declared as a field in the test does not work using Mockito?
Rework your controller to use constructor injection instead of field injection. This makes the dependency explicit and makes your test setup drastically simpler.
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
Processor processor = Mockito.mock(Processor.class);
//This line should be added to perform mock for Processor.
Mockito.when(processor.process()).thenReturn(<Your returned value>);
//ReflectionTestUtils.setField(Controller.class, "processor", processor);
}
In above put the your returned value for "Your returned value" and in test use this value to verify your output.
You can remove servlet context class in SpringApplicationConfiguration and mock servlet context. There is no need for injection of WebApplicationContext and ReflectionTestUtils.
Basically, your code should look something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MockServletContext.class)
#WebAppConfiguration
#ActiveProfiles("test")
public class ControllerTest {
#InjectMocks
private MyController controller;
#Mock
private Processor processor;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testControllerEmptyBody() throws Exception {
when(proessor.process()).thenReturn(<yourValue>);
this.mockMvc.perform(post("/magic")).andExpect(status().isOk());
verify(processor, times(<number of times>)).process();
}
}
Processor will be mocked and mock will be injected into controller.