You are currently viewing Mastering Spring Boot Unit Test for Controller Layer: A Comprehensive Guide

Mastering Spring Boot Unit Test for Controller Layer: A Comprehensive Guide

In the realm of Spring Boot development, the Controller layer serves as the gateway to your application, handling incoming requests, and orchestrating responses. Ensuring the correctness and reliability of this crucial layer is paramount for building robust and user-friendly web applications. Welcome to our comprehensive guide, where we embark on a journey to master the art of “Spring Boot unit test for controller layer.” This guide is designed to equip you with the knowledge and tools needed to create effective unit tests for your Controller layer, making sure your application’s endpoints function seamlessly.

Unit testing in the context of the Controller layer isn’t just about verifying HTTP request-response interactions; it’s about crafting a comprehensive testing strategy that guarantees your application’s endpoints work as expected. Throughout this article Mastering Spring Boot Unit Test for Controller Layer: A Comprehensive Guide, we’ll explore the intricacies of unit testing, with a special focus on the Controller layer. We’ll dive into essential tools and libraries provided by Spring Boot, share best practices, and address common challenges. By the end of this journey, you’ll have the skills and confidence to assess and enhance the reliability of your Controller layer, resulting in web applications that deliver outstanding user experiences.

Whether you’re a seasoned Spring Boot developer or just starting your journey, mastering “Spring Boot unit test for controller layer” is a pivotal step in building high-quality web applications. These tests not only validate the correctness of your endpoints but also contribute to a robust and maintainable codebase. So, if you’re ready to elevate your Spring Boot development skills and ensure the excellence of your Controller layer, let’s embark on this comprehensive exploration together.

This is the third part of the tutorial where we will learn about testing the Controller layer. If you miss Part 1 and Part 2, then you can check the previous post here. In the next article, we will learn about writing the JUnit test cases for Integration testing. So by the end, you will have complete knowledge of Integration testing with Spring Boot.




Read More:

Watch the Video on Spring Boot Unit Test for Controller Layer


Create Controller Layer


Let’s create a class MovieController.java inside in.bushansirgur.springbootjunit.serviceand add the following content

package in.bushansirgur.springbootjunit.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import in.bushansirgur.springbootjunit.model.Movie;
import in.bushansirgur.springbootjunit.service.MovieService;

@RestController
@RequestMapping("/movies")
public class MovieController {
	
	@Autowired
	private MovieService movieService;
	
	@PostMapping
	@ResponseStatus(HttpStatus.CREATED)
	public Movie create(@RequestBody Movie movie) {
		return movieService.save(movie);
	}
	
	@GetMapping
	@ResponseStatus(HttpStatus.OK)
	public List<Movie> read() {
		return movieService.getAllMovies();
	}
	
	@GetMapping("/{id}")
	@ResponseStatus(HttpStatus.OK)
	public Movie read(@PathVariable Long id) {
		return movieService.getMovieById(id);
	}
	
	@DeleteMapping("/{id}")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	public void delete(@PathVariable Long id) {
		movieService.deleteMovie(id);
	}
	
	@ResponseStatus(HttpStatus.OK)
	@PutMapping("/{id}")
	public Movie update(@PathVariable Long id, @RequestBody Movie movie) {
		return movieService.updateMovie(movie, id);
	}
}

This is just a simple Spring Boot Controller class that has some REST endpoints to perform the database operations such as Create, Read, Update and Delete. We added the @RestController annotation to make this class return the response body. We also added the @RequestMapping annotation to provide the URI at the class level.



Create JUnit Class


Now we need to write JUnit test cases for the above controller class. In order to test the Controller layer, we will also use Mockito. It is a testing framework, it will help us to mock the objects and we can stub the methods. It means that we will not test the real implementation, instead, we will provide a fake implementation. Let’s create MovieControllerTest.class inside src/test/java and add the following content

package in.bushansirgur.springbootjunit.controller;


import static org.hamcrest.CoreMatchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
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.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import com.fasterxml.jackson.databind.ObjectMapper;

import in.bushansirgur.springbootjunit.model.Movie;
import in.bushansirgur.springbootjunit.service.MovieService;

@WebMvcTest
public class MovieControllerTest {
	
	@Autowired
	private MockMvc mockMvc;

	@MockBean
	private MovieService movieService;
	
	@Autowired
	private ObjectMapper objectMapper;
	
	private Movie avatarMovie;
	private Movie titanicMovie;
	
	@BeforeEach
	void init() {
		avatarMovie = new Movie();
		avatarMovie.setId(1L);
		avatarMovie.setName("Avatar");
		avatarMovie.setGenera("Action");
		avatarMovie.setReleaseDate(LocalDate.of(1999, Month.APRIL, 22));
		
		titanicMovie = new Movie();
		avatarMovie.setId(2L);
		titanicMovie.setName("Titanic");
		titanicMovie.setGenera("Romance");
		titanicMovie.setReleaseDate(LocalDate.of(2004, Month.JANUARY, 10));
	}
	
	@Test
	void shouldCreateNewMovie() throws Exception {
		
		when(movieService.save(any(Movie.class))).thenReturn(avatarMovie);
		
		this.mockMvc.perform(post("/movies")
				.contentType(MediaType.APPLICATION_JSON)
				.content(objectMapper.writeValueAsString(avatarMovie)))
		.andExpect(status().isCreated())
		.andExpect(jsonPath("$.name", is(avatarMovie.getName())))
		.andExpect(jsonPath("$.genera", is(avatarMovie.getGenera())))
		.andExpect(jsonPath("$.releaseDate", is(avatarMovie.getReleaseDate().toString())));
			
	}
	
	@Test
	void shouldFetchAllMovies() throws Exception {
		
		List<Movie> list = new ArrayList<>();
		list.add(avatarMovie);
		list.add(titanicMovie);
		
		when(movieService.getAllMovies()).thenReturn(list);
		
		this.mockMvc.perform(get("/movies"))
			.andExpect(status().isOk())
			.andExpect(jsonPath("$.size()", is(list.size())));
	}
	
	@Test
	void shouldFetchOneMovieById() throws Exception {
		
		when(movieService.getMovieById(anyLong())).thenReturn(avatarMovie);
		
		this.mockMvc.perform(get("/movies/{id}", 1L))
			.andExpect(status().isOk())
			.andExpect(jsonPath("$.name", is(avatarMovie.getName())))
			.andExpect(jsonPath("$.genera", is(avatarMovie.getGenera())));
	}
	
	@Test
	void shouldDeleteMovie() throws Exception {
		
		doNothing().when(movieService).deleteMovie(anyLong());
		
		this.mockMvc.perform(delete("/movies/{id}", 1L))
			.andExpect(status().isNoContent());
			
	}
	
	@Test
	void shouldUpdateMovie() throws Exception {
		
		when(movieService.updateMovie(any(Movie.class), anyLong())).thenReturn(avatarMovie);		
		this.mockMvc.perform(put("/movies/{id}", 1L)
				.contentType(MediaType.APPLICATION_JSON)
				.content(objectMapper.writeValueAsString(avatarMovie)))
		.andExpect(status().isOk())
		.andExpect(jsonPath("$.name", is(avatarMovie.getName())))
		.andExpect(jsonPath("$.genera", is(avatarMovie.getGenera())));
	}
}
  • MockMvc: It is the main entry point for server-side Spring MVC test support. We will use this to make HTTP requests.
  • ObjectMapper: We will use this to serialize any Java Object into JSON and vice versa.
  • @MockBean: It is used to add the mock objects into the spring application context. This mock will replace the existing bean of the same type in the application context. If no bean is available, then a new bean will be added. This is useful in integration test cases.

Now when we run the test class, we should see the green bar
Spring boot unit test for controller layer

Now, let’s look at the syntax for stub a method using Mockito:

when(mock.method()).thenReturn(someValue)

Here is the order that this code will execute in:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

When the when method is invoked after the invocation of method(), it delegates to MockitoCore.when, which calls the stub() method of the same class. This method unpacks the ongoing stubbing from the shared MockingProgress instance that the mocked method() invocation wrote into, and returns it. Then thenReturn method is then called on the OngoingStubbing instance.

That’s it for this article, in the next article we will continue with the same application where we will write the JUnit test cases for other layers as well. Thank you so much for reading this post, if you feel this post helped you then consider sharing this with your friends, colleagues, and social media.

Keywords:

spring boot test cases example, spring boot test cases for controller, spring boot test cases using mockito, spring boot test cases with h2 database, spring boot test cases for rest api, spring boot test cases junit example, spring boot test cases best practices, spring boot test junit 5 example, spring boot test junit 5 mockito example, spring boot test junit 5 controller.



Bushan Sirgur

Hey guys, I am Bushan Sirgur from Banglore, India. Currently, I am working as an Associate project in an IT company.

Leave a Reply