Cuckoo의 ArgumentCaptor를 소개합니다
원티드의 iOS 앱 프로젝트에서 사용하고 있는 모킹 프레임워크 Cuckoo에 포함된 ArgumentCaptor 가 무엇이고, 어떻게 사용하는지 간단하게 알아보도록 하겠습니다.
이게 뭔가요?
ArgumentCaptor
는 Mock 객체에 입력되는 파라미터를 가로채서 직접 무언가를 할 수 있게 도와주는 객체입니다. verify(_:)
메소드를 통해 검증을 진행할 때 메소드의 파라미터로 이 ArgumentCaptor
객체를 넣어주게 되면 해당 메소드의 파라미터로 들어오는 값을 모아주게 됩니다.
Cuckoo의 Github Repo에 소개된 예제 코드 를 보면 다음과 같습니다. Mock 객체의 특정 프로퍼티에 들어오는 값을 ArgumentCaptor
객체로 잡아내어 어떤 값들이 들어오는지 확인하는 코드입니다.
mock.readWriteProperty = 10
mock.readWriteProperty = 20
mock.readWriteProperty = 30
let argumentCaptor = ArgumentCaptor<Int>()
verify(mock, times(3)).readWriteProperty.set(argumentCaptor.capture())
argumentCaptor.value // Returns 30
argumentCaptor.allValues // Returns [10, 20, 30]
그냥 Matcher 만 써도 되는거 아닌가요? 🤔
기존 Matcher 와 다른 점이 있다면, 이 ArgumentCaptor
는 Equatable
이 “불완전하게 구현”되어 있는 상태에서 파라미터 객체의 일부분만을 비교해야 할 때 쓰기가 좋습니다. 예를 한번 들어볼게요.
enum Response: Equatable {
case content(Community.Post, UIImage?)
case shareSheet(URL)
case commentWriter(CommentWriter)
static func == (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.content(let lp, _), .content(let rp, _)):
return lp.id == rp.id
case (.shareSheet, .shareSheet), (.commentWriter, .commentWriter):
return true
default:
return false
}
}
}
// Post의 ID만 비교하기 때문에 Post의 다른 요소를 확인할 수 없다
verify(presenter).present(.content(post, nil))
위와 상황에서 Post 객체의 내부 프로퍼티를 비교해야 할 일이 생긴다면, Custom Matcher를 구현해도 되겠지만 ArgumentCaptor
를 사용해서 검증을 진행할 수도 있습니다.
Mock 객체가 받은 파라미터를 ArgumentCaptor
가 가로채서 가지고 있으니 우리는 이걸 가지고 비교를 하면 되겠죠. 실제 구현된 테스트 코드를 예로 들어 확인해 볼게요.
context("로그인 된 사용자인 경우") {
it("댓글 작성 영역에 유저 관련 정보가 함께 출력되어야 한다.") {
sut.process(Community.Read.Request.OnLoad())
let argumentCaptor = ArgumentCaptor<Response>()
verify(presenter, atLeastOnce()).present(argumentCaptor.capture())
// OnLoad 의 경우 presenter.present(_:) 함수가 여러번 불릴 수 있기 때문에
// filter(_:) 를 사용해 원하는 Response만 추출해 냅니다.
let response = argumentCaptor.allValues.filter {
if case .commentWriter = $0 { return true }
else { return false }
}.first
// 내부 파라미터를 비교하기 위해 guard case 문을 사용합니다
guard case .commentWriter(let param) = response else {
assertionFailure()
return
}
expect(param.thumbnail).to(equal(sampleThumbnailURL))
}
}
결론
ArgumentCaptor
는 테스트 코드를 좀 더 자유롭게 쓸 수 있게 도와주며, 기존 코드가 이미 존재하는 상황에서 테스트를 작성할 때도 도움이 됩니다. 개인적으로는 Clean Swift의 Presenter에 넘어가는 Response 값을 비교할 때 주로 사용했던 것 같네요. 테스트 코드 작성하실 때 조금이나마 도움이 되셨으면 좋겠습니다 😄
참고 자료
GitHub - Brightify/Cuckoo: Boilerplate-free mocking framework for Swift!