Spring REST Docs를 통한 API 문서화를 위해 Mock Test를 진행하면서 Mockito의 여러 기능들을 알아보았고, 그 중에서도 테스트에 처음 사용해봤던 ArgumentMatchers
에 대해 알아보고 이해한 것들을 여기에 적어보도록 하겠다.
Mock test를 진행하면서 필요한 객체를 mocking하고, 그 객체의 특정 메소드가 실행될 때, 어떤 값을 반환하는지 설정하는 경우가 있다.
현재 내가 진행 중인 프로젝트의 테스트 코드의 일부분을 예로 들면,
given(imageService.save(eq(userId), any(ImageResult.class)))
.willReturn(imageResponse);
위의 코드에서 imageService
는 Spring Boot의 @MockBean
을 사용한 service 객체이고,
imageService
의 .save()
라는 method가 실행될 시, imageResponse
를 반환할 것이다라는 것을 나타내고 있다.
어느 정도 예상했겠지만, ArgumentMatchers
란 imageService.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()
는
userId
를 사용하기 위해 eq(userId)
를 사용하여 인수로 받고, 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
즐겁게 읽었습니다. 유용한 정보 감사합니다.