반응형

환경: java17, springboot3.3, mockito-core5.11

given(moneyUseCase.giveMoney(any(), anyString(), anyString(), anyLong(), anyString(), anyLong(), anyString(), anyString())).willReturn(
    MoneyResult.SUCCESS);

mockito 테스트 코드를 작성하고 돌려보는 도중 아래와 같은 에러가 발생하였다. 함수의 인자를 보고 쓴 거라 모킹에 틀린 게 없을 텐데..?

org.mockito.exceptions.misusing.PotentialStubbingProblem: 
Strict stubbing argument mismatch. Please check:
 - this invocation of 'giveMoney' method:
    MoneyUseCase.giveMoney(  //나는 이렇게 테스트 날렸는데
    com.aa.Rating@30a73947,
    "HYM0000002",
    "127.0.0.1",
    10000000L,
    null,
    10000L,
    "NORIRAT",
    "NORIBASEV"
);
    -> at com.aa.application.service.EventMoneyService.giveMoney(EventMoneyService.java:59)
 - has following stubbing(s) with different arguments:
    1. MoneyUseCase.giveMoney( //any-family는 이렇게 인식했다는거...?
    null,
    "",
    "",
    0L,
    "",
    0L,
    "",
    ""
);
      -> at com.aa.application.service.EventMoneyServiceTest.giveEventReward__fail_giveMoney(EventMoneyServiceTest.java:92)
Typically, stubbing argument mismatch indicates user mistake when writing tests.

 

이 에러는 무엇인가 PotentialStubbingProblem

이 오류는 Mockito에서 엄격한 스텁(strict stubbing) 설정으로 인해 발생한다. Mockito의 엄격한 스텁 모드에서는 정의한 스텁과 호출된 메서드의 인자가 정확히 일치하지 않으면 예외를 발생하는데... Mockito가 기대한 스텁 설정과 실제 호출 인자가 일치하지 않아 정의된 스텁이 호출되지 않은 것으로 간주되어 오류를 발생시키는 것이다.

하지만 any() 매처(org.mockito.ArgumentMatchers.any*)를 사용하면 Mockito는 특정 값이 아닌 타입에 대한 일치만 확인한다. 이 말은, anyString()이 "정확한 문자열 값"을 요구하지 않고 그저 문자열 타입의 어떤 값이든 허용한다는 뜻이다. 그리고 나는 그것을 의도했다.

그럼 이 에러는 왜 나는 것일까

any 매처가 올바르게 작성되었음에도 에러가 나는게 의아하여 에러로그를 자세히 살펴본다.

위 부분이 제일 의심스럽다. 이 부분을 anyString()으로 stubbing 했는데, 만든 request에는 null로 세팅했다.

anyString에 대해 검색해보니 아래와 같은 내용이 나온다.

anyString() 은 null에 매핑되지 않는다!

이게 좀 걸려서 anyString()에 대해 찾아본다.

Mockito에서 anyString()은 null이 아닌 임의의 문자열과 일치한다. 즉, anyString()은 null 값과 매칭되지 않으며, null이 전달된 경우 Mockito는 anyString()으로 매칭하지 않는다. 만약 null을 포함하여 매칭하려면 nullable(String.class)를 사용해야 한다.

String 값에는 null이 충분히 들어가지만, 이걸 모킹할 때는 빈 스트링("")을 넘겨야 하는구나.. 그럼 any()로 바꿔보자.

given(moneyUseCase.giveMoney(any(Rating.class), anyString(), anyString(), anyLong(), any(), anyLong(), anyString(),
    anyString())).willReturn(MoneyResult.FAIL_INIT);

성공한다...! 이럴 수가..

 

이 에러가 날 때마다 매번 토끼눈 뜨고 비교하는 게 여간 번거로울 듯하다. 그렇다고 any()로 다 처리하는 건 너무 가능성을 열어 놓는 것 같고... 다른 해결책은 없을까?

1. nullable(String.class)

널이 올 확률이 아주 높으면 명시하는 것도 좋을 것 같다.

// anyString()은 null이 아닌 모든 String과 매칭
given(mock.someMethod(anyString())).willReturn("result"); // 이 경우 null은 매칭되지 않음

// null 값을 포함하여 String과 매칭하려면 nullable 사용
given(mock.someMethod(nullable(String.class))).willReturn("result");

2. 해당 모킹에 lenient를 달기

lenient().when(
    moneyUseCase.giveMoney(any(Rating.class), anyString(), anyString(), anyLong(), anyString(), anyLong(), anyString(),
        anyString())).thenReturn(MoneyResult.FAIL_INIT);

추가로 주의해야 하는 점은 lenient()은 given()과는 직접 함께 쓸 수는 없다는 점. lenient()는 Mockito의 when() 구문과 함께 사용해야 한다. lenient()는 Mockito의 엄격한 검사(strict stubbing)를 완화하여 인자 일치 오류와 같은 문제를 방지한다.

3. 클래스 레벨에서 처리

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)

null을 허용하지 않는 매처들

기본 타입용 매처 (Primitive Matchers)

  • anyInt()
  • anyLong()
  • anyDouble()
  • anyFloat()
  • anyBoolean()
  • anyChar()
  • anyShort()
  • anyByte()

이유:

  • 기본 타입(primitive)은 null을 가질 수 없으므로, null을 전달하려고 하면 NullPointerException이 발생합니다.
  • nullable(Long.class) 또는 eq(null): null 값을 처리하고자 할 때 사용.
  • Mockito는 이러한 매처를 사용할 때, 내부적으로 해당 타입의 기본값(예: int는 0, boolean은 false)을 검증 기준으로 삼습니다.
728x90
반응형

'개발 > java' 카테고리의 다른 글

[동기화] 뮤텍스/세마포어  (0) 2024.11.24
DB로 분산락 구현  (2) 2024.11.21
자바와 스프링에서 thread pool  (0) 2024.11.11
[test] object mother  (2) 2024.09.26
자바 버전별 특징  (0) 2024.09.23

+ Recent posts