Mockito – Using Spies

Dev.Hammy·2024년 4월 1일

Mockito_Tutorial

목록 보기
14/21

1. Overview

이 튜토리얼에서는 Mockito에서 스파이를 최대한 활용하는 방법을 설명합니다.

@Spy 어노테이션과 스파이를 스텁하는 방법에 대해 이야기하겠습니다. 마지막으로 MockSpy의 차이점에 대해 살펴보겠습니다.

물론 더 많은 Mockito 장점을 보려면 여기에서 시리즈를 살펴보세요.

2. Simple Spy Example

스파이를 사용하는 방법에 대한 간단한 예부터 시작해 보겠습니다. 간단히 말해서 API는 실제 객체를 감시하는 Mockito.spy()입니다.

이를 통해 모의 객체에서와 마찬가지로 모든 상호 작용을 추적하면서 객체의 모든 일반 메서드를 호출할 수 있습니다. 이제 기존 ArrayList 개체를 감시하는 간단한 예를 살펴보겠습니다.

@Test
void givenUsingSpyMethod_whenSpyingOnList_thenCorrect() {
    List<String> list = new ArrayList<String>();
    List<String> spyList = spy(list);

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

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

    assertThat(spyList).hasSize(2);
}

실제 메소드 add()가 실제로 호출되는 방식과 spyList의 크기가 2가 되는 방식을 확인하세요.

3. The @Spy Annotation

다음으로 @Spy 어노테이션을 사용하는 방법을 살펴보겠습니다. spy() 대신 @Spy 어노테이션을 사용할 수 있습니다.

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

@Test
void givenUsingSpyAnnotation_whenSpyingOnList_thenCorrect() {
    spyList.add("one");
    spyList.add("two");

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

    assertThat(aSpyList).hasSize(2);
}

Mockito 주석(예: @Spy, @Mock, … )을 활성화하려면 모의 객체를 초기화하고 엄격한 스텁을 처리하는 @ExtendWith(MockitoExtension.class)를 사용해야 합니다.

4. Stubbing a Spy

이제 Spy를 스텁하는 방법을 살펴보겠습니다. 모의 객체에 사용하는 것과 동일한 구문을 사용하여 메서드의 동작을 configure/oveeride할 수 있습니다. 여기서는 doReturn()을 사용하여 size() 메서드를 재정의합니다.

@Test
void givenASpy_whenStubbingTheBehaviour_thenCorrect() {
    List<String> list = new ArrayList<String>();
    List<String> spyList = spy(list);

    assertEquals(0, spyList.size());

    doReturn(100).when(spyList).size();
    assertThat(spyList).hasSize(100);
}

5. Mock vs Spy in Mockito

Mockito에서 MockSpy의 차이점에 대해 논의해 보겠습니다. 우리는 두 개념 사이의 이론적 차이점을 조사하지 않고 Mockito 자체 내에서 어떻게 다른지 살펴보겠습니다.

Mockito가 모의 객체를 생성할 때 실제 인스턴스가 아닌 타입의 Class에서 생성합니다. 모의 객체는 단순히 클래스의 bare-bones 셸 인스턴스를 생성하며 클래스와의 상호 작용을 추적하기 위해 완전히 계측(instrumented)됩니다.

반면에 스파이는 기존 인스턴스를 래핑합니다. 이는 여전히 일반 인스턴스와 동일한 방식으로 작동합니다. 유일한 차이점은 모든 상호 작용을 추적하도록 계측된다는 것입니다.

여기서는 ArrayList 클래스의 모형을 만듭니다.

@Test
void whenCreateMock_thenCreated() {
    List mockedList = mock(ArrayList.class);

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

    assertThat(mockedList).hasSize(0);
}

보시다시피, 모의 목록에 요소를 추가해도 실제로는 아무것도 추가되지 않습니다. 다른 부작용 없이 메서드를 호출하기만 하면 됩니다.

반면에 스파이는 다르게 행동할 것입니다. 실제로 add 메소드의 실제 구현을 호출하고 기본 목록에 요소를 추가합니다.

@Test
void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());

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

    assertThat(spyList).hasSize(1);
}

6. Understatnding the Mockito NotAMockException

이 마지막 섹션에서는 Mockito NotAMockException에 대해 알아봅니다. 이 예외는 모의 또는 스파이를 오용할 때 발생할 가능성이 있는 일반적인 예외 중 하나입니다.

이 예외가 발생할 수 있는 상황을 이해하는 것부터 시작하겠습니다.

List<String> list = new ArrayList<String>();
doReturn(100).when(list).size();

이 코드 조각을 실행하면 다음 오류가 발생합니다.

org.mockito.exceptions.misusing.NotAMockException: 
Argument passed to when() is not a mock!
Example of correct stubbing:
    doThrow(new RuntimeException()).when(mock).someMethod();

고맙게도 Mockito 오류 메시지를 보면 여기서 문제가 무엇인지 확실히 알 수 있습니다. 우리의 예에서 list 객체는 모의 객체가 아닙니다. Mockito when() 메소드는 모의 객체 또는 스파이 객체를 인수로 기대합니다.

우리가 볼 수 있듯이 예외 메시지는 올바른 호출이 어떤 모습이어야 하는지까지 설명합니다. 이제 문제가 무엇인지 더 잘 이해했으므로 권장 사항에 따라 문제를 해결해 보겠습니다.

final List<String> spyList = spy(new ArrayList<>());
assertThatNoException().isThrownBy(() -> doReturn(100).when(spyList).size());

이제 예제가 예상대로 작동하며 더 이상 Mockito NotAMockException이 표시되지 않습니다.

0개의 댓글