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


122

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

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

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

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

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

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

답변:


236

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코드에 직접 호출을 추가 하십시오. 스택에 무언가가 있으면 던질 것이며 이는 나쁜 증상에 대한 좋은 경고입니다.


2
이 글에 감사드립니다. when / thenReturn 형식의 NullPointerException은 doReturn / when으로 변경할 때까지 문제를 일으켰습니다.
yngwietiger

11

내 문제 중 하나에 대한 해결책을 찾을 때이 질문을 찾았 기 때문에 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.


2
이것은 유용한 정보이지만 일치자가 아닌 스터 빙과 관련이 있으므로이 질문에 대해서는 이해가되지 않을 수 있습니다. 순서는 중요하지만 마지막으로 정의 된 일치 체인이 승리 한다는 점에서만 : 이는 공존하는 스텁이 가장 구체적으로 선언되는 경우가 많지만, 경우에 따라 단일 테스트 케이스에서 특별히 모의 된 동작을 매우 광범위하게 재정의 할 수 있음을 의미합니다. ,이 시점에서 광범위한 정의가 마지막에 와야 할 수도 있습니다.
Jeff Bowman

1
@JeffBowman 질문은 mockito 매처에 관한 것이고 매처는 (대부분의 예에서와 같이) 스터 빙 할 때 사용할 수 있기 때문에이 질문에 의미가 있다고 생각했습니다. Google에서 설명을 검색하면이 질문에 이르게되었으므로 여기 에이 정보가 있으면 유용하다고 생각합니다.
tibtof
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.