Getting Started with Mockito @Mock, @Spy, @Captor and @InjectMocks

Dev.Hammy·2024년 3월 31일

Mockito_Tutorial

목록 보기
1/21

1. Overview

이 튜토리얼에서는 Mockito 라이브러리의 어노테이션인 @Mock, @Spy, @Captor@InjectMocks를 다룹니다.

2. Enable Mockito Annotations

Mockito 테스트를 사용할 수 있는 두가지 어노테이션

2.1 MockitoJUnitRunner

JUnit 테스트에 MockitoJUnitRunner로 어노테이션 달기

@ExtendWith(MockitoExtension.class)
public class MockitoAnnotationUnitTest {
    ...
}

2.2 MockitoAnnotations.OpenMocks()

MockitoAnnotations.openMocks()를 호출하여 프로그래밍 방식으로 Mockito 어노테이션 활성화 하기

@BeforeEach
public void init() {
    MockitoAnnotations.openMocks(this);
}

2,3

MockitoJUnit.rule() 사용하기

public class MockitoAnnotationsInitWithMockitoJUnitRuleUnitTest {

    @Rule
    public MockitoRule initRule = MockitoJUnit.rule();

    ...
}

이 경우 rule을 public으로 생성하는 것을 유념해둡시다.

3. @Mock 어노테이션

Mockito에서 가장 널리 사용되는 어노테이션은 @Mock입니다. Mockito.mock을 수동으로 호출할 필요 없이 @Mock을 사용하여 모의 인스턴스를 생성하고 주입할 수 있습니다.

다음 예에서는 @Mock 어노테이션을 사용하지 않고 수동으로 모의 ArrayList를 생성합니다.

@Test
public void whenNotUseMockAnnotation_thenCorrect() {
    List mockList = Mockito.mock(ArrayList.class);
    
    mockList.add("one");
    Mockito.verify(mockList).add("one");
    assertEquals(0, mockList.size());

    Mockito.when(mockList.size()).thenReturn(100);
    assertEquals(100, mockList.size());
}

이제 동일한 작업을 수행하지만 @Mock 어노테이션을 사용하여 모의 객체를 주입하겠습니다.

@Mock
List<String> mockedList;

@Test
public void whenUseMockAnnotation_thenMockIsInjected() {
    mockedList.add("one");
    Mockito.verify(mockedList).add("one");
    assertEquals(0, mockedList.size());

    Mockito.when(mockedList.size()).thenReturn(100);
    assertEquals(100, mockedList.size());
}

두 예 모두에서 모의 객체가 올바르게 작동하는지 확인하기 위해 모의 개체와 상호 작용하고 이러한 상호 작용 중 일부를 확인하는 방법에 유의하세요.

4. @DoNotMock 어노테이션

@DoNotMock 어노테이션은 테스트 중에 특정 유형을 모의해서는 안됨을 나타내는 데 사용됩니다. 일반적으로 테스트 프레임워크와 개발자를 실제 인스턴스나 표준 가짜 사용과 같은 대체 테스트 솔루션으로 안내하기 위해 클래스 또는 인터페이스 수준에서 적용됩니다.

@DoNotMock 어노테이션을 사용하는 방법의 예는 다음과 같습니다.

import org.mockito.exceptions.misusing.DoNotMock;

@DoNotMock(reason = "Use a real instance instead")
public abstract class NotToMock {
    // Class implementation
}

예를 들어, NotToMock 클래스의 컨텍스트에서 @DoNotMock 어노테이션은 "대신 실제 인스턴스 사용"과 같은 지정된 이유 속성과 함께 적용됩니다. 이 어노테이션은 테스트 중에 NotToMock 클래스를 모의해서는 안 된다는 의도를 명시적으로 전달하고 대신 실제 인스턴스의 사용을 권장합니다.

이유 속성이 있는 @DoNotMock 어노테이션은 테스트 프레임워크와 개발자가 모의에 의존하기보다는 대체 테스트 접근 방식을 고려하도록 명확하게 지시합니다.

5. @Spy 어노테이션

이제 @Spy 어노테이션을 사용하여 기존 인스턴스를 감시하는 방법을 살펴보겠습니다. 다음 예에서는 @Spy 어노테이션을 사용하지 않고 List의 스파이를 만듭니다.

@Test
public void whenNotUseSpyAnnotation_thenCorrect() {
    List<String> spyList = Mockito.spy(new ArrayList<String>());
    
    spyList.add("one");
    spyList.add("two");

    Mockito.verify(spyList).add("one");
    Mockito.verify(spyList).add("two");

    assertEquals(2, spyList.size());

    Mockito.doReturn(100).when(spyList).size();
    assertEquals(100, spyList.size());
}

이제 동일한 작업을 수행하여 리스트를 감시하지만 @Spy 어노테이션을 사용합니다.

@Spy
List<String> spiedList = new ArrayList<String>();

@Test
public void whenUseSpyAnnotation_thenSpyIsInjectedCorrectly() {
    spiedList.add("one");
    spiedList.add("two");

    Mockito.verify(spiedList).add("one");
    Mockito.verify(spiedList).add("two");

    assertEquals(2, spiedList.size());

    Mockito.doReturn(100).when(spiedList).size();
    assertEquals(100, spiedList.size());
}

이전과 마찬가지로 여기에서 스파이가 올바르게 작동하는지 확인하기 위해 스파이와 어떻게 상호 작용하는지 확인하세요. 이 예에서는 다음을 수행합니다.

  • spiedList에 요소를 추가하기 위해 실제 메소드 spiedList.add()를 사용했습니다.
  • Mockito.doReturn()을 사용하여 2 대신 100을 반환하도록 spiedList.size() 메서드를 스텁했습니다.

6. @Captor 어노테이션

다음으로 @Captor 어노테이션을 사용하여 ArgumentCaptor 인스턴스를 생성하는 방법을 살펴보겠습니다. 다음 예에서는 @Captor 어노테이션을 사용하지 않고 ArgumentCaptor를 생성합니다.

@Test
public void whenNotUseCaptorAnnotation_thenCorrect() {
    List mockList = Mockito.mock(List.class);
    ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);

    mockList.add("one");
    Mockito.verify(mockList).add(arg.capture());

    assertEquals("one", arg.getValue());
}

이제 동일한 목적으로 @Captor를 사용하여 ArgumentCaptor 인스턴스를 생성해 보겠습니다.

@Mock
List mockedList;

@Captor 
ArgumentCaptor argCaptor;

@Test
public void whenUseCaptorAnnotation_thenTheSame() {
    mockedList.add("one");
    Mockito.verify(mockedList).add(argCaptor.capture());

    assertEquals("one", argCaptor.getValue());
}

구성(configuration) 논리를 제거하면 테스트가 어떻게 더 간단해지고 읽기 쉬워지는지 확인하세요.

7. @InjectMocks 어노테이션

이제 @InjectMocks 어노테이션을 사용하여 테스트된 객체에 모의 필드를 자동으로 삽입하는 방법을 논의해 보겠습니다. 다음 예에서는 @InjectMocks를 사용하여 모의 wordMapMyDictionary dic에 삽입합니다.

@Mock
Map<String, String> wordMap;

@InjectMocks
MyDictionary dic = new MyDictionary();

@Test
public void whenUseInjectMocksAnnotation_thenCorrect() {
    Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");

    assertEquals("aMeaning", dic.getMeaning("aWord"));
}

다음은 MyDictionary 클래스입니다.

public class MyDictionary {
    Map<String, String> wordMap;

    public MyDictionary() {
        wordMap = new HashMap<String, String>();
    }
    public void add(final String word, final String meaning) {
        wordMap.put(word, meaning);
    }
    public String getMeaning(final String word) {
        return wordMap.get(word);
    }
}

8. Mock을 Spy에 주입하기

위 테스트와 유사하게 스파이에 모의 객체를 주입할 수 있습니다.

@Mock
Map<String, String> wordMap;

@Spy
MyDictionary spyDic = new MyDictionary();

그러나 Mockito는 모의 객체를 스파이에 주입하는 것을 지원하지 않으며 다음 테스트 결과는 예외입니다.

@Test 
public void whenUseInjectMocksAnnotation_thenCorrect() { 
    Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning"); 

    assertEquals("aMeaning", spyDic.getMeaning("aWord")); 
}

스파이와 함께 모의 개체를 사용하려면 생성자를 통해 모의 개체를 수동으로 주입할 수 있습니다.

MyDictionary(Map<String, String> wordMap) {
    this.wordMap = wordMap;
}

주석을 사용하는 대신 이제 수동으로 스파이를 생성할 수 있습니다.

@Mock
Map<String, String> wordMap; 

MyDictionary spyDic;

@@BeforeEach
public void init() {
    MockitoAnnotations.openMocks(this);
    spyDic = Mockito.spy(new MyDictionary(wordMap));
}

이제 테스트가 통과됩니다.

9. Running Into NPE While Using Annotation

@Mock 또는 @Spy 어노테이션이 달린 인스턴스를 실제로 사용하려고 할 때 종종 NullPointerException이 발생할 수 있습니다.

public class MockitoAnnotationsUninitializedUnitTest {

    @Mock
    List<String> mockedList;

    @Test(expected = NullPointerException.class)
    public void whenMockitoAnnotationsUninitialized_thenNPEThrown() {
        Mockito.when(mockedList.size()).thenReturn(1);
    }
}

대부분의 경우 이는 Mockito 어노테이션을 올바르게 활성화하는 것을 잊었기 때문에 발생합니다.

따라서 Mockito 어노테이션을 사용할 때마다 앞서 설명한 대로 추가 단계를 수행하고 초기화해야 한다는 점을 명심해야 합니다.

10. Notes

마지막으로 Mockito 어노테이션에 대한 몇 가지 참고 사항은 다음과 같습니다.

  • Mockito의 어노테이션은 반복적인 모의 생성 코드를 최소화합니다.
  • 테스트를 더 읽기 쉽게 만듭니다.
  • @Spy@Mock 인스턴스를 모두 주입하려면 @InjectMocks가 필요합니다.

0개의 댓글