programing

모키토 입문자들은 어떻게 일하나요?

sourcejob 2022. 9. 29. 00:14
반응형

모키토 입문자들은 어떻게 일하나요?

Mockito 인수 매처(예:any,argThat,eq,same,그리고.ArgumentCaptor.capture())는 햄크레스트 매처와는 매우 다르게 행동합니다.

  • Mockito matchers는 Invalid Use Of Matchers의 원인이 되는 경우가 많다.예외는 매처 사용 후 장시간 실행되는 코드에서도 마찬가지입니다.

  • 모키토 매치들은 특정 방법의 하나의 인수가 매치어를 사용하는 경우에만 모든 인수에 모키토 매치어를 사용하도록 요구하는 것과 같은 이상한 규칙에 신세를 지고 있다.

  • Mockito matchers가 Null Pointer를 발생시킬 수 있습니다.재정의 시 예외Answer또는 를 사용하는 경우(Integer) any()기타.

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

왜 Mockito는 이렇게 설계되어 있고 어떻게 구현되어 있습니까?

Mockito Matcher는 스태틱메서드 및 이러한 메서드에 대한 호출로 콜 중에 인수를 대신합니다.when그리고.verify.

Hamcrest Matchers(아카이브 버전)(또는 Hamcrest 스타일의 Matchers)는 스테이트리스한 범용 객체 인스턴스입니다.Matcher<T>그리고 방법을 노출시킨다.matches(T)오브젝트가 Matcher의 기준과 일치하는 경우 true를 반환합니다.그것들은 부작용이 없도록 의도되어 있으며, 일반적으로 다음과 같은 주장에 사용됩니다.

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

Mockito matchers는 Hamcrest 스타일의 matchers와는 별도로 존재하기 때문에 일치하는 표현에 대한 설명은 메서드 호출에 직접 들어맞습니다.Mockito 매처는 Hamcrest 매처 메서드가 Matcher 오브젝트(타입)를 반환하는 곳을 반환합니다.

Mockito 매처는 다음과 같은 정적 메서드를 통해 호출됩니다.eq,any,gt,그리고.startsWithorg.mockito.Matchers그리고.org.mockito.AdditionalMatchersMockito 버전에 따라 변경된 어댑터도 있습니다.

  • Mockito 1.x의 경우Matchers몇 가지 콜(예:intThat또는argThat)는 Hamcrest matchers를 파라미터로 직접 받아들이는 Mockito matchers입니다.ArgumentMatcher<T> 연장된org.hamcrest.Matcher<T>내부 Hamcrest 표현에서 사용되었으며 Mockito Matcher 대신 Hamcrest Matcher 기본 클래스였습니다.
  • Mockito 2.0+의 경우, Mockito는 더 이상 Hamcrest에 직접 의존하지 않습니다. Matchers라고 표현된 콜intThat또는argThat더 이상 구현되지 않는 개체 랩org.hamcrest.Matcher<T>비슷한 방법으로 사용됩니다.햄레스트 어댑터(예:argThat and intThat are still available, but have moved to MockitoHamcrest instead.

Regardless of whether the matchers are Hamcrest or simply Hamcrest-style, they can be adapted like so:

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

In the above statement: foo.setPowerLevel is a method that accepts an int. is(greaterThan(9000)) returns a Matcher<Integer>, which wouldn't work as a setPowerLevel argument. The Mockito matcher intThat wraps that Hamcrest-style Matcher and returns an int so it can appear as an argument; Mockito matchers like gt(9000) would wrap that entire expression into a single call, as in the first line of example code.

What matchers do/return

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

When not using argument matchers, Mockito records your argument values and compares them with their equals methods.

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

When you call a matcher like any or gt (greater than), Mockito stores a matcher object that causes Mockito to skip that equality check and apply your match of choice. In the case of argumentCaptor.capture() it stores a matcher that saves its argument instead for later inspection.

Matchers return dummy values such as zero, empty collections, or null. Mockito tries to return a safe, appropriate dummy value, like 0 for anyInt() or any(Integer.class) or an empty List<String> for anyListOf(String.class). Because of type erasure, though, Mockito lacks type information to return any value but null for any() or argThat(...), which can cause a NullPointerException if trying to "auto-unbox" a null primitive value.

Matchers like eq and gt take parameter values; ideally, these values should be computed before the stubbing/verification starts. Calling a mock in the middle of mocking another call can interfere with stubbing.

Matcher methods can't be used as return values; there is no way to phrase thenReturn(anyInt()) or thenReturn(any(Foo.class)) in Mockito, for instance. Mockito needs to know exactly which instance to return in stubbing calls, and will not choose an arbitrary return value for you.

Implementation details

Matchers are stored (as Hamcrest-style object matchers) in a stack contained in a class called ArgumentMatcherStorage. MockitoCore and Matchers each own a ThreadSafeMockingProgress instance, which statically contains a ThreadLocal holding MockingProgress instances. It's this MockingProgressImpl that holds a concrete ArgumentMatcherStorageImpl. Consequently, mock and matcher state is static but thread-scoped consistently between the Mockito and Matchers classes.

대부분의 매처 콜은 , 및 등의 매처 콜을 제외하고 이 스택에만 추가됩니다.이는 메서드를 호출하기 전에 인수를 왼쪽에서 오른쪽으로 평가하는 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. 내부 모키토 마크quux(int, int)가장 최근의 콜로서
  6. 불러when(false)인수를 폐기하고 stub 메서드를 준비합니다.quux(int, int)5로 식별됩니다.유효한 상태는 스택 길이0(등가) 또는 2(매처)뿐이며 스택에는 2개의 매처(스텝1과 4)가 있기 때문에 Mockito는 다음 명령어를 사용하여 메서드를 스터브합니다.any()첫 번째 논의에 대한 매처와and(gt(10), lt(20))스택을 클리어합니다.

여기에는 몇 가지 규칙이 있습니다.

  • 모키토는 그 차이를 모른다.quux(anyInt(), 0)그리고.quux(0, anyInt())둘 다 다른 사람을 찾는 것 같아요quux(0, 0)스택에 1개의 int matcher가 있습니다.따라서 1개의 matcher를 사용하는 경우 모든 인수를 일치시켜야 합니다.

  • 통화 순서는 중요할 뿐만 아니라, 이 모든 것을 가능하게 하는 것입니다.일반적으로 콜 순서를 변경하기 때문에 변수에 대한 매처 추출은 기능하지 않습니다.단, 방법을 통해 Matcher를 추출하는 것은 매우 효과적입니다.

    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나 mock과 대화할 때만 스택을 체크할 수 있으며, 즉시 사용되는지, 실수로 버려지는지 모르는 상태에서 Matcher를 접수해야 합니다.이론적으로 스택은 항상 콜 이외에는 비어 있어야 합니다.when또는verify하지만 Mockito는 그것을 자동으로 확인할 수 없습니다.에서 수동으로 확인할 수 있습니다.Mockito.validateMockitoUsage().

  • 문의처:whenMockito는 실제로 문제의 메서드를 호출합니다.메서드를 스텁하여 예외를 슬로우한 경우(또는 0이 아닌 값 또는 null이 아닌 값이 필요함) 예외가 발생합니다. doReturn그리고.doAnswer(etc) 실제 방법을 호출하지 않으며 종종 유용한 대체 방법이 됩니다.

  • 스터빙 도중에 모의 메서드를 호출한 경우(예를 들어, Subbing에 대한 응답 계산)eqMockito는 대신 스택 길이를 해당 콜과 대조하여 체크하지만 실패할 가능성이 있습니다.

  • 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.

ReferenceURL : https://stackoverflow.com/questions/22822512/how-do-mockito-matchers-work

반응형