모키토 : 단위 테스트를 위해 모의 객체를 생성하고 관리하는데 사용되는 Java 오픈소스 프레임워크를 의미한다. 이를 통해 DB를 읽어오지 않고 단위 테스트를 진행할 수 있다.

 

목 객체

  • 실제 사용되는 객체 생성을 대체하기 위해 테스트에 사용되는 객체를 의미한다. 일반적으로 목 객체의 변수 값은 null, 0, false와 같은 타입의 기본값으로 설정되고 메소드는 void일 때는 예외 포함해서 아무것도 발생하지 않고 void가 아닐 때는 타입의 기본 값을 반환한다.
  • 따라서 유의미하게 사용하려면 반환값을 제대로 설정해줘야 한다.

장점

  • 클래스를 독립적으로 테스트할 수 있다.
  • 클래스와 의존성 간의 상호작용을 검증할 수 있다.
  • 의존성 설정을 최소화하고, 실제 데이터베이스나 API 없이 테스트할 수 있다.

 

필요성 : 목 객체와 함께 서비스를 호출하여 비즈니스 로직이 올바르게 처리되는지 확인하기 위함.

 

모키토 간단 수행 예시

  1. 목 객체 생성(set up 1) - List<String> list = Mockito.mock(List.class)
  2. 메서드 호출 예상 동작 설정(set up 2, stub) - Mockito.when(list.size()).(thenReturn(3) or thenThrow(Exception.class));
  3. 메소드 실행(execute)
  4. 메소드 호출 검증(assert)
  5. 메소드 호출 검증(verify) - Mockito.verify(list).size();

목 객체를 생성하는 어노테이션으로는 @Mock, @Spy, @InjectMock이 있다.

  • @Mock : 이 어노테이션으로 생성된 Mock 객체는 반드시 stub 설정을 해야 한다. 하지 않고 호출하면 타입의 기본값이 리턴된다.
  • @Spy : 진짜 객체처럼 사용할 수 있는 Mock 객체로 stub 설정을 하지 않으면 기존 객체의 로직을 실행한 값을 리턴한다. stub을 설정할 시 stub으로 설정한 값이 리턴된다.
  • @InjectMock : @Mock이나 @Spy로 생성된 mock객체를 자동으로 주입해주는 어노테이션이다. 같은 클래스 안에 존재해야 한다. @Spy와 마찬가지로 stub 설정하지 않으면 기존 객체의 로직을 실행한다.
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
    @InjectMocks
    UserService userService;

    @Mock
    UserRepository userRepository;
}
  • @InjectMock의 예제이다. UserService는 UserRepository를 주입받는데, 이때 @Mock으로 생성된 UserRepository가 @InjectMocks에 의해 자동으로 주입된다.

 

stub은 두 가지 종류가 있는데 OngoingStubbing과 stubber가 있다. 기본적으로 OngoingStubbing을 사용하지만, 메소드가 void일 때는 stubber를 사용하자. when 함수가 인자로 T를 받기 때문이다.

  • OngoingStubbing : Mockito.when("실행할 메소드").thenReturn("반환값)
  • stubber : Mockito.doReturn("반환값").when("변수명")."실행할 메소드"

verify 함수 안에는 변수명 뿐만 아니라 VerificationMode를 넣을 수 있다. 주의 사항으로는 stub 설정했을 때 넣어줬던 인자랑 verify할 때 메소드에 넣어주는 인자가 다르면 실패가 나온다.

times(n) n번 호출되었는지 검증
never 메소드가 한 번도 호출되지 않았는지 검증
atLeastOne 최소 한 번은 호출되었는지 검증
atLeast(n) 최소 n번은 호출되었는지 검증
atMostOnce 최대 한 번 호출 되었는지 검증
atMost(n) 최대 n번 호출되었는지 검증
calls(n) n번 호출되었는지 검증 (InOrder와 같이 사용해야 함)
only 해당 검증 메소드만 실행되었는지 검증
timeout(long mills) n ms이상 걸리면 Fail, 바로 검증 종료
after(long mills) n ms이상 걸리는지 확인, 검증 종료 X
description 실패한 경우 나올 문구

 

이때 검증은 위에서 호출한 메소드들에 대해서 검증한다.

@Test
    void testAtMost() {
        userService.getUser();
        userService.getUser();
        userService.getUser();
        // userService.getUser(); - 4번 실행하면 fail

        verify(userService, times(3)).getUser();
    }

 

 

calls(n) 설명에 있는 InOrder의 경우 검증할 때 메소드 호출 순서가 맞는지 검증해준다.

@Test
    void testInOrder() {

        /* userService.getLoginErrNum();
           userService.getUser();
           와 같이 하면 fail
        */
        userService.getUser();
        userService.getUser();
        userService.getLoginErrNum();

        InOrder inOrder = inOrder(userService);

        inOrder.verify(userService, calls(2)).getUser();
        inOrder.verify(userService).getLoginErrNum();
    }

 

Mockitto.when(repository.save(any(Class<?> class))) : any() 메소드를 사용하면 특정 객체를 안 넣어도 되고, 같은 클래스의 아무 객체를 넣기만 하면 상관없다. 

 

Mockitto.when(repository.save(nullable(Class<?> class))) : any() 메소드에서 만약 null이 들어가면 동작하지 않는다. 이 때는 nullable을 사용해야 한다.

 

Mockitto.when(repository.save(anyInt())) : 그냥 int일 경우에는 anyInt()를 사용하면 된다.

 

verify(~Service, times(~)).~(eq(~), eq(~)) : eq() 메소드를 사용하면 해당 메소드가 호출될 때 전달된 파라미터의 인자를 검증할 수 있다.

 

 

계층 별 테스트할 때, 만약 controller를 테스트하고 싶을 때 controller는 stub하지 않고 주입해주는 것 service는 stub해준다. 이때 service의 메소드가 호출되었는지 verify()를 사용하고, controller의 경우에는 원래 로직에서 정상적으로 return값이 왔는지 검증해준다. 이처럼 테스트하고자 하는 계층은 stub 설정하지 않고, 주입해줘야하는 것을 stub 설정해준다.

ServiceTest

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
    @InjectMocks
    UserService userService;

    @Mock
    UserRepository userRepository;

    @Test
    public void deleteTest(){
        //given
        Mockito.doNothing().when(userRepository).deleteByOauth2Id("kakao123");

        //when
        userService.delete("kakao123");

        //then
        Mockito.verify(userRepository, Mockito.times(1)).deleteByOauth2Id("kakao123");
    }
}

@ExtendWith(MockitoExtendsion.class) 애너테이션을 안 붙이면 Mock 객체가 생성되지 않으므로 꼭 붙여주자.

+ Recent posts