In the software development process, testing is essential. There are two types of testing i.e. unit testing and integration testing. While unit testing focuses on testing the smallest component of an application, integration testing focuses on testing the end-to-end behavior of the application also known as endpoint testing. Integration testing verifies the @Controller and @RestController endpoints. In Spring Boot Applications, integration testing can be done using MockMvc.
What is MockMvC?
MockMVC is a testing framework provided by Spring that allows developers to write unit tests for web applications in a simulated environment. It provides a set of classes that can be used to test the behavior of controllers and other web-related components of an application, without the need to start an actual web container.
With MockMVC, developers can simulate HTTP requests and responses and test the behavior of their web application’s controllers, filters, and other components in a controlled environment. MockMVC also provides a fluent API that makes it easy to perform assertions on the response and validate the behavior of the application under test.
MockMVC is part of the Spring Test framework and can be used in conjunction with other testing frameworks, such as JUnit and Mockito, to write comprehensive and reliable unit tests for web applications.
When to use MockMvc?
Integration testing is done when your application has exposed endpoints. When an application has exposed endpoints, it means the users can create, read, update, and delete objects. This is also known as having CRUD functionality.
Normally, testing focuses on the actual logic of the controllers in your software. Extra testing is needed when you need to validate Request URL mappings and the Response status and object. This is where MockMvc comes in. MockMvc creates mock inputs and dependencies to test that the exposed endpoints are functioning the way they are supposed to without explicitly starting a servlet container.
It’s important to note that MockMvc works on all things in Spring MVC but for this tutorial, we’ll focus on @RestController.
How to use MockMvc?
MockMvc requires the following Maven dependencies in order to run the integration tests. Check your pom.xml file to ensure they are there, if not, add them. Other dependencies can be added based on your needs.
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
For this tutorial, we’re going to use a simple controller that returns a list of users.
package com.example.users; import javassist.NotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class UserController { @Autowired UserService userService; @GetMapping("/") public ResponseEntity<List<User>> displayUsers() { List<User> result = userService.getAllUsers(); return new ResponseEntity<List<User>>(result, HttpStatus.OK); } @GetMapping("/find/{firstName}") public ResponseEntity<List<User>> findUser(@PathVariable("firstName") String firstName) throws NotFoundException { List<User> result = userService.getUserByFirstName(firstName); return new ResponseEntity<List<User>>(result, HttpStatus.OK); } }
Creating a Test Class
We create a MockMvc instance using Spring Boots auto configuration methos. In UserControllerTest.java, we use annotation @WebMvcTest that tells Spring Boot we have created a testing class. We further specify the controller we are testing by annotating it as @WebMvcTest(UserController.class). This ensures that the endpoints prepared for testing are those linked to the user controller class.
@WebMvcTest(UserController.class) public class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; }
Notice that in the code example above I have used the @WebMvcTest and @MockBean annotations. Below are some key points about these two annotations:
@WebMvcTest annotation
@WebMvcTest
is a Spring Boot test annotation that can be used to test your Spring MVC web layer (i.e., controllers). When you add this annotation to a test class, it loads only the web-related beans and components, including the controllers, but not the entire application context.
@WebMvcTest
can be used in combination with@AutoConfigureMockMvc
to set up a mock MVC environment that allows you to perform requests against your controllers and verify the responses.@WebMvcTest
disables full auto-configuration and instead applies only configuration relevant to MVC tests.@WebMvcTest
loads only the specified controller(s) and their dependencies, so you can test your controllers in isolation from the rest of your application.@WebMvcTest
can be used with@MockBean
to replace any required dependencies with mock objects, so you can control the behavior of the dependencies in your tests.
@MockBean annotation
The @MockBean annotation is a Spring Boot test annotation that can be used to replace a bean with a mock object. This is useful when you want to test a specific component or module in isolation from the rest of the application and need to control the behavior of its dependencies.
Here are some key points about @MockBean
:
@MockBean
is used in conjunction with the@SpringBootTest
or@WebMvcTest
annotations.- When you add
@MockBean
to a test class, Spring Boot will automatically replace any bean of the same type in the application context with a mock object. - You can use the mock object to define the behavior of the dependency during testing, using standard mocking frameworks like Mockito or EasyMock.
- By default, the mock object will be a “strict” mock, which means that any calls to the object that are not explicitly defined will throw an exception. However, you can configure the mock to be “lenient” if needed.
Creating a Test Method
Our controller has two endpoints being mapped and is acting as a REST API for the User entity. So our first step is to test that the JSON result from the RestController is the correct one.
@Test public void getUserByFirstName() throws Exception { User user = new User("u054", "Rudiger"); List<User> allUsers = Arrays.asList(user); when(userService.getUserByFirstName("Rudiger")).thenReturn(allUsers); RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/find/Rudiger") .accept(MediaType.APPLICATION_JSON); MvcResult result = mockMvc.perform(requestBuilder).andReturn(); String expected = "[{firstName: \"Rudiger\", firstName:\"Rudiger\"}]"; JSONAssert.assertEquals(expected, result.getResponse().getContentAsString(), false); }
The above test validates the response received from REST API Endpoint.
Here’s what each line of the code does:
-
when(userService.getUserByFirstName("Rudiger"))
: This is a Mockito method that sets up a mock call to thegetUserByFirstName
method of theuserService
object. The method is called with a string argument “Rudiger”..thenReturn(allUsers)
: This is another Mockito method that specifies what the mock object should return when thegetUserByFirstName
method is called with “Rudiger”. In this case, theallUsers
object is returned.MockMvcRequestBuilders.get("/find/Rudiger")
: This creates a GET request to the “/find/Rudiger” endpoint. The endpoint is specified as a String argument to the get() method..accept(MediaType.APPLICATION_JSON)
: This sets the Accept header of the request to indicate that the client expects to receive JSON data in response. MediaType.APPLICATION_JSON is a constant that represents the JSON media type.mockMvc.perform(requestBuilder)
: This performs the request using the MockMvc framework, which allows you to test your Spring MVC controllers without starting a full web server. The requestBuilder object created in the previous lines is passed as an argument to the perform() method. Also, it does not send a real HTTP request over the network..andReturn()
: This returns a MvcResult object that represents the response to the request. You can use this object to inspect the response status, headers, and body.
Overall, this code creates a GET request to the “/find/Rudiger” endpoint with an Accept header set to “application/json”. The request is then performed using the MockMvc framework, and the resulting MvcResult object is returned.
Testing for Exceptions
Exception handling is an integral part of software development and in our sample controller, we had to handle the exception that a search could be done using a first name that we don’t have in our database or if the name is misspelled. When we use exception handlers, we have to test that they are actually handling these errors.
@Test public void getUserByFirstNameException() throws Exception { String exceptionParam = "Rowena"; RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/find/exception/{exception_id}", exceptionParam) .contentType(MediaType.APPLICATION_JSON); mockMvc.perform(requestBuilder) .andExpect(status().isNotFound()) .andExpect(result -> assertTrue(result.getResolvedException() instanceof NotFoundException)) .andExpect(result -> assertEquals("User doesn't exist", result.getResolvedException() .getMessage())); }
Notice that in the code example above I have used the andExpect()
method?
The andExpect()
method is used in the MockMvc
framework to assert expectations on the response of a request made to a web application. It takes a ResultMatcher
object as an argument, which is used to match and validate the response from the server.
The ResultMatcher
interface provides various methods to perform assertions on the response, such as checking the status code, headers, response body, resolved exceptions, etc. For example, status()
method is used to check the HTTP status code of the response, and content()
method is used to check the response body.
Below are assertions I have used:
status().isNotFound()
: This line checks that the HTTP status code of the response is “404 Not Found”.result -> assertTrue(result.getResolvedException() instanceof NotFoundException)
: This line checks that the resolved exception of the response is an instance of theNotFoundException
class. This is useful for testing that an expected exception was thrown during the request processing.result -> assertEquals("User doesn't exist", result.getResolvedException().getMessage())
: This line checks that the message of the resolved exception is “User doesn’t exist”. This is useful for testing the exact error message that was returned in the response.
Combined, these three matchers ensure that the response to the request is a “404 Not Found” error with a specific error message and exception type. If any of the matchers fail, the test will fail and the specific reason for the failure will be reported.
Does MockMvc.perform() send a real HTTP request?
No. MockMvc.perform()
method in Spring MockMvc sends a simulated HTTP request and receives a simulated HTTP response. It does not send a real HTTP request over the network. Instead, it internally uses the Spring DispatcherServlet
to simulate the request and response objects, and processes the request through the Spring MVC infrastructure. This allows you to test your controller logic and response handling without actually starting a server or sending real network requests.
Conclusion
In conclusion, MockMvc is a powerful tool for testing Spring Boot applications, allowing developers to simulate HTTP requests and test the behavior of their controllers without the need for a running server. Understanding how to effectively use MockMvc can greatly enhance the testing process for Spring Boot applications.
Expand your testing toolkit with our comprehensive Mockito tutorials available on the Testing Java Code page. Dive into the world of mock objects and learn how to effectively simulate dependencies, enabling you to create thorough and reliable tests for your Java applications.