Programming

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

procodes 2020. 7. 28. 22:16
반응형

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


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

  • Mockito matcher는 matcher를 사용한 후 오래 실행되는 코드에서도 종종 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래핑 된 ArgumentMatcher<T>객체를 호출 org.hamcrest.Matcher<T>합니다. 와 같은 argThatHamcrest 어댑터 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 스타일 매처를 랩핑 하여 인수로 표시 있도록 리턴합니다 . 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

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

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

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

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

구현 세부 사항

매처는 ArgumentMatcherStorage 라는 클래스에 포함 된 스택에 Hamcrest 스타일 객체 매 처로 저장됩니다 . MockitoCore 및 Matchers는 각각 ThreadSafeMockingProgress 인스턴스를 소유하며 , MockingProgress 인스턴스를 보유하는 ThreadLocal 정적으로 포함합니다. 그것은이의 MockingProgressImpl 콘크리트의 보유 ArgumentMatcherStorageImpl을 . 결과적으로 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. Call when(false), which discards its argument and prepares to stub method quux(int, int) identified in 5. The only two valid states are with stack length 0 (equality) or 2 (matchers), and there are two matchers on the stack (steps 1 and 4), so Mockito stubs the method with an any() matcher for its first argument and and(gt(10), lt(20)) for its second argument and clears the stack.

This demonstrates a few rules:

  • Mockito can't tell the difference between quux(anyInt(), 0) and quux(0, anyInt()). They both look like a call to quux(0, 0) with one int matcher on the stack. Consequently, if you use one matcher, you have to match all arguments.

  • Call order isn't just important, it's what makes this all work. Extracting matchers to variables generally doesn't work, because it usually changes the call order. Extracting matchers to methods, however, works great.

    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.
    
  • The stack changes often enough that Mockito can't police it very carefully. It can only check the stack when you interact with Mockito or a mock, and has to accept matchers without knowing whether they're used immediately or abandoned accidentally. In theory, the stack should always be empty outside of a call to when or verify, but Mockito can't check that automatically. You can check manually with Mockito.validateMockitoUsage().

  • In a call to when, Mockito actually calls the method in question, which will throw an exception if you've stubbed the method to throw an exception (or require non-zero or non-null values). doReturn and doAnswer (etc) do not invoke the actual method and are often a useful alternative.

  • If you had called a mock method in the middle of stubbing (e.g. to calculate an answer for an eq matcher), Mockito would check the stack length against that call instead, and likely fail.

  • If you try to do something bad, like stubbing/verifying a final method, Mockito will call the real method and also leave extra matchers on the stack. The final method call may not throw an exception, but you may get an InvalidUseOfMatchersException from the stray matchers when you next interact with a mock.

Common problems

  • InvalidUseOfMatchersException:

    • Check that every single argument has exactly one matcher call, if you use matchers at all, and that you haven't used a matcher outside of a when or verify call. Matchers should never be used as stubbed return values or fields/variables.

    • Check that you're not calling a mock as a part of providing a matcher argument.

    • Check that you're not trying to stub/verify a final method with a matcher. It's a great way to leave a matcher on the stack, and unless your final method throws an exception, this might be the only time you realize the method you're mocking is final.

  • NullPointerException with primitive arguments: (Integer) any() returns null while any(Integer.class) returns 0; this can cause a NullPointerException if you're expecting an int instead of an Integer. In any case, prefer anyInt(), which will return zero and also skip the auto-boxing step.

  • NullPointerException or other exceptions: Calls to when(foo.bar(any())).thenReturn(baz) will actually call foo.bar(null), which you might have stubbed to throw an exception when receiving a null argument. Switching to doReturn(baz).when(foo).bar(any()) skips the stubbed behavior.

General troubleshooting

  • Use MockitoJUnitRunner, or explicitly call validateMockitoUsage in your tearDown or @After method (which the runner would do for you automatically). This will help determine whether you've misused matchers.

  • For debugging purposes, add calls to validateMockitoUsage in your code directly. This will throw if you have anything on the stack, which is a good warning of a bad symptom.


Just a small addition to Jeff Bowman's excellent answer, as I found this question when searching for a solution to one of my own problems:

If a call to a method matches more than one mock's when trained calls, the order of the when calls is important, and should be from the most wider to the most specific. Starting from one of Jeff's examples:

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

is the order that ensures the (probably) desired result:

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

If you inverse the when calls then the result would always be true.

참고URL : https://stackoverflow.com/questions/22822512/how-do-mockito-matchers-work

반응형