본문 바로가기
개발 관련/spring

Mockito 정리 (1)

by lazysnack 2022. 7. 14.

Mockito 란?

  • 자바에서 단위 테스트를 위한 Mocking framework
  • tastes really good(?!)
  • (개인적으론) 간편하게 사용할 수 있어서 테스트코드 작성할 때 즐겨 사용하는 편입니다.

1. 의존성 추가

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.0.0</version>
    <scope>test</scope>
</dependency>
  • 버전에 대해
    • mockito 의 버전은 1.x, 2.x, 3.x 로 총 3개가 있습니다. 공식 wiki 에 따르면 1과 2는 맞지 않는(Incompatible) 것도 있다고 합니다. 2와 3은 API 로는 변경된 것이 없으나, Java8 에 맞춰졌다고 합니다.

2. 사용 방법

2-1. verify

// mock 등의 코드를 깔끔하게 하기 위해 static import
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

public class MockitoTest {
    @Test
    public void verify_테스트() {
        //mock 객체 생성
        List mockedList = mock(List.class);

        mockedList.add("one");
        mockedList.clear();

        // 검증
        verify(mockedList).clear();
        verify(mockedList).add("one");
        verify(mockedList).add("two"); // 검증 실패 (호출된 적이 없기 때문에)
    }
}
  • 생성되면 mock 은 모든 상호작용을 기억합니다. 어떤 메소드가 실행되었는지 verify 로 검증할 수 있으며, 검증에는 순서를 따지지 않는 것을 확인할 수 있습니다.

2-2. stubbing

public void stubbing_테스트() {

    LinkedList mockedList = mock(LinkedList.class);

    // stubbing
    when(mockedList.get(0)).thenReturn("first");
    when(mockedList.get(1)).thenThrow(new RuntimeException("not bound index"));

    // 첫번째 first 호출
    System.out.println(mockedList.get(0)); // first

    // stub 해주지 않았으므로 null 호출
    System.out.println(mockedList.get(999)); // null

    // runtimeException 발생
    System.out.println(mockedList.get(1)); // java.lang.RuntimeException: not bound index
}
  • stub 라는 단어는 한글로 해석하면 그루터기 라는 뜻인데, 모르겠싶어 관련내용을 wiki 에서 찾아보면 Test Stub 이 있는데요. 내용을 보면

    • Test stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.

    여기서 나오는 canned answer 이라는 건 정해진 답 으로 stubbing 이란 정해진 응답을 주도록 하는 것이라 볼 수 있겠습니다. (자꼬치기?)

2-3. Argument matchers

@Test
public void argument_matchers_테스트() {

    LinkedList mockedList = mock(LinkedList.class);

    when(mockedList.get(anyInt())).thenReturn("element");

    System.out.println(mockedList.get(999));
}
  • stubbing 을 할 때 입력 파라미터 인자를 유연하게 하고 싶을 때 사용합니다.
  • argument matchers 의 anyX() 메소드들은 테스트를 편하게 도와주긴 하지만, 경함상 Int 파라미터가 필요한 자리에 any() 를 쓰면 에러가 났던 걸로 기억합니다. (제네릭인줄 알았는데 말이죠.)
  • arugment matcher를 사용하려면 모든 파라미터는 matcher 로 전달되어야 합니다. 즉, String 문자열을 넣을 땐 eq() 로 감싸주어야 합니다.
when(someMethod.call(anyInt(), "url")).thenReturn("OK"); // error
when(someMethod.call(anyInt(), eq("url"))).thenReturn("OK"); // 정상 동작

2-4. 호출 여부에 대한 검증

@Test
public void times_검증테스트() {
    LinkedList mockedList = mock(LinkedList.class);

    mockedList.add("once");

    mockedList.add("twice");
    mockedList.add("twice");

    mockedList.add("three times");
    mockedList.add("three times");
    mockedList.add("three times");

    // 지정한 횟수만큼 호출되었는지 검사
    verify(mockedList, times(1)).add("once");
    verify(mockedList, times(2)).add("twice");
    verify(mockedList, times(3)).add("three times");

    // never() == times(0)
    verify(mockedList, never()).add("never");

    verify(mockedList, atLeastOnce()).add("three times");
    verify(mockedList, atLeast(2)).add("six times"); // times(0) 검증 실패
    verify(mockedList, atMost(5)).add("three times");
}
  • 메소드 호출 횟수와 호출 여부를 확인할 수 있습니다. times(1) 의 경우는 default 이므로 생략 가능합니다.

2-5. void method with exception

@Test
public void void_exception_테스트() {
    LinkedList mockedList = mock(LinkedList.class);

    doThrow(new RuntimeException()).when(mockedList).clear();

    mockedList.clear(); // runtimeException
}
  • void 메소드에서 exception 을 던질 때 사용합니다.

2-6. 순서 검증하기

@Test
public void 순서검증하기_테스트() {
    List firstMock = mock(List.class);
    List secondMock = mock(List.class);

    firstMock.add("first call");
    secondMock.add("second call");

    InOrder inOrder = inOrder(firstMock, secondMock);

    inOrder.verify(firstMock).add("first call");
    inOrder.verify(secondMock).add("second call");
}
  • mock 들이 순서대로 실행되는지 검증하는 방식입니다.
  • inOrder 에 들어가는 순서는 상관없네요. verify 를 통해 순서대로 실행되는지 확인할 수 있습니다.

2-7. 연속적인 콜 stubbing

@Test
public void 연속적인_콜_stubbing() {
    MockingExample mockingExample = mock(MockingExample.class);
    when(mockingExample.someMethod(eq("some arg"))).thenThrow(new RuntimeException()).thenReturn("foo");

    // 첫번째 호출 runtime Exception
    mockingExample.someMethod("some arg");
    // 'foo' 호출
    System.out.println(mockingExample.someMethod("some arg"));
    // 이후 모든 호출은 마지막 stubbing 인 'foo' 호출
    System.out.println(mockingExample.someMethod("some arg"));
    // 다음과 같이 간략화 가능
    when(mockingExample.someMethod(eq("some arg"))).thenReturn("one", "two", "three");

}
  • 해당 메소드를 호출할 때마다 다른 return 값을 보여주기 위해 사용합니다.
  • 이런 경우는 거의 없다고 하지만, 알아두는 것도 나쁘지 않을 것 같네요.

2-8. 실제 객체 감시하기

@Test
public void spy_테스트() {
    List list = new LinkedList();
    List spy = spy(list);
    // 특정 메소드 stub 
    when(spy.size()).thenReturn(100);
    // real method 호출
    spy.add("one");
    spy.add("two");

    System.out.println(spy.get(0));
    // stub 된 size() 가 호출
    System.out.println(spy.size());

    verify(spy).add("one");
    verify(spy).add("two");
}
  • real 객체에 대해 감시할 수 있다고 합니다. method 가 stub 되지 않았을 경우 실제 method 가 호출이 됩니다. 위의 경우에서는 size() 에 대해서만 stub 을 했습니다. 자주 사용하지 않아서 그런지 알쏭달쏭 합니다.
  • "partial mocking" 이라는 개념과 연관되어 있다고 합니다.
List list = new LinkedList();
List spy = spy(list);

// spy.get(0)은 list가 아직 비어있으므로 IndexOutOfBoundsException
when(spy.get(0)).thenReturn("foo");

// doReturn을 이용해 stubbing
doReturn("foo").when(spy).get(0);
  • 부분만 mocking 한다는 건 이런 문제점이 발생할 수 있기에 잘 써야 할 것 같네요.

2-9. stubbing 되지 않은 메소드에 기본 리턴값 설정

@Test
public void stubbing_기본값_설정_테스트() {
    MockingExample mock = mock(MockingExample.class, Mockito.RETURNS_SMART_NULLS);
    System.out.println(mock.someMethod("test")); // ""
}
  • stub 되지 않은 method 를 호출할 때 리턴되는 값을 정해줄 수 있습니다.
  • 좋은 테스트를 위해 꼭 필요한 기능은 아니지만, 레거시 시스템에는 유용하게 사용될 수 있다고 합니다..만, 어디에 사용할지 딱 하고 느낌이 오진 않네요.

2-10. Aliases for Behavior Driven Development

public void shouldBuyBread() throws Exception {
    //given
    given(seller.askForBread()).willReturn(new Bread());

    //when
    Goods goods = shop.buyBread();

    //then
    then(goods).should().containBread();
}
  • BDD 스타일의 테스트 작성 방법은 테스트 method 에 기본으로 //given, //when, //then 이라고 주석을 달아주는 것이라 합니다. (저도 모르게 BDD 방식을 사용해 테스트 코드를 작성하고 있었네요.)
  • BDDMockito.given(Object) 를 제공해 BDD 스타일의 테스트를 만들 수 있다고 합니다.

Reference

'개발 관련 > spring' 카테고리의 다른 글

HandlerMethodArgumentResolver 사용  (0) 2022.07.14
JPA 어노테이션  (0) 2022.07.14
JPA 락에 대해  (0) 2022.07.14
SpringBoot Actuator  (0) 2022.07.14
WireMock 사용  (0) 2022.07.14