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);
[...]
}
}
Related
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()
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.
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();
I am trying to to rest my rest classes in Spring MVC
If I run the following code (ran fine when the project was small but now fails) it tries to load all the different components in my application.
This includes beans which interact with external systems and need credentials in order to connect
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class TestDummyRest extends BaseRestTestCase{
#Autowired
private MockMvc mockMvc;
#MockBean
private IDummyServices mockDummyServices;
#Test
public void getSendGoodMessage() throws Exception {
given(mockDummyServices.sendGoodMessage(Mockito.anyString())).willReturn(true);
mockMvc.perform(get("/dummy"))
.andExpect(status().isOk())
.andExpect(content().contentType(TEXT_PLAIN_CONTENT_TYPE));
verify(mockDummyServices, times(1)).sendGoodMessage(Mockito.anyString());
}
}
How do I tell my test classes not to load the #Configuration or #Component classes of my application?
Instead of not creating other classes in your application, you could only create the classes you are interested in, see 15.6.1 Server-Side Tests - Setup Options
The second is to simply create a controller instance manually without
loading Spring configuration. Instead basic default configuration,
roughly comparable to that of the MVC JavaConfig or the MVC namespace,
is automatically created and can be customized to a degree:
public class MyWebTests {
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
You need to use #TestComponent and #TestConfiguration for this as explained in Spring doc here
I'm implementing a service using Spring Boot and Spring Cloud Config service to provide the configuration values. In my Service I have a couple of config values which need to refresh when the value changes in the remote Git repo, and I was using #RefreshScope to enable that feature.
The problem comes when I try to inject a mock for RestTemplate in that service, it appears to ignore it and use the autowired instance instead. If I comment out the annotation it seems to work fine.
Here's the code for the Service:
#Service
#RefreshScope
public class MyServiceImpl implements MyService {
private static final Logger LOG = Logger.getLogger(MyServiceImpl.class);
#Autowired
public RestTemplate restTemplate;
#Value("${opts.default}")
private String default;
#Value("${opts.address}")
private String address;
#Value("${opts.separator}")
private String separator;
...
}
Test source code:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class ServiceTest {
#Mock
private RestTemplate restTemplate;
#Autowired
#InjectMocks
private MyServiceImpl service;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
public void testMethod() throws Exception {
when(restTemplate.postForObject(anyString(), any(), eq(ServiceResponse.class), anyMap())).thenReturn(getSuccessfulResponse());
ServiceResponse response = service.doYourStuff();
Assert.assertNotNull(response);
Assert.assertTrue(response.isSuccessful());
}
...
}
When adding the #RefreshScope the bean becomes a proxy instead of an actual raw implementation. Currently the RestTemplate is set on the proxy rather then the underlying instance. (If you debug you would see that your MyServiceImpl is actually more like an instance of MyServiceImpl$SpringCgLib#353234).
To fix you need to manually set the dependency using ReflectionTestUtils and AopTestUtils. The latter is to obtain the actual proxy.
Remove the #InjectMocks annotation and add the following to your setup method after the initialization of the mocks:
Object actualTarget = AopTestUtils.getUltimateTargetObject(service);
ReflectionTestUtils.setfield(actualTarget, "restTemplate", restTemplate);
For versions earlier as 4.2 the following might do the trick
Object actualTarget = (service instanceof Advised) ? ((Advised) service).getTargetSource().getTarget() : service;
The problem is that Mockito doesn't detect the proxy and just sets the field. The ReflectionTestUtils doesn't detect the proxy either hence the manual unwrapping. I actually stepped into this trap a couple of times before, which led me to create SPR-14050 this morning to have it embedded in the ReflectionTestUtils to easy the pain a little.