Spring Boot: Unit Testing Using JUnit 5 & Mockito

In this article we concern about the unit testing of Spring Boot based web application. This article covers the unit testing of controller,service and DAO layers. As you know unit testing plays crucial role in application development so we must do the unit testing of whatever we are developing to ensure that it works as expected.

Approaches to do Unit Testing in Web Application Environment

There are following layers of a Web Application for which we need to write unit test cases.  Always mock dependencies and test only the concern thing. Do behavior testing for mocked dependencies using Mockito.verify().

  1. Controllers
  2. Services
  3. DAO

 Best Practices for Unit Testing for Web Application

  • Use @WebMvcTest for testing Controller. Controller tests are intended to test the endpoints with mock request and response objects.
  • Mock you service classes dependencies using @Mock.
  • Do behavior test for your service classes using Mockito.verify().
  • Inject MockMvc using @Autowired annotation.
  • Don’t load Spring Container in service and dao tests.
  • Always do assertion to verify the actual result with expected results using Assertions
    class static method.

Creating Spring Application

You can create your Spring Boot Application using Spring Initializr website. You can easily specify the dependencies and it will wrap all the dependencies for you. https://start.spring.io/

The only dependency you need to specify is of web starter module. It will itself add the spring-boot-starter-test dependency too. You can choose lombok dependency too for generating getter,setter etc.

You can also include dependencies for Junit 5 in build.gradle or pom.xml.

testCompile('org.junit.jupiter:junit-jupiter-api:5.3.1')
testCompile('org.junit.jupiter:junit-jupiter-params:5.3.1')
testRuntime('org.junit.jupiter:junit-jupiter-engine:5.3.1')

Defining Controller

Here we have UserController in our application for which we need to write unit test cases. There are following endpoints in UserController.

  1. findAllUsers()
  2. create()
  3. findById()

I have defined a generic translator using ModelMapper for translating request and dto objects.

package com.startwithjava.controller;

import java.util.List;

import com.startwithjava.controller.request.CreateUserRequest;
import com.startwithjava.controller.response.UserResponse;
import com.startwithjava.dto.User;
import com.startwithjava.service.dto.UserDto;
import com.startwithjava.translator.BaseTranslator;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.startwithjava.exception.UserNotFoundException;
import com.startwithjava.service.UserService;

import lombok.AllArgsConstructor;

@AllArgsConstructor
@RestController
@RequestMapping("/users/")
public class UserController {
    private UserService userService;
    private BaseTranslator<CreateUserRequest, UserDto> createUserRequestToUserDtoTranslator;
    private BaseTranslator<UserDto, UserResponse> userDtoToUserResponseTranslator;
    @GetMapping
    public ResponseEntity<List<UserResponse>> findAllUsers(){
        return new ResponseEntity(userDtoToUserResponseTranslator.translate(userService.findAll(),UserResponse.class), HttpStatus.OK);
    }
    @PostMapping
    public ResponseEntity<Object> create(@RequestBody CreateUserRequest user){
        return new ResponseEntity(userService.create(createUserRequestToUserDtoTranslator.translate(user,UserDto.class)), HttpStatus.OK);
    }
    @GetMapping("{id}")
    public ResponseEntity<Object> findById(@PathVariable("id") long userId){
       return userService.findById(userId)
               .map(user->new ResponseEntity(user,HttpStatus.OK))
               .orElseThrow(()->new UserNotFoundException("User not found"));
    }
}

Unit Testing for UserController

As we are using Junit 5, so we have use @ExtendWith(SpringExtension.class) for it.  We have mocked the beans for UserService and translators. We have followed the given/when/then structure. In given part we are specify the mocking using Mockito.when() method. In then part, we are using Mockito.verify() to verify the interaction of mocked objects. We have covered both the positive and negative scenarios for writing unit test cases.

package com.startwithjava.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.startwithjava.controller.request.CreateUserRequest;
import com.startwithjava.controller.response.UserResponse;
import com.startwithjava.service.UserServiceImpl;
import com.startwithjava.service.dto.UserDto;
import com.startwithjava.translator.BaseTranslator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
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.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Arrays;
import java.util.Optional;

import static org.mockito.Mockito.*;
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.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(SpringExtension.class)
@WebMvcTest(value = UserController.class, secure = false)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;

@MockBean
private UserServiceImpl userService;

private ObjectMapper objectMapper= new ObjectMapper();
   @MockBean
   private BaseTranslator<CreateUserRequest, UserDto> createUserRequestToUserDtoTranslator;
   @MockBean
   private BaseTranslator<UserDto, UserResponse> userDtoToUserResponseTranslator;


   @Test
   @DisplayName("Test findAll()")
   public void findAllUsers_InputsAreValid_ReturnUserList() throws Exception {
      when(userService.findAll()).thenReturn(Arrays.asList(new UserDto()));
      mockMvc.perform(get("/users/")
              .accept(MediaType.APPLICATION_JSON))
              .andExpect(status().isOk());
      
      verify(userService,times(1)).findAll();
   }
   @Test
   @DisplayName("Test findById() with invalid userId")
   public void findUserById_WhenIdIsNotPresent_ReturnUserAsResponse() throws Exception {
      when(userService.findById(Mockito.anyLong())).thenReturn(Optional.empty());
      mockMvc.perform(get("/users/{id}",1L)
              .accept(MediaType.APPLICATION_JSON))
              .andExpect(status().isNotFound());
      verify(userService,times(1)).findById(Mockito.anyLong());
   }

   @Test
   @DisplayName("Test findById() with invalid userId")
   public void findUserById_WhenIdIsInValid_ReturnUserAsResponse() throws Exception {
      when(userService.findById(Mockito.anyLong())).thenReturn(Optional.empty());
      mockMvc.perform(get("/users/{id}","aa")
              .accept(MediaType.APPLICATION_JSON))
              .andExpect(status().isInternalServerError());
   }

   @Test
   @DisplayName("Test createUser with valid request")
   public void createUser_WhenCreateUserRequestIsIsValid_ReturnUserAsResponse() throws Exception {
      //Given
      CreateUserRequest createUserRequest = new CreateUserRequest();
      createUserRequest.setEmail("abc@mail.com");
      createUserRequest.setName("Gaurav");

      UserDto userDto = new UserDto();
      userDto.setEmail("abc@mail.com");
      userDto.setName("Gaurav");


      when(userService.create(Mockito.any(UserDto.class))).thenReturn(1l);
      when(createUserRequestToUserDtoTranslator.translate(Mockito.any(CreateUserRequest.class),eq(UserDto.class))).thenReturn(userDto);
      //When
      when(userService.findById(Mockito.anyLong())).thenReturn(Optional.empty());
      mockMvc.perform(post("/users/")
              .contentType(MediaType.APPLICATION_JSON)
              .content(objectMapper.writeValueAsString(createUserRequest))
              .accept(MediaType.APPLICATION_JSON))
              .andDo(print())
              .andExpect(status().isOk());

      //Then
      verify(userService,times(1)).create(Mockito.any(UserDto.class));
      verify(createUserRequestToUserDtoTranslator,times(1)).translate(Mockito.any(CreateUserRequest.class),eq(UserDto.class));
   }

   @Test
   @DisplayName("Test createUser with Invalid request")
   public void createUser_WhenCreateUserRequestIsInValid_ReturnUserAsResponse() throws Exception {
      //Given
      CreateUserRequest createUserRequest = new CreateUserRequest();

      UserDto userDto = new UserDto();
      userDto.setEmail("abc@mail.com");
      userDto.setName("Gaurav");


      when(userService.create(Mockito.any(UserDto.class))).thenReturn(1l);
      when(createUserRequestToUserDtoTranslator.translate(Mockito.any(CreateUserRequest.class),eq(UserDto.class))).thenReturn(userDto);
      //When
      when(userService.findById(Mockito.anyLong())).thenReturn(Optional.empty());
      mockMvc.perform(post("/users/")
              .contentType(MediaType.APPLICATION_JSON)
              .content(objectMapper.writeValueAsString(createUserRequest))
              .accept(MediaType.APPLICATION_JSON))
              .andDo(print())
               .andExpect(status().isBadRequest());
   }

}

You can find the complete project on Github.

Leave a Reply