본문 바로가기
백엔드

단위 테스트를 위한 Mockito 사용법

by hseong 2023. 6. 6.

목 객체 생성하기

Mockito.mock() 메서드를 사용하면 목 객체를 생성할 수 있습니다. 목 객체는 클래스, 인터페이스, 추상 클래스 어떤 것이든 대상으로 설정이 가능합니다.

MemberRepository repositroy = Mockito.mock(MemberRepository.class);

목 객체를 좀 더 쉽게 만들려면 JUnit5 확장 기능을 사용하면 됩니다. 이를 사용하면 일일이 mock() 메서드를 사용할 필요 없이 필드에 @Mock 애노테이션만 붙이면 자동으로 목 객체를 생성해 줍니다.

@ExtendWith(MockitoExtension.class)
public class MemberServiceTest {

	@Mock
	private MemberRepository repository;
	...
}

스텁 설정

Mockito.when() 또는 BDDMockito.given() 메서드를 이용해서 메서드의 반환 값을 설정해 줄 수 있습니다.

//Mockito
when(memberRepository.findById(1L)).thenReturn(memberA);
//BDDMockito
given(memberRepository.findById(1L)).willReturn(memberA);

이제 memberRepository.findById() 메서드를 호출하면서 1L이라는 인자를 전달하면 항상 memberA라는 객체를 리턴하게 됩니다.

예외를 던지도록 설정해 줄 수도 있습니다.

//Mockito
when(memberRepository.findById(any())).thenThrow(IllegalArgumentException.class);
//BDDMockito
given(memberRepository.findById(any())).willThrow(IllegalArgumentException.class);

//리턴 타입이 void 인 경우
//Mockito
doThrow(IllegalArgumentException.class)
                .when(memberRepository).findMemberButThrowException(anyString());
//BDDMocito
willThrow(IllegalArgumentException.class)
                .given(memberRepository).findMemberButThrowException(anyString());

Argument matchers

스텁 설정을 진행할 때, 위에서 한 것과 같이 정확하게 그 값을 전달할 때 특정 값을 리턴하도록 설정을 하였습니다. 그러나 만일 인자로 전달되는 값을 실수로 수정하게 된다면 테스트에 빨간불이 들어오고 어디서 잘못되었는지 찾아야 하는 번거로움이 수반됩니다. 스텁 설정을 진행한 메서드는 어떤 인자가 전달되든 우리가 의도한 값을 반환해주기만 하면 됩니다.

ArgumentMatchers 클래스는 이러한 유연한 스터빙을 가능하게 합니다.

given(memberRepository.findById(any()).willReturn(memberA);
given(memberRepository.findById(anyLong()).willReturn(memberA);

ArgumentMatchers.any() 또는 anyLong() 메서드를 인자로 전달함으로써 memberRepository.findById() 메서드는 어떤 값이 인자로 전달되든지 항상 memberA를 리턴합니다.

단, any() 메서드를 사용할 시 주의할 점이 있다. 메서드의 인자 값으로 any(), anyLong()과 같은 argument matchers를 전달할 경우 반드시 모든 인자값은 argument matchers로 전달해야 합니다.

만일 인자 값으로 정확한 값을 전달하는 경우 eq() 메서드를 통해 전달해야 합니다.

given(memberRepository.findAll(any(), any(), "user");  //exception 발생!
given(memberRepository.findAll(any(), any(), eq("user"));  //정상 작동

호출 횟수 검증

메서드 스터빙뿐만 아니라 특정 메서드가 얼마나 호출되었는지 또한 확인할 수 있습니다.

//Mockito
verify(memberRepository).findById(any());
//BDDMockito
then(memberRepository).should().findById(any());

만일 몇 번 호출되었는지 정확한 횟수를 알고 싶다면 Mockito.only()와 같은 메서드를 인자로 전달하여 호출 횟수의 검증 또한 가능합니다.

//Mockito
verify(memberRepository, only()).findById(any());  //verify()의 두 번째 인자로 전달
//BDDMockito
then(memberRepository).should(only()).findById(any());  //should()의 인자로 전달

이 외에도

  • times(int): 지정한 횟수만큼 호출
  • never(): 호출하지 않음
  • atLeast(int): 최소 지정한 횟수만큼은 호출
  • atLeastOnce(): 최소 한 번의 호출
  • atMost(int): 최대 지정한 횟수만큼 호출

과 같은 메서드들을 제공한다.

@InjectMocks

해당 애노테이션을 사용하면 생성한 목 객체들을 자동으로 @InjectMocks 객체에 주입하게 됩니다. 내부적인 규칙에 따라 주입이 이루어지게 되며, 주의할 점은 오직 @Mock, @Spy 애노테이션을 사용하여 생성된 목 또는 스파이 객체만을 주입한다는 점입니다.

@ExtendWith(MockitoExtension.class)
public class MockitoTest {

    @Mock
    private MemberRepository memberRepository;

    @InjectMocks
    private MemberService memberService;
		...
}

'백엔드' 카테고리의 다른 글

Parameter와 Argument  (0) 2023.06.20
[디자인 패턴] 옵저버 패턴  (0) 2023.06.19
[디자인 패턴] 싱글턴 패턴  (0) 2023.06.11
Gradle에 대한 짧은 정리  (1) 2023.06.04
[nGrinder] 부하 테스트 해보기  (0) 2023.05.22