[java] Mockito 매처는 어떻게 작동합니까?

Mockito 인수 매처 (matcher) (예는 any, argThat, eq, same,과 ArgumentCaptor.capture()) Hamcrest 매처 (matcher)에서 매우 다르게 동작합니다.

  • Mockito 매처는 매 처가 사용 된 후 오랫동안 실행되는 코드에서도 종종 InvalidUseOfMatchersException을 발생시킵니다.

  • Mockito 매처는 주어진 메소드의 한 인수가 매처를 사용하는 경우 모든 인수에 대해 Mockito 매처 만 사용하도록 요구하는 것과 같은 이상한 규칙을 따릅니다.

  • Mockito 매처는 Answers를 재정의 하거나 (Integer) any()등을 사용할 때 NullPointerException을 일으킬 수 있습니다 .

  • 특정 방식으로 Mockito 매 처로 코드를 리팩토링하면 예외와 예기치 않은 동작이 발생할 수 있으며 완전히 실패 할 수 있습니다.

Mockito 매 처가 이와 같이 설계된 이유는 무엇이며 어떻게 구현됩니까?



답변

Mockito 매처 는 정적 메서드 및 해당 메서드 에 대한 호출로 when, 및을 호출하는 동안 인수 를 나타 verify냅니다.

Hamcrest 매처 (아카이브 버전) (또는 Hamcrest 스타일 매처)는 객체가 Matcher의 기준과 일치하는 경우 true를 반환 Matcher<T>하는 메서드 matches(T)를 구현 하고 노출하는 상태 비 저장 범용 객체 인스턴스입니다 . 그들은 부작용이 없도록 의도되었으며 일반적으로 아래와 같은 주장에 사용됩니다.

/* Mockito */  verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));

Mockito 매처는 Hamcrest 스타일 매처와 별도로 존재 하므로 일치하는 표현식에 대한 설명이 메서드 호출에 직접 들어 맞습니다 . Mockito 매처 T는 Hamcrest 매처 메서드가 Matcher 객체 (유형 Matcher<T>)를 반환하는 위치를 반환합니다 .

Mockito 매처 (matcher)는 같은 정적 인 방법을 통해 호출 eq, any, gt,과 startsWithorg.mockito.Matchersorg.mockito.AdditionalMatchers. Mockito 버전에서 변경된 어댑터도 있습니다.

  • Mockito 1.x의 경우 Matchers일부 호출 (예 : intThat또는 argThat)은 Hamcrest 매처를 매개 변수로 직접 허용하는 Mockito 매처입니다. ArgumentMatcher<T>extended org.hamcrest.Matcher<T>는 내부 Hamcrest 표현에 사용되었으며 모든 종류의 Mockito 매처 대신 Hamcrest 매처 기본 클래스 였습니다.
  • Mockito 2.0+의 경우 Mockito는 더 이상 Hamcrest에 직접 의존하지 않습니다. 더 이상 구현 되지 않지만 비슷한 방식으로 사용되는 객체 Matchers로 표현 intThat되거나 argThat래핑 된 호출 . 같은 Hamcrest 어댑터 와는 여전히 사용할 수 있지만 이동 한 대신.ArgumentMatcher<T>org.hamcrest.Matcher<T>argThatintThatMockitoHamcrest

매 처가 Hamcrest이든 단순히 Hamcrest 스타일이든 상관없이 다음과 같이 적용 할 수 있습니다.

/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));

위의 문장에서 : foo.setPowerLevelint. 인수 로 작동하지 않는를 is(greaterThan(9000))반환합니다 . Mockito 매처는 Hamcrest 스타일 Matcher를 반환는 것을 랩 그렇게 할 수 있습니다 인수로 표시; Mockito 매처 는 예제 코드의 첫 번째 줄 에서처럼 전체 표현식을 단일 호출로 래핑합니다.Matcher<Integer>setPowerLevelintThatintgt(9000)

매 처가하는 일 / 반환

when(foo.quux(3, 5)).thenReturn(true);

인수 매처를 사용하지 않을 때 Mockito는 인수 값을 기록하고 equals메서드 와 비교합니다 .

when(foo.quux(eq(3), eq(5))).thenReturn(true);    // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different

any또는 gt(보다 큼) 과 같은 매처를 호출 하면 Mockito는 Mockito가 해당 동등성 검사를 건너 뛰고 선택한 일치 항목을 적용하도록하는 매처 객체를 저장합니다. 이 경우 argumentCaptor.capture()나중에 검사를 위해 인수를 저장하는 매처를 저장합니다.

정합 기 반환 더미 값 과 같은 제로로, 빈 컬렉션을, 또는 null. Mockito는 0과 같은 안전하고 적절한 더미 값을 반환하려고 anyInt()하거나 any(Integer.class)또는 빈 List<String>에 대한 anyListOf(String.class). 그러나 유형 삭제로 인해 Mockito에는 또는 nullfor 이외의 값을 반환하는 유형 정보가 없으므로 기본 값 을 “자동 해제”하려고하면 NullPointerException이 발생할 수 있습니다 .any()argThat(...)null

매처 는 매개 변수 값을 좋아 eq하고 gt받습니다. 이상적으로 이러한 값은 스터 빙 / 검증이 시작되기 전에 계산되어야합니다. 다른 호출을 조롱하는 중에 모의를 호출하면 스터 빙을 방해 할 수 있습니다.

Matcher 메서드는 반환 값으로 사용할 수 없습니다. 예를 들어, 문구 thenReturn(anyInt())thenReturn(any(Foo.class))Mockito 에는 방법이 없습니다 . Mockito는 스터 빙 호출에서 반환 할 인스턴스를 정확히 알아야하며 임의의 반환 값을 선택하지 않습니다.

구현 세부 정보

매처는 ArgumentMatcherStorage 라는 클래스에 포함 된 스택에 (Hamcrest 스타일의 객체 매 처로) 저장됩니다 . MockitoCore 및 Matchers는 각각 ThreadSafeMockingProgress 인스턴스를 소유하며 , 여기 에는 MockingProgress 인스턴스를 보유하는 ThreadLocal 이 정적으로 포함됩니다. 구체적인 ArgumentMatcherStorageImpl 을 보유하는 것은 이 MockingProgressImpl 입니다 . 결과적으로 mock 및 matcher 상태는 정적이지만 Mockito 및 Matchers 클래스간에 일관되게 스레드 범위가 지정됩니다.

대부분의 매처 호출은이 스택에만 추가 and되지만 or, 및not 같은 매처는 예외입니다 . 이것은 메소드를 호출하기 전에 왼쪽에서 오른쪽으로 인수를 평가하는 Java 평가 순서 와 완벽하게 일치하며 이에 의존합니다 .

when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6]      [5]  [1]       [4] [2]     [3]

이것은 :

  1. anyInt()스택에 추가하십시오 .
  2. gt(10)스택에 추가하십시오 .
  3. lt(20)스택에 추가하십시오 .
  4. 제거 gt(10)lt(20)추가합니다 and(gt(10), lt(20)).
  5. 를 호출합니다 foo.quux(0, 0)(스텁 처리되지 않은 경우) false. 기본값을 반환합니다 . 내부적으로 Mockito quux(int, int)는 가장 최근 통화로 표시합니다 .
  6. when(false)인수를 버리고 quux(int, int)5에서 식별 된 메서드를 스텁 할 준비를하는를 호출 합니다. 유효한 상태는 스택 길이가 0 (동일) 또는 2 (매처)이며 스택에 두 개의 일치자가 있습니다 (1 단계 및 4 단계). Mockito any()는 첫 번째 인수와 and(gt(10), lt(20))두 번째 인수에 대해 매처를 사용 하여 메서드를 스텁 하고 스택을 지 웁니다.

이것은 몇 가지 규칙을 보여줍니다.

  • Mockito는 quux(anyInt(), 0)과 의 차이를 알 수 없습니다 quux(0, anyInt()). 둘 다 quux(0, 0)스택에 하나의 int 매 처가 있는 호출처럼 보입니다 . 따라서 하나의 매처를 사용하는 경우 모든 인수를 일치시켜야합니다.

  • 통화 순서는 중요 할뿐만 아니라이 모든 것이 작동하도록 합니다. 매처를 변수로 추출하는 것은 일반적으로 호출 순서를 변경하기 때문에 일반적으로 작동하지 않습니다. 그러나 매처를 메서드로 추출하는 것은 훌륭하게 작동합니다.

    int between10And20 = and(gt(10), lt(20));
    /* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
    // Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
    
    public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
    /* OK */  when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
    // The helper method calls the matcher methods in the right order.
    
  • 스택은 Mockito가 매우 신중하게 감시 할 수 없을 정도로 자주 변경됩니다. Mockito 또는 mock과 상호 작용할 때만 스택을 확인할 수 있으며, 매 처가 즉시 사용되는지 또는 실수로 버려지는지 알지 못한 채 매처를 수락해야합니다. 이론적으로 스택은 when또는 호출 외부에서 항상 비어 있어야 verify하지만 Mockito는이를 자동으로 확인할 수 없습니다. 를 사용하여 수동으로 확인할 수 있습니다 Mockito.validateMockitoUsage().

  • 를 호출 할 when때 Mockito는 실제로 해당 메서드를 호출합니다.이 메서드는 예외를 throw하기 위해 메서드를 스텁 처리 한 경우 (또는 0이 아니거나 null이 아닌 값이 필요한 경우) 예외를 throw합니다.
    doReturn그리고 doAnswer(등) 실제 메소드를 호출 하지 않으며 종종 유용한 대안입니다.

  • eq스터 빙 도중에 모의 메서드를 호출했다면 (예 : matcher에 대한 답변을 계산하기 위해 ) Mockito는 대신 해당 호출 에 대해 스택 길이를 확인하고 실패 할 가능성이 높습니다.

  • 최종 메서드 스터 빙 / 검증 과 같이 나쁜 일을하려고하면 Mockito는 실제 메서드를 호출하고 추가 매처를 스택에 남겨 둡니다 . final메서드 호출 예외가 발생하지 않을 수 있습니다,하지만 당신은 얻을 수 InvalidUseOfMatchersException을 모의와 부유 매처 (matcher) 당신 옆의 상호 작용에서.

일반적인 문제

  • InvalidUseOfMatchersException :

    • 매처를 전혀 사용하지 않는 경우 모든 단일 인수에 정확히 하나의 매처 호출이 있는지 확인하고 when또는 verify호출 외부에서 매처를 사용하지 않았는지 확인 합니다. 매처는 스텁 반환 값 또는 필드 / 변수로 사용해서는 안됩니다.

    • matcher 인수 제공의 일부로 mock을 호출하지 않았는지 확인하십시오.

    • 매처를 사용하여 최종 메서드를 스텁 / 검증하려고하지 않는지 확인하십시오. 스택에 매처를 남겨 두는 좋은 방법이며, 최종 메서드가 예외를 throw하지 않는 한, 조롱하는 메서드가 최종임을 깨달은 유일한 시간 일 수 있습니다.

  • 기본 인수가있는 NullPointerException : 0 (Integer) any()any(Integer.class)반환 하는 동안 null을 반환합니다. 이것은 Integer 대신 NullPointerExceptionan int을 기대하는 경우 발생할 수 있습니다 . 어쨌든 anyInt()0을 반환하고 자동 박싱 단계를 건너 뛰는을 선호 합니다.

  • NullPointerException 또는 기타 예외 :에 대한 호출 when(foo.bar(any())).thenReturn(baz)은 실제로를 호출합니다 foo.bar(null) . 이는 null 인수를받을 때 예외를 throw하기 위해 스텁 처리되었을 수 있습니다. 로 전환 doReturn(baz).when(foo).bar(any()) 하면 스텁 동작건너 뜁니다 .

일반적인 문제 해결

  • 사용 MockitoJUnitRunner는 , 또는 명시 적으로 호출 validateMockitoUsage하여에서 tearDown또는 @After(주자가 자동으로 당신을 위해 할 것이다)하는 방법. 이것은 매처를 오용했는지 여부를 확인하는 데 도움이됩니다.

  • 디버깅을 위해 validateMockitoUsage코드에 직접 호출을 추가 하십시오. 스택에 무언가가 있으면 던질 것이며 이는 나쁜 증상에 대한 좋은 경고입니다.


답변

내 문제 중 하나에 대한 해결책을 찾을 때이 질문을 찾았 기 때문에 Jeff Bowman의 탁월한 답변에 약간의 추가 사항입니다.

메서드 호출이 두 개 이상의 모의 when훈련 된 호출 과 일치하는 경우 호출 순서 when가 중요하며 가장 넓은 것부터 가장 구체적인 것까지 순서가 지정 되어야합니다. Jeff의 예 중 하나에서 시작합니다.

when(foo.quux(anyInt(), anyInt())).thenReturn(true);
when(foo.quux(anyInt(), eq(5))).thenReturn(false);

(아마도) 원하는 결과를 보장하는 순서입니다.

foo.quux(3 /*any int*/, 8 /*any other int than 5*/) //returns true
foo.quux(2 /*any int*/, 5) //returns false

when 호출을 반대로하면 결과는 항상 true.


답변