Java

Junit과 mockito 조합으로 단위 테스트 해보기

setung 2021. 11. 21. 16:28

테스트의 종류에는 통합 테스트와 단위 테스트가 있다.

이 글에는 단위 테스트를 이야기해보겠다.

 

단위 테스트의 특징은 다른 의존하는 객체가 있다면 의존성을 끊는게 중요한데 이를 고립이라고 표현한다. 예를 들어 Service의 비지니스 로직을 테스트하는데 클라이언트나 DB의 문제로 테스트가 진행되지 않는다면 고립이 되어있지 않은 것이다. 

 

그런데 의존하는 객체를 어떻게 고립시킬 수 있을까? 바로 Mock 객체를 사용한다. Mock이란 아무 기능이 없는 가짜 객체를 의미한다. 하지만 의존하는 모든 객체에 대해 Mock을 만드는 건 쉬운 일이 아니다만 다행히 Mockito라는 Mock 객체를 쉽게 만들어주는 프레임워크가 있다.

 

자바 환경에서 junit과 mokito를 이용해서 테스트 코드를 작성한다. 간단한 예를 보겠다.

간단히 User, UserRepository, UserService를 만들어 보았다.

 

User.class

public class User {

    public Long id;
    public String name;

    public User(String name) {
        this.name = name;
    }

}

UserRepository.class

public class UserRepository {

    private HashMap<Long, User> users = new HashMap<>();
    private Long sequence = 0L;

    public User save(User user) {
        user.id = sequence;
        users.put(sequence++, user);
        return user;
    }

    public User findById(Long id) {
        return users.get(id);
    }

    public int getSize() {
        return users.size();
    }

}

UserService.class

public class UserService {

    UserRepository userRepository = new UserRepository();

    public User joinUser(User user) {
        return userRepository.save(user);
    }

    public User findUserById(Long id) {
        User user = userRepository.findById(id);

        if(user==null)
            throw new NullPointerException();

        return user;
    }

    public int count() {
        return userRepository.getSize();
    }
}

 

TestCode.class

public class TestCode {

    UserService userService = new UserService();

    @Test
    public void save() {
        User user = new User("test_user");

        User savedUser = userService.joinUser(user);
        User findUser = userService.findUserById(savedUser.id);

        Assertions.assertThat(findUser).isInstanceOf(User.class);
        Assertions.assertThat(findUser.id).isEqualTo(savedUser.id);
        Assertions.assertThat(findUser.name).isEqualTo(savedUser.name);
        Assertions.assertThat(userService.count()).isEqualTo(1);

        assertThrows(NullPointerException.class, () -> userService.findUserById(-1L));
    }
}

@Test 어노테이션을 붙여 테스트 코드임을 선언한다.

Assertions의 메서드를 사용해 의도대로 코드가 동작했는지 검증을 한다.

isInstanceOf, inEqualTo, isNull 등 다양한 메서드가 있다. 또한 assertThrows()는 예외 발생을 검증해준다.

 

테스트 성공

 

UserRepository.class 수정

import java.util.HashMap;

public class UserRepository {

    private HashMap<Long, User> users = new HashMap<>();
    private Long sequence = 0L;

    public User save(User user) {
        if (true)
            throw new RuntimeException();

        user.id = sequence;
        users.put(sequence++, user);
        return user;
    }

    public User findById(Long id) {
        if (true)
            throw new RuntimeException();
        
        return users.get(id);
    }

    public int getSize() {
        if (true)
            throw new RuntimeException();
        
        return users.size();
    }
}

이제 Repository의 모든 메서드에 예외를 던지도록 했다. 대충 커넥션 예외를 연출해봤다.

기존의 TestCode는 모두 예외가 발생한다.

 

이제 Mock을 사용해 보겠다.

 

TestCodeWithMock.class

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class TestCodeWithMock {

    @Mock
    UserRepository userRepository;

    @InjectMocks
    UserService userService;

    @Test
    public void save() {
        User user = new User("test_user");
        user.id = 1L;

        Mockito.when(userRepository.save(user)).thenReturn(user);
        Mockito.when(userRepository.findById(user.id)).thenReturn(user);
        Mockito.when(userRepository.findById(-1L)).thenReturn(null);
        Mockito.when(userRepository.getSize()).thenReturn(1);

        User savedUser = userService.joinUser(user);


        Assertions.assertThat(savedUser).isInstanceOf(User.class);
        Assertions.assertThat(user.id).isEqualTo(user.id);
        Assertions.assertThat(savedUser.name).isEqualTo(user.name);
        Assertions.assertThat(userService.count()).isEqualTo(1);

        assertThrows(NullPointerException.class, () -> userService.findUserById(-1L));

        Mockito.verify(userRepository,Mockito.times(1)).save(user);
        Mockito.verify(userRepository,Mockito.times(0)).findById(user.id);
        Mockito.verify(userRepository,Mockito.times(1)).getSize();
    }
}

 

 

클래스명 위에 @ExtendWith(MockitoExtension.class), @MockitoSettings(strictness = Strictness.LENIENT) 추가한다. 그리고 테스트 대상인 UserService는 @InjectMocks 어노테이션을 추가해주고, UserService가 의존하는 UserRepository는 @Mock 어노테이션을 추가한다.

그러면 Mock 객체인 UserRepository를 의존하는 UserService가 만들어진다.

 

Mock은 아무런 기능이 없기 때문에 

Mockito.when(userRepository.save(user)).thenReturn(user); 와 같이 UserRepository에 있는 메서드의 동작을 정할 수 있다. 

그리고  Mockito.verify() 메서드를 통해 Mock 객체가 어떤 메서드가 몇 번 호출되었는지 검증이 가능하다.

 

UserRepository가 무조건 예외를 발생시킨다 해도 Mock을 사용했기 때문에 테스트 코드는 성공하게 된다.