I am trying to write the integration test for my controller.
Exception handler is there in a library as part of dependency. But my webMvc Test is not able to invoke the handler
The exception is thrown from method
#WebMvcTest(controllers = MyController.class, excludeAutoConfiguration = {
SecurityAutoConfiguration.class})
#ExtendWith(SpringExtension.class)
public class WebMockTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MyService service;
#Test
public void shall_make_get_call_404_not_ok() throws Exception {
when(service.getById(any())).thenThrow(new CustomException("Not found"));
this.mockMvc.perform(get("/items/" + "12")).andDo(print())
.andExpect(status().isNotFound());
}
}
Here I am not able to get the response but exception is thrown from test method.
How do I trigger the exception handler.
You need to add #Import(Exceptionhandler.class).
I described a similar problem, when using #WebMvcTest with specific controllers and its solution here: https://stackoverflow.com/a/74191408/7789681
Related
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();
}
}
I write a Spring Boot app and I was able to access and test Controller with MockMvc. The issue is that during testing security is not enforced and I can access Controller with no user.
Am I doing anything wrong? Is it intended behavior?
ControllerTest class:
#RunWith(MockitoJUnitRunner.class)
public class ControllerTest {
private MockMvc mockMvc;
#Mock
private Service service;
#InjectMocks
private Controller controller;
private final static String URL = "/test";
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void test() throws Exception {
mockMvc.perform(get(URL))
.andExpect(status().isOk());
}
}
My SecurityConfig StackOverflow QA.
Your examples uses a plain unit test to test your controller. In this setup the Controller is created by Mockito (the controller field is annotated with Mockito's #InjectMocks).
Mockito is not aware of Spring, in consequence no Spring Security will be setup in your test.
You need to use the SpringRunner to run your test. This runner is Spring aware and allows you to properly initialize your controller before the test is run.
The test should look something like this (junit5):
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = Controller.class)
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service serviceMock;
#Test
public void test() throws Exception {
mockMvc.perform(get(URL))
.andExpect(status().isOk());
}
}
check our the Spring documentation or some tutorials for further information
https://spring.io/guides/gs/testing-web/
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html
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.
My #ControllerAdvice annotated Controller looks like this:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
#ExceptionHandler(AuthenticationException.class)
public void authenticationExceptionHandler() {
}
}
Of course my development is test driven and I would like to use my exception Handler in the JUnit Tests. My Test case looks like this:
public class ClientQueriesControllerTest {
private MockMvc mockMvc;
#InjectMocks
private ClientQueriesController controller;
#Mock
private AuthenticationService authenticationService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void findAllAccountRelatedClientsUnauthorized() throws Exception {
when(authenticationService.validateAuthorization(anyString())).thenThrow(AuthenticationException.class);
mockMvc.perform(get("/rest/clients").header("Authorization", UUID.randomUUID().toString()))
.andExpect(status().isUnauthorized());
}
}
Probably I need to register the ControllerAdvice Class. How to do that?
Since Spring 4.2, you can register your ControllerAdvice directly into your StandaloneMockMvcBuilder:
MockMvcBuilders
.standaloneSetup(myController)
.setControllerAdvice(new MyontrollerAdvice())
.build();
In order for the full Spring MVC configuration to get activated, you need to use MockMvcBuilders.webAppContextSetup instead of MockMvcBuilders.standaloneSetup.
Check out this part of the Spring documentation for more details.
Your code would look like:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("test-config.xml")
public class ClientQueriesControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private AuthenticationService authenticationService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void findAllAccountRelatedClientsUnauthorized() throws Exception {
when(authenticationService.validateAuthorization(anyString())).thenThrow(AuthenticationException.class);
mockMvc.perform(get("/rest/clients").header("Authorization", UUID.randomUUID().toString()))
.andExpect(status().isUnauthorized());
}
}
Then inside test-config.xml you would add a Spring bean for AuthenticationService that is a mock.
<bean id="authenticationService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="your.package.structure.AuthenticationService"/>
</bean>
You could of course use profiles to inject the mock AuthenticationService in the tests if want to reuse your regular Spring configuration file instead of creating test-config.xml.
UPDATE
After digging around a bit, I found that StandaloneMockMvcBuilder returned by (MockMvcBuilders.standaloneSetup) is totally customizable. That means that you can plug in whatever exception resolver you prefer.
However since you are using #ControllerAdvice, the code below will not work.
If however your #ExceptionHandler method was inside the same controller the code all you would have to change is the following:
mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(new ExceptionHandlerExceptionResolver()).build();
UPDATE 2
Some more digging gave the answer to how you can register a correct exception handler when you are also using #ControllerAdvice.
You need to update the setup code in the test to the following:
#Before
public void setUp() throws Exception {
final ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
//here we need to setup a dummy application context that only registers the GlobalControllerExceptionHandler
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerBeanDefinition("advice", new RootBeanDefinition(GlobalControllerExceptionHandler.class, null, null));
//set the application context of the resolver to the dummy application context we just created
exceptionHandlerExceptionResolver.setApplicationContext(applicationContext);
//needed in order to force the exception resolver to update it's internal caches
exceptionHandlerExceptionResolver.afterPropertiesSet();
mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(exceptionHandlerExceptionResolver).build();
}
Got past the NestedServletException with the following solution...
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("exceptionHandler", GlobalControllerExceptionHandler.class);
final WebMvcConfigurationSupport webMvcConfigurationSupport = new WebMvcConfigurationSupport();
webMvcConfigurationSupport.setApplicationContext(applicationContext);
mockMvc = MockMvcBuilders.standaloneSetup(controller).
setHandlerExceptionResolvers(webMvcConfigurationSupport.handlerExceptionResolver()).
build();
If you have multiple advice classes, each with #ExceptionHandler and one of those classes is handling a very generic base exception, like #ExceptionHandler({Exception.class}), then you will need to add some priority ordering to your advice classes per this SO answer
https://stackoverflow.com/a/19500823/378151
If you are using junits older version than 5 and can not upgrade it for any reason then consider defining like below:
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("exceptionHandler", MyExceptionHandler.class);
final WebMvcConfigurationSupport webMvcConfigurationSupport = new WebMvcConfigurationSupport();
webMvcConfigurationSupport.setApplicationContext(applicationContext);
mockMvc = MockMvcBuilders.standaloneSetup(myController).
setHandlerExceptionResolvers(webMvcConfigurationSupport.handlerExceptionResolver()).
build();
You can add this to your test class
#Autowired
#Qualifier("handlerExceptionResolver")
void setExceptionResolver(HandlerExceptionResolver resolver)
{
this.exceptionResolver = resolver;
}
and then add the exceptionResolver to your MockMvc
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(this.exceptionResolver).build();
}