본문 바로가기
Develop/Java

[Mockito] Mockito란?

by 연로그 2021. 12. 2.
반응형


Mockito란?

 

  • Java에서의 단위 테스트를 지원하기 위한 Mocking Framework
  • Mock(가짜) 객체를 통해 테스트 코드 작성을 더 원활하게 해줌

 


설치 방법

 

Maven

jar파일 다운로드 혹은 직접 설치

 

Gradle

repositories { mavenCentral() }
dependencies { testImplementation "org.mockito:mockito-core:3.+" }

 


Mock 사용 예제

 

호출 되었는지 확인하기

import static org.mockito.Mockito.*;

class TempTest {
    @Test
    void verify1() {
        List mockedList = mock(List.class);
        mockedList.add("one");
        verify(mockedList).add("one");
    }

    @Test
    void verify2() {
        List mockedList = mock(List.class);
        mockedList.add("one");
        verify(mockedList).add("two");
    }
}

verify2()를 실패한 모습

  • mock()을 이용해 List.class 형태의 가상의 객체를 생성
  • verify()를 통해 메소드가 호출되었는지 검증

verify1()의 경우 mockedList.add("one")이 호출되었으므로 테스트가 통과했지만,

verify2()의 경우 mockedList.add("two")가 호출되지 않았으므로 테스트가 실패했다.

 

Stub 처리하기

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class MockTest {
    @Test
    void test() {
        LinkedList mockedList = mock(LinkedList.class);
        when(mockedList.get(0)).thenReturn("first");
        assertEquals("first", mockedList.get(0));
        assertNotEquals("first", mockedList.get(999));
        assertEquals(null, mockedList.get(999));
    }
}
  • mock()을 이용해 LinkedList.class 형태의 가상의 객체를 생성
    (mockStatic()을 통해 static 메소드도 처리 가능)
  • when()과 thenReturn()을 통해 특정 코드가 실행되면 원하는 결과를 받을 수 있도록 설정

위 예제의 같은 경우에는 mockedList에서 index 0번을 가져올 때 "first"라는 문자열을 받을 수 있도록 했다.

index 0 외에는 값을 설정하지 않아서 null을 가져오게 된다.

 when(mockedList.get(1)).thenThrow(new RuntimeException());
  • thenThrow()를 통해 특정 코드가 실행되면 원하는 Exception을 발생시킬 수도 있다.

 

@Test
void stub_test() {
    List mockedList = mock(List.class);
    when(mockedList.size())
            .thenReturn(1,2,3,4,5);

    assertEquals(mockedList.size(),1);
    assertEquals(mockedList.size(),2);
    assertEquals(mockedList.size(),3);
    assertEquals(mockedList.size(),4);
    assertEquals(mockedList.size(),5);
}

위와 같이 thenReturn 값을 여러개 넣을 수도 있다.

 

호출 횟수 검증하기

@Test
void verify_test() {
    LinkedList mockedList = mock(LinkedList.class);
    mockedList.add("a");
    mockedList.add("a");
    mockedList.add("a");
    mockedList.add("b");

    verify(mockedList, times(3)).add("a");
    verify(mockedList, never()).add("c");
    verify(mockedList, atMostOnce()).add("b");
    verify(mockedList, atLeastOnce()).add("a");
    verify(mockedList, atLeast(2)).add("a");
    verify(mockedList, atMost(5)).add("a");
}
  • times(n): n번 호출했음
  • never(): 한 번도 호출하지 않음
  • atMostOnce(): 최대 한번 호출
  • atLeastOnce(): 최소 한번 호출
  • atLeast(n): 최소 n번 호출
  • atMost(n): 최대 n번 호출

 

호출 순서 확인하기

@Test
void verify_test() {
    List singleMock = mock(List.class);
    singleMock.add("first");
    singleMock.add("second");

    InOrder inOrder = inOrder(singleMock);
    inOrder.verify(singleMock).add("first");
    inOrder.verify(singleMock).add("second");
}
  • inOrder(객체)를 통해 InOrder 객체 생성

 

추가적인 interaction 확인하기

@Test
void test1() {
    List mockedList = mock(List.class);
    mockedList.add("one");
    mockedList.size();
    verify(mockedList).add("one");
    verifyNoMoreInteractions(mockedList);
}

@Test
void test2() {
    List mockedList = mock(List.class);
    mockedList.add("one");
    verify(mockedList).add("one");
    verifyNoMoreInteractions(mockedList);
}

  • verifyNoMoreInteractions()
    : verify() 후에 추가적인 interaction이 발생했는지 확인
    - 다만 위 메소드는 테스트 메소드마다 사용하기는 부적절하고 과도하게 남용하면 유지 관리하기 어려운 테스트가 됨
    - never()를 이용하는게 더 명확하고 의도를 잘 전달할 수 있다.

test1()의 경우에는 add("one") 이후로 mock 객체의 size() 메소드를 이용했기 때문에 실패한다.

test2()의 경우에는 add("one") 이후로 mock 객체를 사용하지 않았기 때문에 성공한다.

 

@Mock 어노테이션 사용하기

@Mock private User user;
  • mock() 외에도 @Mock을 이용해 가상의 객체를 생성할 수 있음

 

콜백과 함께 Stub 처리하기

@Test
void test1() {
    List<Integer> list = mock(List.class);
    when(list.size()).thenAnswer(
            new Answer<Integer>() {
                private int cnt = 3;

                public Integer answer(InvocationOnMock invocation) {
                    return new Integer(++cnt);
                }
            });
    System.out.println(list.size());
}
@Test
void test2() {
    List<Integer> list = mock(List.class);
    when(list.get(1)).thenAnswer(
            new Answer<Integer>() {
                public Integer answer(InvocationOnMock invocation) {
                    Object[] arguments = invocation.getArguments();
                    return (Integer) arguments[0];
                }
            });
    System.out.println(list.get(1));
}
  • thenAnswer()와 Answer 인터페이스를 구현해 직접 동작 방식을 구현할 수 있음
  • arguments[0, 1, ... n]을 통해 입력받은 파라미터를 사용할 수 있음

위 예제의 경우 구동이 된다는 것을 보여주기 위한 코드로 적절한 테스트 코드는 아님

test1() 결과: 4, test2() 결과: 1

 

객체를 선택적으로 stub하기

@Test
void test1() {
    List list = new LinkedList();
    List spy = spy(list);

    // stub
    when(spy.size()).thenReturn(100);

    // real 메소드
    spy.add("one");
    spy.add("two");

    // stub된 메소드
    System.out.println(spy.size());

    // real 메소드
    System.out.println(spy.get(0));
    verify(spy).add("one");
}
  • spy() 또는 @Spy를 통해 스파이 객체로 생성
  • 일부 메소드만 stub하고 나머지 메소드들은 정상 동작하도록 하고 싶을 때 작성
 when(spy.get(3)).thenReturn("foo");

위와 같은 코드는 불가능하다. (get()은 real 메소드이고 spy의 3번째 값이 존재하지 않으므로)

아래와 같이 doReturn(), Answer, Throw() 등을 사용하자.

doReturn("foo").when(spy).get(0);

 

 


참고

  1. Mockito 공식사이트
  2. javadoc.io/doc/org.mockito
반응형