Best Practices for Unit Testing

Unit Testing is the most important phase of Software Development in which programmer ensures their code work as expected by writing test cases. For unit testing, Junit & Mockito are being used. There are following best practices for doing unit testing.

  1. Test only the public methods.
  2. Never mock private,protected and static methods.
  3. Never place logic inside test cases.
  4. Only one assertion per test case.
  5. Identifies the dependencies for testing the public method and mock all dependencies and do behavior test for all mocked dependencies using Mockito.verify() confirm the interaction.
  6. Never load Spring Container(ApplicationContext) in case of Service or DAO unit test cases only in case of Spring Controller test cases we should load Spring Container.
  7. Test Cases names should be clear and tells its intention. It should be descriptive.
  8. Test Cases are not only for coverage purpose, it should test the behavior of the application for both the negative and positive scenarios.
  9. Both negative and positive scenarios covered in independent test cases.
  10. Never update or remove the test cases until there’s a change in design.

Never made database call while unit testing

Always mock database call while writing unit test cases or you can use inMemory database(H2 DB) for it. Best way is to mock the repository part.

Always do Behavior Testing for Dependencies

It is necessary so that you can be assured that there is an interaction between the mock dependency. The mocked dependency method never invoked but we should verify the interaction using Mockito.verify().

Here we wrote test case for addUser(), there is one dependency to invoke this method is userRepository. We mocked it and did behavior testing.


class UserDao{

@Autowired

private UserRepository userRepository;

public Long addUser(User user){

userRepository.save(user).getId();

}

import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class UserDao {
    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserDao userDao;

    @BeforeAll
    public void setUp(){
        MockitoAnnotations.initMocks(this);
    }
  @Test  
  public void addUser_WithValidUserDetails_ShouldReturnUserId(){
    //Given
    User user = new User(1l,"Gaurav","Delhi");
    //When 
    Long userId = userDao.addUser(user);
   
    //Then
    assertNotNull(userId);
    Mockito.verify(userRepository,Mockito.times(1)).save(user);
  }
}

Never Place Logic Inside Test Case

I have seen several developers placing logic inside test case which is completely wrong. You should never place logic inside test case because it won’t assure the correctness of method which we are testing.

Never use @Autowired for Unit Testing

I have seen several times, programmers inject the dependencies(like DAO or Services) using @Autowired for writing unit cases for Spring based application. As you know it will inject the actual dependency and made actual call to that dependency object which is not required in unit testing.

Never Handle Exception using try/catch

We should never place any exception handling code while doing unit testing rather we should use Assertions.assertThrows()  to check exceptional condition.

assertThrows(BadInputException.class, () -> userService.create(user));

As you know Spring provided @ControllerAdvice to handle application level exceptions at one place. So whenever you are writing test cases for exceptional condition where some exception being thrown from service or some input validation using JSR 303 annotations (@NotBlank).  In that case we should never catch them within the test case rather let the flow to reach to global exception handler to handle that exception. You can easily use @Autowired MockMvc for this.

Lets take an scenario where we have requested a user for particular id and somehow it is found present. We have thrown an custom exception called UserNotFoundExcetption which handled inside global exception handler and return the 404 as response status.

@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());
}

Use Map<String,Object> with ObjectMapper for Bad Input request

Sometimes we have to write some API where json request body used. In that case one can pass wrong format value which will not parse by Jackson. The required format could be Date,Long,Double. So in that case we cannot use defined request class for creating bad request json. We can use Map with ObjectMapper to pass the bad input request object to the particular endpoint and see the behavior of the application for it.

Here in below example we are passing bad input for Date object and it will throw an exception which in turn send bad request from global exception handler.

@Getter
@Setter
public class CreateEmployeeRequest {
    @NotNull
    private String name;
    @NotNull
    private String email;
    
    @NotNull
    private Date joiningDate;
}
@Test
@DisplayName("Test createUser with Invalid request")
public void createUser_WhenCreateUserRequestIsInValid_ReturnUserAsResponse() throws Exception {
   //Given
   Map<String,Object> createEmployeeRequest = new HashMap<>();
   createEmployeeRequest.put("name","Suman");
   createEmployeeRequest.put("email","suman@mail.com");
   createEmployeeRequest.put("joiningDate","bad-date-str");

   mockMvc.perform(post("/employee/")
           .contentType(MediaType.APPLICATION_JSON)
           .content(objectMapper.writeValueAsString(createEmployeeRequest ))
           .accept(MediaType.APPLICATION_JSON))
           .andDo(print())
            .andExpect(status().isBadRequest());
}

 

 

Leave a Reply