Unit testing Spring Boot controllers

One of the important aspect of software development is testing. At developer level we need to write Unit test and Integration test while developing the project.

In this blog post I am going to discuss approaches you can take to unit test the Spring Boot controllers. When you search the internet for sample code for unit testing Spring Boot controllers, you might get confused because there are 4 different ways you can write unit test classes.

In this post I am going to discuss all the different approaches. I will also share sample code and cover frequently used HTTP method calls while writing the test cases.

Required Software

Java 8

Spring Boot 2.2.1

Junit 5

Sample Application

For writing test cases I am going to use the springboot-restapi-demo project from my GitHub repository. I am going to write unit test cases for PostController class.

Below you can see interaction between different layers in sample application

Adding dependency jars for Unit testing

Before we start writing the test cases first we need to add the required dependency jars in the class path. Since we are using the maven project, lets add dependency jars in the pom.xml file. We are limiting the scope of these jars only to test as we do not require them in production jar

<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
Code language: Java (java)
https://gist.github.com/sureshgadupu/67d5e0eebb5e41e110dfed42c2bbd100

Sample Controller

Below you can see the code for PostController. I posted code from the repository for easy reference.

https://gist.github.com/sureshgadupu/3d7007d80ccb92c30bc1ba17a7d52545

In unit testing we are only intended to test controller class, so we will use Mockito to mock/simulate services and repository classes depending on the situation.

1) Using SpringBootTest with Real WebServer

In this approach we will use @SpringBootTest annotation and start actual the webserver ( in our case its Tomcat) to test the controller methods.

webEnvironment = WebEnvironment.DEFINED_PORT parameter starts webserver at defined port. webEnvironment=WebEnvironment.RANDOM_PORT option also can be used to avoid conflict, if you are running tests in parallel.

This approach resembles testing application with external client like Postman or SoapUI

Now let’s look at the code

@SpringBootTest – The @SpringBootTest annotation tells Spring Boot to look for a main configuration class (one with @SpringBootApplication) and use that to start a Spring application context. webEnvironment = WebEnvironment.DEFINED_PORT parameter starts actual HTTP webserver. In below screenshot you can see the Tomcat server starts on port 8085.

@MockBean -The annotation will create the mock bean and will replace any existing bean of the same type in the application context. In the code mock will replace the actual PostService bean with the mocked bean in the application context.

TestRestTemplate – This class offers templates for common scenarios by HTTP method to prepare web requests. If you are using the @SpringBootTest annotation with an embedded server,  TestRestTemplate class is automatically available and can be @Autowired into test.

The biggest advantage of this approach is it has very simple configuration and the same approach can be used for writing integration tests. In above code instead of mocking the `PostService` class, if we can autowire the actual class it will become an integration test.

The biggest disadvantage of this approach is, even though we are testing a single controller, @SpringBootTest annotation will boot the complete application which adds unnecessary overhead. This approach starts an entire webserver.

package com.sureshtech.springbootrestdemo.unittest; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.server.ResponseStatusException; import com.sureshtech.springbootrestdemo.entity.Comment; import com.sureshtech.springbootrestdemo.entity.Post; import com.sureshtech.springbootrestdemo.service.PostService; @SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) public class PostControllerTest4 { Logger log = LoggerFactory.getLogger(PostControllerTest4.class); @MockBean private PostService postService; @Autowired private TestRestTemplate testRestTemplate ; private static final String ROOT_URL = "http://localhost:8085"; @Test public void testAllPosts() { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); Comment comment = new Comment(); comment.setId(1); comment.setContent("This is comment"); comment.setEmail("[email protected]@email.com"); Comment comment2 = new Comment(); comment2.setId(2); comment2.setContent("This is comment-2"); comment2.setEmail("[email protected]@email.com"); List<Comment> commentsList = new ArrayList<>(); commentsList.add(comment); commentsList.add(comment2); post.setComments(commentsList); Post post2 = new Post(); post2.setId(2); post2.setContent("Content2"); post2.setTitle("Title2"); List<Post> allPosts = new ArrayList<>(); allPosts.add(post); allPosts.add(post2); when(postService.getAllPosts()).thenReturn(allPosts); ResponseEntity<Post[]> responseEntity = testRestTemplate.getForEntity(ROOT_URL+"/posts", Post[].class); List<Post> posts = Arrays.asList(responseEntity.getBody()); // for (Post p : posts) { // log.info("Post = "+ p.getId() + " : "+ p.getTitle()); // } assertNotNull(posts); assertArrayEquals("", allPosts.toArray(), posts.toArray()); assertEquals( allPosts.size(), posts.size()); } @Test public void testGetPostById() { int postId = 1; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); when(postService.getPost(postId)).thenReturn(post); ResponseEntity<Post> responseEntity = testRestTemplate.getForEntity(ROOT_URL+"/posts/{id}", Post.class,postId); Post p = (Post) responseEntity.getBody(); assertEquals(postId, p.getId().intValue()); assertEquals(post.getTitle(), p.getTitle()); } @Test public void testGetPostByIdThrowsException() { int postId = 199; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); when(postService.getPost(postId)).thenThrow(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Resource Not found")); ResponseEntity<Post> responseEntity = testRestTemplate.getForEntity(ROOT_URL+"/posts/{id}", Post.class,postId); assertNull( responseEntity.getBody().getContent()); assertEquals(400,responseEntity.getStatusCodeValue()); } @Test public void testCreatePost() { int postId = 1; Post post = new Post(); post.setId(postId); post.setTitle("Testing title"); post.setContent("Testing content!!"); when(postService.createPost(post)).thenReturn(post); ResponseEntity<Post> postResponse = testRestTemplate.postForEntity(ROOT_URL+"/posts", post, Post.class); Post p = (Post) postResponse.getBody(); assertNotNull(postResponse.getBody()); assertEquals(post.getTitle(),p.getTitle()); } }
Code language: Java (java)
https://gist.github.com/sureshgadupu/ed3fed6c953f404b702fbfe271b95a50
console log

2) Using SpringBootTest with Mock WebServer

In the mock webserver approach, we are not starting an actual webserver , we are just simulating the server so instead of using TestRestTemplate ,we are using Spring MockMvc to perform HTTP calls.

Now let’s look at the code

@SpringBootTest – The @SpringBootTest annotation tells Spring Boot to look for a main configuration class (one with @SpringBootApplication) and use that to start a Spring application context. This annotation without any parameter or (webEnvironment = WebEnvironment.MOCK) starts a mock webserver

@AutoConfigureMockMvc – Enables all auto-configuration related to MockMvc . Again, this is a subset of overall auto-configuration.

@MockBean -The annotation will create the mock bean and will replace any existing bean of the same type in the application context. In the code mock will replace the actual PostService bean with the mocked bean in the application context.

Since we are mocking the webserver , it reduces the overhead of the starting webserver on the other side it boots the whole application just like the first approach.

package com.sureshtech.springbootrestdemo.unittest; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.ArrayList; import java.util.List; import org.hamcrest.Matchers; import org.hamcrest.core.IsNull; import org.hamcrest.text.IsEmptyString; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.server.ResponseStatusException; import com.fasterxml.jackson.databind.ObjectMapper; import com.sureshtech.springbootrestdemo.entity.Comment; import com.sureshtech.springbootrestdemo.entity.Post; import com.sureshtech.springbootrestdemo.service.PostService; @SpringBootTest @AutoConfigureMockMvc public class PostControllerTest3 { Logger log = LoggerFactory.getLogger(PostControllerTest3.class); @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private PostService postService; @BeforeEach void setUp() { } @Test public void testGetPost() throws Exception { int postId = 1; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); when(postService.getPost(postId)).thenReturn(post); this.mockMvc.perform(get("/posts/{id}", postId)) .andExpect(status().isOk()) // check HTTP status .andExpect(jsonPath("$.content").value("Content")) // check "content" .andExpect(jsonPath("$.title").value("Title")) // check "title" .andExpect(jsonPath("$.id", is(postId))); // check "id" } @Test public void testGetPostThrowsException() throws Exception { int postId = 100; when(postService.getPost(postId)) .thenThrow(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Post Not found :" + postId)); this.mockMvc.perform(get("/posts/{id}", postId)) .andExpect(status().isBadRequest()) // check HTTP status // .andDo(handler -> log.info("response :" + handler.getResponse().getErrorMessage())) .andExpect(response -> assertTrue(response.getResolvedException() instanceof ResponseStatusException)) .andExpect(response -> assertEquals("Post Not found :" + postId,response.getResponse().getErrorMessage())); } @Test public void testGetAllPosts() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); Comment comment = new Comment(); comment.setId(1); comment.setContent("This is comment"); comment.setEmail("[email protected]@email.com"); Comment comment2 = new Comment(); comment2.setId(2); comment2.setContent("This is comment-2"); comment2.setEmail("[email protected]@email.com"); List<Comment> commentsList = new ArrayList<>(); commentsList.add(comment); commentsList.add(comment2); post.setComments(commentsList); Post post2 = new Post(); post2.setId(2); post2.setContent("Content2"); post2.setTitle("Title2"); List<Post> allPosts = new ArrayList<>(); allPosts.add(post); allPosts.add(post2); when(postService.getAllPosts()).thenReturn(allPosts); this.mockMvc.perform(get("/posts/")).andExpect(status().isOk()) // check HTTP status // .andDo(handler ->log.info(handler.getResponse().getContentAsString())) .andExpect(jsonPath("$.length()", is(2))) // check the size of post array in response .andExpect(jsonPath("$[0].content").value("Content")).andExpect(jsonPath("$[0].title").value("Title")) .andExpect(jsonPath("$[0].createdOn").value(IsNull.notNullValue())) .andExpect(jsonPath("$[0].updatedOn").value(IsNull.nullValue())).andExpect(jsonPath("$[0].id", is(1))) .andExpect(jsonPath("$[0].comments.*", hasSize(2))) // check the size of comments array in response .andExpect(jsonPath("$[0].comments[0].id", is(1))) .andExpect(jsonPath("$[0].comments[0].content", is("This is comment"))) .andExpect(jsonPath("$[1].content").value("Content2")).andExpect(jsonPath("$[1].title").value("Title2")) .andExpect(jsonPath("$[1].id", is(2))) .andExpect(jsonPath("$[1].comments", is(IsEmptyString.emptyOrNullString()))) // check for empty or null value .andExpect(jsonPath("$[1].comments", is(IsNull.nullValue()))) // check for null value .andExpect(jsonPath("$[0:].content", Matchers.containsInAnyOrder("Content2", "Content"))) .andExpect(jsonPath("$[0:].content", Matchers.containsInRelativeOrder("Content", "Content2"))); } @Test public void testCreatePost() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.createPost(any((Post.class)))).thenReturn(post); this.mockMvc.perform(post("/posts/").contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.content").value("Content")) .andExpect(jsonPath("$.title").value("Title")) .andExpect(jsonPath("$.id", is(1))); } @Test public void testCreatePost2() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.createPost(any((Post.class)))).thenReturn(post); MockHttpServletResponse response = this.mockMvc.perform( post("/posts/").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(post))) .andReturn().getResponse(); Post postObj = objectMapper.readValue(response.getContentAsString(), Post.class); assertEquals(post.getId(), postObj.getId()); assertEquals(post.getContent(), postObj.getContent()); assertEquals(post.getTitle(), postObj.getTitle()); assertNotNull(post.getCreatedOn()); assertNull(post.getUpdatedOn()); } @Test public void testUpdatePost() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.updatePost(any((Post.class)))).thenReturn(post); this.mockMvc.perform(put("/posts/{id}", 1).contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isOk()) .andExpect(jsonPath("$.content").value("Content")) .andExpect(jsonPath("$.title").value("Title")) .andExpect(jsonPath("$.id", is(1))) .andDo(handler -> log.info(handler.getResponse().getContentAsString())); } @Test public void testUpdatePostThrowsException() throws Exception { int postId = 100; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); when(postService.updatePost(any((Post.class)))) .thenThrow(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Resource Not found")); this.mockMvc.perform(put("/posts/{id}", postId).contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isBadRequest()) .andExpect(response -> assertTrue(response.getResolvedException() instanceof ResponseStatusException)) .andExpect(response -> assertEquals("Resource Not found", response.getResponse().getErrorMessage())) .andDo(handler -> log.info(handler.getResponse().getContentAsString())); } @Test public void testPostDelete() throws Exception { int postId = 100; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); this.mockMvc.perform(delete("/posts/{id}", postId)).andExpect(status().isOk()); } }
Code language: Java (java)
https://gist.github.com/sureshgadupu/8e7414edb860d7ba577bb06ee0557ef7

Below you can see the console log from above example test class. You can clearly see that there is no tomcat server startup.

console log

3) Spring MockMvc with WebApplicationContext

In the first two approaches, SpringBootTest annotation instantiates the whole application context even though we are interested in testing only the web layer.

@WebMvcTest annotation instantiates only the web layer of the application. We can also make the annotation instantiate only controller we are testing by specifying the controller class.

@MockMvc is used for simulating HTTP method calls. With @WebMvcTest annotation MockMVC instance gets auto-configured and available in the context to use.

@MockBean -The annotation will create the mock bean and will replace any existing bean of the same type in the application context. In the code mock will replace the actual PostService bean with the mocked bean in the application context.

This is the one of the lightweight approach for testing SpringBoot controllers since it loads in only controller we are testing.

package com.sureshtech.springbootrestdemo.unittest; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.ArrayList; import java.util.List; import org.hamcrest.Matchers; import org.hamcrest.core.IsNull; import org.hamcrest.text.IsEmptyString; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.server.ResponseStatusException; import com.fasterxml.jackson.databind.ObjectMapper; import com.sureshtech.springbootrestdemo.controller.PostController; import com.sureshtech.springbootrestdemo.entity.Comment; import com.sureshtech.springbootrestdemo.entity.Post; import com.sureshtech.springbootrestdemo.service.PostService; @WebMvcTest(controllers = PostController.class) public class PostControllerTest1 { Logger log = LoggerFactory.getLogger(PostController.class); @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private PostService postService; @Test public void testGetPost() throws Exception { int postId = 1; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); when(postService.getPost(postId)).thenReturn(post); this.mockMvc.perform(get("/posts/{id}", postId)) .andExpect(status().isOk()) // check HTTP status .andExpect(jsonPath("$.content").value("Content")) // check "content" .andExpect(jsonPath("$.title").value("Title")) // check "title" .andExpect(jsonPath("$.id", is(postId))); // check "id" verify(postService).getPost(postId); } @Test public void testGetPostThrowsException() throws Exception { int postId = 100; when(postService.getPost(postId)) .thenThrow(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Post Not found :" + postId)); this.mockMvc.perform(get("/posts/{id}", postId)) .andExpect(status().isBadRequest()) // check HTTP status // .andDo(handler -> log.info("response :" + handler.getResponse().getErrorMessage())) .andExpect(response -> assertTrue(response.getResolvedException() instanceof ResponseStatusException)) .andExpect(response -> assertEquals("Post Not found :" + postId,response.getResponse().getErrorMessage())); verify(postService).getPost(postId); } @Test public void testGetAllPosts() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); Comment comment = new Comment(); comment.setId(1); comment.setContent("This is comment"); comment.setEmail("[email protected]@email.com"); Comment comment2 = new Comment(); comment2.setId(2); comment2.setContent("This is comment-2"); comment2.setEmail("[email protected]@email.com"); List<Comment> commentsList = new ArrayList<>(); commentsList.add(comment); commentsList.add(comment2); post.setComments(commentsList); Post post2 = new Post(); post2.setId(2); post2.setContent("Content2"); post2.setTitle("Title2"); List<Post> allPosts = new ArrayList<>(); allPosts.add(post); allPosts.add(post2); when(postService.getAllPosts()).thenReturn(allPosts); this.mockMvc.perform(get("/posts/")).andExpect(status().isOk()) // check HTTP status // .andDo(handler ->log.info(handler.getResponse().getContentAsString())) .andExpect(jsonPath("$.length()", is(2))) // check the size of post array in response .andExpect(jsonPath("$[0].content").value("Content")).andExpect(jsonPath("$[0].title").value("Title")) .andExpect(jsonPath("$[0].createdOn").value(IsNull.notNullValue())) .andExpect(jsonPath("$[0].updatedOn").value(IsNull.nullValue())).andExpect(jsonPath("$[0].id", is(1))) .andExpect(jsonPath("$[0].comments.*", hasSize(2))) // check the size of comments array in response .andExpect(jsonPath("$[0].comments[0].id", is(1))) .andExpect(jsonPath("$[0].comments[0].content", is("This is comment"))) .andExpect(jsonPath("$[1].content").value("Content2")).andExpect(jsonPath("$[1].title").value("Title2")) .andExpect(jsonPath("$[1].id", is(2))) .andExpect(jsonPath("$[1].comments", is(IsEmptyString.emptyOrNullString()))) // check for empty or null value .andExpect(jsonPath("$[1].comments", is(IsNull.nullValue()))) // check for null value .andExpect(jsonPath("$[0:].content", Matchers.containsInAnyOrder("Content2", "Content"))) .andExpect(jsonPath("$[0:].content", Matchers.containsInRelativeOrder("Content", "Content2"))); verify(postService).getAllPosts(); } @Test public void testCreatePost() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.createPost(any((Post.class)))).thenReturn(post); this.mockMvc.perform(post("/posts/").contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.content").value("Content")) .andExpect(jsonPath("$.title").value("Title")) .andExpect(jsonPath("$.id", is(1))); verify(postService).createPost(any((Post.class))); } @Test public void testCreatePost2() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.createPost(any((Post.class)))).thenReturn(post); MockHttpServletResponse response = this.mockMvc.perform( post("/posts/").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(post))) .andReturn().getResponse(); Post postObj = objectMapper.readValue(response.getContentAsString(), Post.class); assertEquals(post.getId(), postObj.getId()); assertEquals(post.getContent(), postObj.getContent()); assertEquals(post.getTitle(), postObj.getTitle()); assertNotNull(post.getCreatedOn()); assertNull(post.getUpdatedOn()); verify(postService).createPost(any((Post.class))); } @Test public void testUpdatePost() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.updatePost(any((Post.class)))).thenReturn(post); this.mockMvc.perform(put("/posts/{id}", 1).contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isOk()) .andExpect(jsonPath("$.content").value("Content")) .andExpect(jsonPath("$.title").value("Title")) .andExpect(jsonPath("$.id", is(1))) .andDo(handler -> log.info(handler.getResponse().getContentAsString())); verify(postService).updatePost(any((Post.class))); } @Test public void testUpdatePostThrowsException() throws Exception { int postId = 100; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); when(postService.updatePost(any((Post.class)))) .thenThrow(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Resource Not found")); this.mockMvc.perform(put("/posts/{id}", postId).contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isBadRequest()) .andExpect(response -> assertTrue(response.getResolvedException() instanceof ResponseStatusException)) .andExpect(response -> assertEquals("Resource Not found", response.getResponse().getErrorMessage())) .andDo(handler -> log.info(handler.getResponse().getContentAsString())); verify(postService).updatePost(any((Post.class))); } @Test public void testPostDelete() throws Exception { int postId = 100; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); this.mockMvc.perform(delete("/posts/{id}", postId)).andExpect(status().isOk()); verify(postService).deletePost(postId); } }
Code language: Java (java)
https://gist.github.com/sureshgadupu/d51c419d56a82a13e0f408be33a32962

Below you can see the console log when you run the test in IDE

console log

4) Spring MockMvc in Standalone mode

Unlike the above approach where MockMvc i s autoconfigured , we can manually instantiate the MockMvc in standalone mode also.

In the below sample code, you can see the setUp method code for mocking PostController

In the below code you will find the couple of new annotations @InjectMock and @Mock.Lets see what they does.

@Mock creates a mock object.

@InjectMocks creates an instance of the class and injects the mocks that are created with the @Mock annotations into this instance.

These annotations can only be used when you are writing Junit 5 test classes with @ExtendWith(MockitoExtension.class).

Like the third approach, this approach also instantiates only the controller which is under test. Since we are not using any Spring context, autoconfiguration is not in place, so we have to manually set up any configuration of outside the controller class.

In the below code example, in setup() method, we are configuring the Controller Advice manually.

package com.sureshtech.springbootrestdemo.unittest; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.ArrayList; import java.util.List; import org.hamcrest.Matchers; import org.hamcrest.core.IsNull; import org.hamcrest.text.IsEmptyString; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.server.ResponseStatusException; import com.fasterxml.jackson.databind.ObjectMapper; import com.sureshtech.springbootrestdemo.controller.PostController; import com.sureshtech.springbootrestdemo.entity.Comment; import com.sureshtech.springbootrestdemo.entity.Post; import com.sureshtech.springbootrestdemo.exception.GlobalExceptionHandler; import com.sureshtech.springbootrestdemo.service.PostService; @ExtendWith(MockitoExtension.class) public class PostControllerTest2 { Logger log = LoggerFactory.getLogger(PostControllerTest2.class); private MockMvc mockMvc; private ObjectMapper objectMapper; @InjectMocks private PostController postController; @Mock private PostService postService; @BeforeEach void setUp() { objectMapper = new ObjectMapper(); // MockMvc standalone approach mockMvc = MockMvcBuilders.standaloneSetup(postController) .setControllerAdvice(new GlobalExceptionHandler()) .build(); } @Test public void testGetPost() throws Exception { int postId = 1; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); when(postService.getPost(postId)).thenReturn(post); this.mockMvc.perform(get("/posts/{id}", postId)) .andExpect(status().isOk()) // check HTTP status .andExpect(jsonPath("$.content").value("Content")) // check "content" .andExpect(jsonPath("$.title").value("Title")) // check "title" .andExpect(jsonPath("$.id", is(postId))); // check "id" } @Test public void testGetPostThrowsException() throws Exception { int postId = 100; when(postService.getPost(postId)) .thenThrow(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Post Not found :" + postId)); this.mockMvc.perform(get("/posts/{id}", postId)) .andExpect(status().isBadRequest()) // check HTTP status // .andDo(handler -> log.info("response :" + handler.getResponse().getErrorMessage())) .andExpect(response -> assertTrue(response.getResolvedException() instanceof ResponseStatusException)) .andExpect(response -> assertEquals("Post Not found :" + postId,response.getResponse().getErrorMessage())); } @Test public void testGetAllPosts() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); Comment comment = new Comment(); comment.setId(1); comment.setContent("This is comment"); comment.setEmail("[email protected]@email.com"); Comment comment2 = new Comment(); comment2.setId(2); comment2.setContent("This is comment-2"); comment2.setEmail("[email protected]@email.com"); List<Comment> commentsList = new ArrayList<>(); commentsList.add(comment); commentsList.add(comment2); post.setComments(commentsList); Post post2 = new Post(); post2.setId(2); post2.setContent("Content2"); post2.setTitle("Title2"); List<Post> allPosts = new ArrayList<>(); allPosts.add(post); allPosts.add(post2); when(postService.getAllPosts()).thenReturn(allPosts); this.mockMvc.perform(get("/posts/")).andExpect(status().isOk()) // check HTTP status // .andDo(handler ->log.info(handler.getResponse().getContentAsString())) .andExpect(jsonPath("$.length()", is(2))) // check the size of post array in response .andExpect(jsonPath("$[0].content").value("Content")).andExpect(jsonPath("$[0].title").value("Title")) .andExpect(jsonPath("$[0].createdOn").value(IsNull.notNullValue())) .andExpect(jsonPath("$[0].updatedOn").value(IsNull.nullValue())).andExpect(jsonPath("$[0].id", is(1))) .andExpect(jsonPath("$[0].comments.*", hasSize(2))) // check the size of comments array in response .andExpect(jsonPath("$[0].comments[0].id", is(1))) .andExpect(jsonPath("$[0].comments[0].content", is("This is comment"))) .andExpect(jsonPath("$[1].content").value("Content2")).andExpect(jsonPath("$[1].title").value("Title2")) .andExpect(jsonPath("$[1].id", is(2))) .andExpect(jsonPath("$[1].comments", is(IsEmptyString.emptyOrNullString()))) // check for empty or null value .andExpect(jsonPath("$[1].comments", is(IsNull.nullValue()))) // check for null value .andExpect(jsonPath("$[0:].content", Matchers.containsInAnyOrder("Content2", "Content"))) .andExpect(jsonPath("$[0:].content", Matchers.containsInRelativeOrder("Content", "Content2"))); } @Test public void testCreatePost() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.createPost(any((Post.class)))).thenReturn(post); this.mockMvc.perform(post("/posts/").contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.content").value("Content")) .andExpect(jsonPath("$.title").value("Title")) .andExpect(jsonPath("$.id", is(1))); } @Test public void testCreatePost2() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.createPost(any((Post.class)))).thenReturn(post); MockHttpServletResponse response = this.mockMvc.perform( post("/posts/").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(post))) .andReturn().getResponse(); Post postObj = objectMapper.readValue(response.getContentAsString(), Post.class); assertEquals(post.getId(), postObj.getId()); assertEquals(post.getContent(), postObj.getContent()); assertEquals(post.getTitle(), postObj.getTitle()); assertNotNull(post.getCreatedOn()); assertNull(post.getUpdatedOn()); Mockito.verify(postService).createPost(any(Post.class)); } @Test public void testUpdatePost() throws Exception { Post post = new Post(); post.setId(1); post.setContent("Content"); post.setTitle("Title"); when(postService.updatePost(any((Post.class)))).thenReturn(post); this.mockMvc.perform(put("/posts/{id}", 1).contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isOk()) .andExpect(jsonPath("$.content").value("Content")) .andExpect(jsonPath("$.title").value("Title")) .andExpect(jsonPath("$.id", is(1))) .andDo(handler -> log.info(handler.getResponse().getContentAsString())); Mockito.verify(postService).updatePost(any(Post.class)); } @Test public void testUpdatePostThrowsException() throws Exception { int postId = 100; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); when(postService.updatePost(any((Post.class)))) .thenThrow(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Resource Not found")); this.mockMvc.perform(put("/posts/{id}", postId).contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(post))) .andExpect(status().isBadRequest()) .andExpect(response -> assertTrue(response.getResolvedException() instanceof ResponseStatusException)) .andExpect(response -> assertEquals("Resource Not found", response.getResponse().getErrorMessage())) .andDo(handler -> log.info(handler.getResponse().getContentAsString())); } @Test public void testPostDelete() throws Exception { int postId = 100; Post post = new Post(); post.setId(postId); post.setContent("Content"); post.setTitle("Title"); this.mockMvc.perform(delete("/posts/{id}", postId)).andExpect(status().isOk()); } }
Code language: Java (java)
https://gist.github.com/sureshgadupu/3e38b7f2892d52bf20a3d845b45e66ae

Below you can see the console log when you run the test in IDE

console log

Similar Posts

One Comment

Comments are closed.