[Mockito] Mockito의 ArgumentMatchers

Jungwoong (David) Yoon·2023년 8월 10일
1

Test

목록 보기
2/2

Spring REST Docs를 통한 API 문서화를 위해 Mock Test를 진행하면서 Mockito의 여러 기능들을 알아보았고, 그 중에서도 테스트에 처음 사용해봤던 ArgumentMatchers 에 대해 알아보고 이해한 것들을 여기에 적어보도록 하겠다.


ArgumentMatchers 란?


Mock test를 진행하면서 필요한 객체를 mocking하고, 그 객체의 특정 메소드가 실행될 때, 어떤 값을 반환하는지 설정하는 경우가 있다.

현재 내가 진행 중인 프로젝트의 테스트 코드의 일부분을 예로 들면,

given(imageService.save(eq(userId), any(ImageResult.class)))
		.willReturn(imageResponse);

위의 코드에서 imageService는 Spring Boot의 @MockBean 을 사용한 service 객체이고,

  1. imageService.save() 라는 method가 실행될 시,
  2. imageResponse 를 반환할 것이다

라는 것을 나타내고 있다.

어느 정도 예상했겠지만, ArgumentMatchersimageService.save() 안에 있는 eq(userId)any(ImageResult.class) 를 말한다.

그런데 any()eq() 는 정확히 무엇을 의미하는 것일까?


ArgumentMatchers.any()

Mocking 된 method를 설정하기 위한 방볍은 여러가지가 있는데, 첫번째는 method에 "고정된 값"을 사용하는 것이다.

doReturn("...").when(imageService).do("...");

위와 같이 doReturn() 이나 do() 에 사용된 문자열처럼 고정된 값을 사용할 수 있다.

하지만 모든 테스트가 고정된 값만 사용하지는 않는다. 여러 상황에서 벌어질 수 있는 결과를 확인하는 것이 테스트의 주 목적이기 때문에 다양한 범위의 값, 혹은 다양한 종류의 값을 사용해야 하는 경우가 많이 생긴다.

이럴 때 사용할 수 있는 것이 바로 위에서 나온 any() 이다.

given(imageService.do(anyString())).willReturn(...);

위의 코드는 anyString() 때문에 imageService.do() 에 어떤 문자열이 들어가도 똑같은 값을 반환한다.

any()anyString() 외에도 anyInt(), anyLong()any() 와 관련된 다양한 ArgumentMatchers가 존재한다.

참고로 any()ArgumentMatchers에서 오버로딩 된 method 이다.

  • any() - 포함한 모든 것을 matching (null 포함)
  • any(Class<T>) - null 을 제외 한 모든 Class<T> 객체를 matching

또 다른 예를 살펴보자.

given(imageService.doAgain("foo", anyString())).willReturn(bar);

위의 코드를 보면 imageService.doAgain()"foo" 라는 문자열과 아무 값이나 상관없는 문자열 하나를 더 인수로 받고, bar 라는 값을 반환한다,

라고 생각할 수도 있다.

하지만 위와 비슷하게 작성한 코드를 실행하면

이렇게 InvalidUseOfMatchersException 이 생기는 것을 확인할 수 있다.

왜 이런 예외가 발생한 것일까?


ArgumentMatchers.eq()

Mockito는 mocking 된 method의 모든 인수가 ArguemntMatcher 로 되어 있거나 고정 값이 되도록 강제하고 있다.

만약 mocking 된 method가 하나 이상의 argument를 갖고 있다면, 몇몇 인수만 선택하여 ArgumentMatchers 를 사용하는 것은 불가능하고, 전부 ArgumentMatchers 를 사용하거나 혹은 전부 고정값을 사용해야 한다.

"그럼 전부 다 any() 를 쓰면 되는거 아닌가?"

라고 생각할 수도 있다. 하지만 만약, 특정 인수로 고정값을 넣어야 하는 상황이 생긴다면 어떻게 해야할까?

이럴 때 사용하는 것이 바로 eq() 이다.

특정 인수로 고정값을 넣고 싶은 상황이 생긴다면 eq() 안에 그 값을 넣어 사용하면 된다.

다시 예외를 발생시킨 코드로 돌아가서,

given(imageService.doAgain("foo", anyString())).willReturn(bar);

"foo"eq("foo") 로 바꾸고 다시 실행하면 예외 없이 정상적으로 작동하게 될 것이다.


그 외에도, ArgumentMatchers를 사용할 때 주의해야 할 사항들이 있다.

  • ArguementMatchers 를 반환 값으로 사용할 수 없다 - Stubbing에는 정확히 명시 된 값만 사용해야 한다.
  • Verification이나 stubbing의 목적 외에는 ArguementMatchers 를 사용할 수 없다.

그러면, 맨 처음에 예로 들었던 코드를 다시 살펴보자.

given(imageService.save(eq(userId), any(ImageResult.class)))
		.willReturn(imageResponse);

이제 이 코드를 보면 imageService.save()

  1. Exact value 인 userId 를 사용하기 위해 eq(userId) 를 사용하여 인수로 받고,
  2. 아무 ImageResult.class 를 받을 수 있도록 any(ImageResult.class) 사용하여 인수로 받는다.

라고 이해할 수 있다.


그 외의 기능들

ArgumentMatchers를 customizing 하여 사용할 수 있다.

public class ImageResultMatcher implements ArgumentMatcher<ImageResult> {

    private ImageResult left;

    // constructors

    @Override
    public boolean matches(ImageResult right) {
        ...
    }
}

위와 같은 방식으로 ArgumentMatcher 를 상속 받은 ImageResultMatcher 를 구현하여 더 정확한 값이 matching 될 수 있도록 만들 수 있다.


마무리


사실 위에서 설명한 기능들 외에도 더 많은 ArgumentMatchers 가 존재하고, 더 댜양한 활용 방법들도 존재한다. 기회가 된다면 다른 기능들도 사용해보고 정리해봐야겠다.

다른 기능들이 궁금하다면 밑에 있는 참고 링크를 확인하면 좋을 것 같다.



References

profile
깨부하는 개발자

1개의 댓글

comment-user-thumbnail
2023년 8월 10일

즐겁게 읽었습니다. 유용한 정보 감사합니다.

답글 달기