[Mockito] Answer를 알아보자

예니·2022년 6월 12일
4

Mockito Answers 관련 javadoc 번역 및 테스트를 해보았다.

Unstubbed mock의 동작 방식을 구현해 주고 싶을 때 사용한다.
@Mock(answer = Answers.RETURNS_MOCKS) 이런 식으로 사용할 수 있다.
어노테이션의 필드가 아니라면 모킹할 때 직접적으로 mock(class, Answer)의 형태로 사용할 수 있다.
Foo mock = mock(Foo.class, RETURNS_DEEP_STUBS);

Answers enum에 있는 값들을 살펴보자.

1. RETURNS_DEFAULTS

@Mock에 아무 설정을 주지 않으면 RETURNS_DEFAULTS로 설정된다.
Mock 객체가 stubbing 되지 않았으면, 빈 값을 반환한다.
(GloballyConfiguredAnswer 사용)

public class YenyService {
  public String saveYeny(String yeny) {
    String yenyString = "i love " + yeny + " yeny";
    return yenyString;
  }
}
@ExtendWith(MockitoExtension.class)
public class YenyTest {
  @Mock(answer = RETURNS_DEFAULTS)
  private YenyService yenyService;

  @Test
  void saveyenyWithStaticMockTest() {
    YenyService yenyService = mock(YenyService.class, RETURNS_DEFAULTS);
    String yenyName = yenyService.saveYeny("Macintosh");
    System.out.println("saveyenyWithStaticMockTest yenyName = " + yenyName);
  }

  @Test
  void saveyenyWithAnnotationMockTest() {
    String yenyName = yenyService.saveYeny("Macintosh");
    System.out.println("saveyenyWithAnnotationMockTest yenyName = " + yenyName);
  }
}

2. RETURNS_SMART_NULLS

stubbing 되지 않은 메소드를 호출할 때, 보통은 NullPointerException을 발생시킨다.
RETURNS_SMART_NULLS은 null 대신 SmartNull을 반환한다.
SmartNull은 SmartNullPointerException라는 NPE보다 나은 예외를 발생시킨다. stack trace에서 unstubbed method의 라인을 찾는 데에 더 유용하다.

RETURNS_DEFAULTS 예제의 YenyService 클래스 사용

@ExtendWith(MockitoExtension.class)
public class YenyTest {
  @Mock(answer = RETURNS_SMART_NULLS)
  private YenyService yenyService;

  @Test
  void saveyenyWithStaticMockTest() {
    YenyService yenyService = mock(YenyService.class, RETURNS_SMART_NULLS);
    String yenyName = yenyService.saveYeny("Macintosh");
    System.out.println("saveYenyWithStaticMockTest yenyName = " + yenyName);
    System.out.println("saveYenyWithStaticMockTest yenyName = " + yenyName.toUpperCase());
  }

  @Test
  void saveyenyWithAnnotationMockTest() {
    String yenyName = yenyService.saveYeny("Macintosh");
    System.out.println("saveYenyWithAnnotationMockTest yenyName = " + yenyName);
    System.out.println("saveYenyWithStaticMockTest yenyName = " + yenyName.toUpperCase());
  }
}


오잉 NPE가 아니라 SmartNullPointerException이 발생할 줄 알았는데, 그냥 아무것도 안뜬다.

3. RETURNS_MOCKS

Unstubbed method에 대해 일반적으로 null을 반환하지만, 모키토가 응답을 감지하고 그에 맞는 응답을 만들도록 하려면 RETURNS_MOCKS를 사용하면 된다.

public class YenyService {
  private Yeny yeny;

  public Yeny getYeny() {
    return yeny;
  }

  public class Yeny {
  }
}

일반적인 경우

@ExtendWith(MockitoExtension.class)
class YenyTest {

  @Mock private YenyService yenyService;

  @Test
  void getYenyWithStaticMockTest() {
    YenyService yenyService = mock(YenyService.class);
    Yeny yeny = yenyService.getYeny();
    Yeny yeny1 = yenyService.getYeny();
    System.out.println("yeny = " + yeny);
    System.out.println("yeny1 = " + yeny1);
    assertEquals(yeny, yeny1);
  }

  @Test
  void getYenyWithAnnotationMockTest() {
    Yeny yeny = yenyService.getYeny();
    Yeny yeny1 = yenyService.getYeny();
    System.out.println("yeny = " + yeny);
    System.out.println("yeny1 = " + yeny1);
    assertEquals(yeny, yeny1);
  }
}


null이 나온다. 또한, yeny, yeny1 모두 null이므로 assertEquals를 통과한다.

RETURNS_MOCKS 사용

@ExtendWith(MockitoExtension.class)
class YenyTest {

  @Mock(answer = Answers.RETURNS_MOCKS)
  private YenyService yenyService;

  @Test
  void getYenyWithStaticMockTest() {
    YenyService yenyService = mock(YenyService.class, RETURNS_MOCKS);
    Yeny yeny = yenyService.getYeny();
    Yeny yeny1 = yenyService.getYeny();
    System.out.println("yeny = " + yeny);
    System.out.println("yeny1 = " + yeny1);
    assertNotEquals(yeny, yeny1);
  }

  @Test
  void getYenyWithAnnotationMockTest() {
    Yeny yeny = yenyService.getYeny();
    Yeny yeny1 = yenyService.getYeny();
    System.out.println("yeny = " + yeny);
    System.out.println("yeny1 = " + yeny1);
    assertNotEquals(yeny, yeny1);
  }
}

모키토가 unstubbed method의 응답을 감지하여 새로운 응답을 생성했기 때문에, 모두 다른 객체가 나온다.

RETURNS_MOCKS을 사용하면 내부 변수들까지 stub해준다!

public class YenyService {
  private Yeny yeny;

  public Yeny getYeny() {
    return yeny;
  }

  public class Yeny {
    private Best best;

    public Best getBest() {
      return best;
    }

    public class Best {
    }
  }
}
@ExtendWith(MockitoExtension.class)
class YenyTest {

  @Mock(answer = Answers.RETURNS_MOCKS)
  private YenyService yenyService;

  @Test
  void getYenyWithStaticMockTest() {
    YenyService yenyService = mock(YenyService.class, RETURNS_MOCKS);
    Yeny yeny = yenyService.getYeny();
    Yeny yeny1 = yenyService.getYeny();
    System.out.println("yeny = " + yeny);
    System.out.println("yeny1 = " + yeny1);
    assertNotEquals(yeny, yeny1);
    System.out.println("yeny.getBest() = " + yeny.getBest());
    System.out.println("yeny1.getBest() = " + yeny1.getBest());
    assertNotEquals(yeny.getBest(), yeny1.getBest());
  }

  @Test
  void getYenyWithAnnotationMockTest() {
    Yeny yeny = yenyService.getYeny();
    Yeny yeny1 = yenyService.getYeny();
    System.out.println("yeny = " + yeny);
    System.out.println("yeny1 = " + yeny1);
    assertNotEquals(yeny, yeny1);
    System.out.println("yeny.getBest() = " + yeny.getBest());
    System.out.println("yeny1.getBest() = " + yeny1.getBest());
    assertNotEquals(yeny.getBest(), yeny1.getBest());
  }
}

4. RETURNS_DEEP_STUBS

중첩된 클래스들이 있는 경우, 보통의 경우 아래와 같이 중간 단계에 속한 모든 mock method가 필요하다.

일반적인 경우

public class YenyService {
  private Yeny yeny;

  public Yeny getYeny() {
    return yeny;
  }

  public class Yeny {
    private Best best;

    public Best getBest() {
      return best;
    }

    public class Best {
      private String yenyName;

      public String getYenyName() {
        return yenyName;
      }
    }
  }
}
@ExtendWith(MockitoExtension.class)
class YenyTest {

  @Mock private YenyService yenyService;
  @Mock private Yeny yeny;
  @Mock private Best best;

  @Test
  void withStaticMockTest() {
    YenyService yenyService = mock(YenyService.class);
    Yeny yeny = mock(Yeny.class);
    Best best = mock(Best.class);
    when(yenyService.getYeny()).thenReturn(yeny);
    when(yeny.getBest()).thenReturn(best);
    when(best.getYenyName()).thenReturn("i love yeny");
    when(yenyService.getYeny().getBest().getYenyName()).thenReturn("i love yeny");
    assertEquals("i love yeny", yenyService.getYeny().getBest().getYenyName());
  }

  @Test
  void withAnnotationMockTest() {
    when(yenyService.getYeny()).thenReturn(yeny);
    when(yeny.getBest()).thenReturn(best);
    when(best.getYenyName()).thenReturn("i love yeny");
    when(yenyService.getYeny().getBest().getYenyName()).thenReturn("i love yeny");
    assertEquals("i love yeny", yenyService.getYeny().getBest().getYenyName());
  }
}

RETURNS_DEEP_STUBS

YenyService 코드는 위와 같음

@ExtendWith(MockitoExtension.class)
class YenyTest {

  @Mock(answer = RETURNS_DEEP_STUBS)
  private YenyService yenyService;

  @Test
  void withStaticMockTest() {
    YenyService yenyService = mock(YenyService.class, RETURNS_DEEP_STUBS);
    when(yenyService.getYeny().getBest().getYenyName()).thenReturn("i love yeny");
    assertEquals("i love yeny", yenyService.getYeny().getBest().getYenyName());
  }

  @Test
  void withAnnotationMockTest() {
    when(yenyService.getYeny().getBest().getYenyName()).thenReturn("i love yeny");
    assertEquals("i love yeny", yenyService.getYeny().getBest().getYenyName());
  }
}

RETURNS_DEEP_STUB를 사용하면, 내부적으로 중간 단계의 객체들을 모킹해준다.
우리는 가장 마지막 결과만 모킹해주면 된다.
마지막 결과가 primitive 타입이나 final 클래스인 경우에는 사용할 수 없다!

5. CALLS_REAL_METHODS

Unstubbed method에 대해 실제 메소드를 호출하는 partial mock 객체를 생성한다.
CALLS_REAL_METHODS이 사용되지 않았다면, unstubbed method는 Nice Mock을 사용하고 null을 반환한다.
CALLS_REAL_METHODS는 unstubbed method에 대해서만 동작한다. Stubbed method라면 스텁된 결과가 반환된다.

public class YenyService {

  public String saveYeny(String yeny) {
    String yenyString = "i love " + yeny + " yeny";
    System.out.println("yenyString = " + yenyString);
    return yenyString;
  }
}
@ExtendWith(MockitoExtension.class)
class YenyTest {

  @Mock(answer = CALLS_REAL_METHODS)
  private YenyService yenyService;

  @Test
  void withStaticMockTest() {
    YenyService yenyService  = mock(YenyService.class, Mockito.CALLS_REAL_METHODS);
    String yeny = yenyService.saveYeny("Yeny");
    verify(yenyService).saveYeny("Yeny");
    assertEquals("i love Yeny yeny", yeny);
  }

  @Test
  void withAnnotationMockTest() {
    String yeny = yenyService.saveYeny("Yeny");
    verify(yenyService).saveYeny("Yeny");
    assertEquals("i love Yeny yeny", yeny);
  }
}

6. RETURNS_SELF

Builder 클래스는 build() 메소드 외에는 자기 자신 self 인스턴스를 리턴한다.
자기 자신객체를 리턴하는 모든 메소드에 대해 mocking 을 편하게 지정하기 위해 사용한다.


참고문서

https://wesome.org/mockito-3-answere-returns-deep-stubs

0개의 댓글