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)
Sample Controller
Below you can see the code for PostController
. I posted code from the repository for easy reference.
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("test@[email protected]");
Comment comment2 = new Comment();
comment2.setId(2);
comment2.setContent("This is comment-2");
comment2.setEmail("test2@[email protected]");
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)
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("test@[email protected]");
Comment comment2 = new Comment();
comment2.setId(2);
comment2.setContent("This is comment-2");
comment2.setEmail("test2@[email protected]");
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)
Below you can see the console log from above example test class. You can clearly see that there is no tomcat server startup.
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("test@[email protected]");
Comment comment2 = new Comment();
comment2.setId(2);
comment2.setContent("This is comment-2");
comment2.setEmail("test2@[email protected]");
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)
Below you can see the console log when you run the test in IDE
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("test@[email protected]");
Comment comment2 = new Comment();
comment2.setId(2);
comment2.setContent("This is comment-2");
comment2.setEmail("test2@[email protected]");
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)
Below you can see the console log when you run the test in IDE
COFFEEEEEEE