Mockito 인수 매처 (matcher) (예는 any
, argThat
, eq
, same
,과 ArgumentCaptor.capture()
) Hamcrest 매처 (matcher)에서 매우 다르게 동작합니다.
-
Mockito 매처는 매 처가 사용 된 후 오랫동안 실행되는 코드에서도 종종 InvalidUseOfMatchersException을 발생시킵니다.
-
Mockito 매처는 주어진 메소드의 한 인수가 매처를 사용하는 경우 모든 인수에 대해 Mockito 매처 만 사용하도록 요구하는 것과 같은 이상한 규칙을 따릅니다.
-
Mockito 매처는
Answer
s를 재정의 하거나(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
,과 startsWith
에 org.mockito.Matchers
와 org.mockito.AdditionalMatchers
. Mockito 버전에서 변경된 어댑터도 있습니다.
- Mockito 1.x의 경우
Matchers
일부 호출 (예 :intThat
또는argThat
)은 Hamcrest 매처를 매개 변수로 직접 허용하는 Mockito 매처입니다.ArgumentMatcher<T>
extendedorg.hamcrest.Matcher<T>
는 내부 Hamcrest 표현에 사용되었으며 모든 종류의 Mockito 매처 대신 Hamcrest 매처 기본 클래스 였습니다. - Mockito 2.0+의 경우 Mockito는 더 이상 Hamcrest에 직접 의존하지 않습니다. 더 이상 구현 되지 않지만 비슷한 방식으로 사용되는 객체
Matchers
로 표현intThat
되거나argThat
래핑 된 호출 . 같은 Hamcrest 어댑터 와는 여전히 사용할 수 있지만 이동 한 대신.ArgumentMatcher<T>
org.hamcrest.Matcher<T>
argThat
intThat
MockitoHamcrest
매 처가 Hamcrest이든 단순히 Hamcrest 스타일이든 상관없이 다음과 같이 적용 할 수 있습니다.
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
위의 문장에서 : foo.setPowerLevel
는 int
. 인수 로 작동하지 않는를 is(greaterThan(9000))
반환합니다 . Mockito 매처는 Hamcrest 스타일 Matcher를 반환는 것을 랩 그렇게 할 수 있습니다 인수로 표시; Mockito 매처 는 예제 코드의 첫 번째 줄 에서처럼 전체 표현식을 단일 호출로 래핑합니다.Matcher<Integer>
setPowerLevel
intThat
int
gt(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에는 또는 null
for 이외의 값을 반환하는 유형 정보가 없으므로 기본 값 을 “자동 해제”하려고하면 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]
이것은 :
anyInt()
스택에 추가하십시오 .gt(10)
스택에 추가하십시오 .lt(20)
스택에 추가하십시오 .- 제거
gt(10)
및lt(20)
추가합니다and(gt(10), lt(20))
. - 를 호출합니다
foo.quux(0, 0)
(스텁 처리되지 않은 경우)false
. 기본값을 반환합니다 . 내부적으로 Mockitoquux(int, int)
는 가장 최근 통화로 표시합니다 . when(false)
인수를 버리고quux(int, int)
5에서 식별 된 메서드를 스텁 할 준비를하는를 호출 합니다. 유효한 상태는 스택 길이가 0 (동일) 또는 2 (매처)이며 스택에 두 개의 일치자가 있습니다 (1 단계 및 4 단계). Mockitoany()
는 첫 번째 인수와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 대신NullPointerException
anint
을 기대하는 경우 발생할 수 있습니다 . 어쨌든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
.