Spring mock MVC unit test with controller advice - java

I am trying to test my controller advice exception handling. I have registered my controller advice to my mockmvc:
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setControllerAdvice(new ExceptionHandlingControllerAdvice())
.build();
I can see in the console the test is picking up the exception handling methods:
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver Detected #ExceptionHandler methods in com.myapp...ExceptionHandlingControllerAdvice
The ExceptionHandlingControllerAdvice class has a handler method for a security exception:
#ExceptionHandler(SecurityException.class)
When my unit test throws a SecurityException, the test fails with a stacktrace instead of invoking the handler method in the controller advice.
Have I done something wrong?

Annotate your Spring mock mvc test with
#ImportAutoConfiguration(YourControllerAdvice.class)

If you are testing #ControllerAdvice, I would suggest not to use standalone setup, but rather load full Spring Context duting test:
Something like:
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.addFilters(springSecurityFilter)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();

Related

MockMVC instance not being instantiated

I am new to JUnit5 an MockMvc and am struggling to establish basic communication between a test class
and a Controller in a fully developed web application.
The UI is a Spring Boot application built on the MVC pattern. I would like to target one Controller
to prove my setup works and I have been using the MockMVC example in section 4.26.3 in the Spring Bott Reference
as a starting point (Spring Boot Reference)
When I run my test code I get the following a NullPointerException. Stepping through in debug mode shows me that
when I hit this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk()) the mockMvc
instance is null. It would seem that there is an issue building MockMvc but does not provide any further information.
This is puzzling because the code is almost identical to the example provided:
#WebMvcTest([my controller class])
public class TestController {
#Autowired
private MockMvc mockMvc;
#MockBeans for Services
#Test
public void testConfig() {}
#Test
public void testGet() throws Exception {
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk());
}
}
I am using version 5.7.1 of the junit jupiter engine. I am sorry I cannot post the whole pom as it is full of proprietory
information. I am also using Java 8 and Spring boot start test version 2.2.0.RELEASE.
I'd be very grateful for any suggestions as towhat I may have missed. Thanks.

Global exception handling in spring webflux during tests

I'm developing a service with spring webflux. I implemented exception handling using #ControllerAdvice. It works pretty well, but when I run integration tests it seems that #ControllerAdvice annotated component is not loaded, resulting in this response:
{
"timestamp":"2019-11-28T08:56:47.285+0000",
"path":"/fooController/bar",
"status":500,
"error":"Internal Server Error",
"message":"java.lang.IllegalStateException: Could not resolve parameter [1] in protected org.springframework.http.ResponseEntity<it.test.model.Response> it.test.exception.ExceptionHandlerController.handleServiceException(java.lang.Exception,org.springframework.web.context.request.WebRequest): No suitable resolver
}
This is my controller advice:
#ControllerAdvice
public class ExceptionHandlerController extends ResponseEntityExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(ExceptionHandlerController.class);
#ExceptionHandler
protected ResponseEntity<Response> handleServiceException(Exception ex, WebRequest request) {
this.logger.error("Error occurred: \"{}\"", ex.getMessage());
Response<Foo> response = new Response<>(new Foo(),
"generic error",
HttpStatus.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>(response, null, HttpStatus.OK);
}
}
And this is my integration test class
#ExtendWith(SpringExtension.class)
#WebFluxTest(MyController.class)
#ContextConfiguration(classes = {MyController.class, MyServiceImpl.class, ExceptionHandlerController.class })
public class MyControllerIT {
#Autowired
private WebTestClient webTestClient;
#Test
public void testShouldFail() throws IOException {
return this.webTestClient.get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.statusCode").isEqualTo(500);
}
}
if you read the documentation for #WebFluxTest it states:
Annotation that can be used for a Spring WebFlux test that focuses
only on Spring WebFlux components.
Using this annotation will disable full auto-configuration and instead
apply only configuration
relevant to WebFlux tests (i.e. #Controller, #ControllerAdvice,
#JsonComponent, Converter/GenericConverter, and WebFluxConfigurer
beans but not #Component, #Service or #Repository beans).
Typically #WebFluxTest is used in combination with #MockBean or
#Import to create any collaborators required by your #Controller
beans.
If you are looking to load your full application configuration and use
WebTestClient, you should consider #SpringBootTest combined with
#AutoConfigureWebTestClient rather than this annotation.
This means that
#ContextConfiguration(classes = {MyController.class, MyServiceImpl.class, ExceptionHandlerController.class })
is not what u use here. The #WebFluxTest annotation does not load #Component, #Service or #Repository
It is mainly used to test RestControllers only, and their advices.
The options you have seem to be:
loading MyController.class and then mock any dependency this class has (MyServiceImpl.class)
load a full Context using #SpringBootTest instead combined with
#AutoConfigureWebTestClient
Just a not on top of the already accepted answer which is totally correct.
The usage of #ContextConfiguration is necessary only for testing Functional endpoints. I Think this is probably what confuses people.
#WebFluxTest is used during functional endpoint tests for starting the web context and server.
But since we dont use controllers, we have to use #ContextConfiguration to bring the handler and routerFunction beans in our spring context.
In this case since we use annotated controller a simple #WebFluxTest(Controller.class) is more than enough for unit testing.
Adding controllerAdvice in the WebTestClient solved the issue.
WebTestClient.bindToController(controller)
.controllerAdvice(errorControllerAdvice)
.build()

SpringBoot Container in Junit Test

I am unable to understand few things while testing with JUnit, I have written a few tests, while some seem to work simply with
#RunWith(MockitoJUnitRunner.class)
and
#Mock
some seem not to work with them, and so I have to use
#RunWith(SpringRunner.class)
and
#MockBean
I understand that #MockBean is used when dealing with Spring Container, while #Mock is just to use to replicate/mock a certain class and its methods. But what would be the perfect time to use #MockBean?
#Test
public void addBulkFcmLog() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
AdminFcmResource adminFcmResource = AdminFcmResource.builder()
.adminId(123L)
.build();
given(this.fcmService.addBulkFcmLog(any(BulkFcmDataResource.class))).willReturn(adminFcmResource);
MockHttpServletResponse response = mockMvc.perform(
post(Routes.SEND_FCM + "/admin/logs/add")
.contentType(MediaType.APPLICATION_JSON)
.content(bulkFcmDataResourceJacksonTester.write(BulkFcmDataResource.builder().adminId(123L).build()).getJson()))
.andExpect(status().isCreated())
.andReturn()
.getResponse();
assertThat(response.getContentAsString()).isEqualTo(
adminFcmResourceJacksonTester.write(adminFcmResource).getJson()
);
assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value());
}
Above is a test I wrote for a controller in my spring boot application, but When i mock the fcmService class using #Mock, the response comes with an empty body, but then I changed It to #MockBean and Autowired the controller class then test returned the right result.
How did this happen?
In an integration-test context it is always advisable to work with beans (mocked if necessary). Few points to keep in mind:
#MockBean takes care of injecting the object, you do not need to set it yourself.
If your configuration is wrong (you did not specify a #Qualifier for example), the test will expose that problem on startup.
In an integration test you want to have your system in a state which resembles the prod scenario as close as possible, and #MockBean gets you closer than a plain #Mock
Many times the bean dependencies have no setters and manual injection would be just plain hard.

MockMvc and Spring Security - Null FilterChainProxy

I need to test my REST Controllers which they are secured using Spring Security. I'm using MockMvc as spring security reference suggests here
http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc
Test:
#ContextConfiguration(locations = "classpath:applicationContext.xml")
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
public class LikesTest {
protected MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
//.standaloneSetup(new MessageController())
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
#Test
#WithMockUser("user")
public void testAddLike() throws Exception {
mockMvc.perform(get("/like?msgId=4&like=false"));
}
}
When i'm running the JUnit test, i'm getting this failure trace
java.lang.NullPointerException at
org.springframework.security.web.FilterChainProxy.getFilters(FilterChainProxy.java:223)
Also if remove the bean inside applicationContext.xml:
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy"/>
Then i'm getting this failure trace:
java.lang.IllegalStateException: springSecurityFilterChain cannot be
null. Ensure a Bean with the name springSecurityFilterChain
implementing Filter is present or inject the Filter to be used. at
org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurer.beforeMockMvcCreated(SecurityMockMvcConfigurer.java:62)
I have no idea why the FilterChainProxy is null. Inside my Web.xml i have declare the DelegatingFilterProxy with filter-name springSecurityFilterChain and my application works fine. Please help me! Thanks
In your case, when using .webAppContextSetup you probably forgot to extend AbstractSecurityWebApplicationInitializer to make security filters initialized.
With standalone setup one would need to add her security config to #ContextConfiguration and autowire this bean:
#Autowired
FilterChainProxy springSecurityFilterChain;
Then prepare MockMvc like this:
MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(controller)
.apply(SecurityMockMvcConfigurers.springSecurity(springSecurityFilterChain))
.build();
I've managed to get it working.
Just make sure you have the correct Context Configurations. Also you don't need to declare a FilterChainProxy bean.
Thanks

Difference between MockMvc and RestTemplate in integration tests

Both MockMvc and RestTemplate are used for integration tests with Spring and JUnit.
Question is: what's the difference between them and when we should choose one over another?
Here are just examples of both options:
//MockMVC example
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
(...)
//RestTemplate example
ResponseEntity<User> entity = restTemplate.exchange("/api/users",
HttpMethod.GET,
new HttpEntity<String>(...),
User.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
As said in this
article you should use MockMvc when you want to test Server-side of application:
Spring MVC Test builds on the mock request and response from spring-test and does not require a running servlet container. The main difference is that actual Spring MVC configuration is loaded through the TestContext framework and that the request is performed by actually invoking the DispatcherServlet and all the same Spring MVC infrastructure that is used at runtime.
for example:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("servlet-context.xml")
public class SampleTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void getFoo() throws Exception {
this.mockMvc.perform(get("/foo").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}}
And RestTemplate you should use when you want to test Rest Client-side application:
If you have code using the RestTemplate, you’ll probably want to test it and to that you can target a running server or mock the RestTemplate. The client-side REST test support offers a third alternative, which is to use the actual RestTemplate but configure it with a custom ClientHttpRequestFactory that checks expectations against actual requests and returns stub responses.
example:
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(requestTo("/greeting"))
.andRespond(withSuccess("Hello world", "text/plain"));
// use RestTemplate ...
mockServer.verify();
also read this example
With MockMvc, you're typically setting up a whole web application context and mocking the HTTP requests and responses. So, although a fake DispatcherServlet is up and running, simulating how your MVC stack will function, there are no real network connections made.
RestTemplate can conveniently be initialized with a custom ClientHttpRequestFactory. Implementations usually create ClientHttpRequest objects that open actual TCP/HTTP(s) connections. But you don't have to. You can provide a mock implementation where you can do whatever you want. In fact, this is how the MockRestServiceServer utility operates, you could use that.
It is possible to use both RestTemplate and MockMvc!
This is useful if you have a separate client where you already do the tedious mapping of Java objects to URLs and converting to and from Json, and you want to reuse that for your MockMVC tests.
Here is how to do it:
#RunWith(SpringRunner.class)
#ActiveProfiles("integration")
#WebMvcTest(ControllerUnderTest.class)
public class MyTestShould {
#Autowired
private MockMvc mockMvc;
#Test
public void verify_some_condition() throws Exception {
MockMvcClientHttpRequestFactory requestFactory = new MockMvcClientHttpRequestFactory(mockMvc);
RestTemplate restTemplate = new RestTemplate(requestFactory);
ResponseEntity<SomeClass> result = restTemplate.getForEntity("/my/url", SomeClass.class);
[...]
}
}

Categories

Resources