단위 테스트를 진행하다 보면
단위 테스트는 타깃이 되는 객체만을 철저히 검증하는 테스트라고 볼 수가 있습니다. 그 때문에 최대한 의존하고 있는 객체에 영향을 받지 않아야 합니다. 그래서 Mock 객체를 활용하여 의존하고 있는 객체의 행동에 대해서 의도대로 정의를 해주고 단위 테스트를 진행하는 경우가 있습니다.
이때, 특정 행동의 결과로서 메소드에 인자로 받기 전에 해당 값을 중간에 값을 받아 검증하고 싶을 때가 있습니다.
그것을 수행해주는 것이 바로 ArgumentCeptor 입니다.
간단한 예시와 함께 코드를 살펴보도록 하겠습니다.
예시 객체
public class Machine {
private final TwoInserter twoInserter;
public Machine(TwoInserter twoInserter) {
this.twoInserter = twoInserter;
}
public void addNumbers(List<Integer> numbers) {
numbers.add(1);
twoInserter.insertNumber(numbers);
}
}
public class TwoInserter {
public void insertNumber(List<Integer> numbers) {
numbers.add(2);
}
}
이렇게 의존 관계를 가진 객체의 메소드를 테스트하는 케이스가 있다고 가정합시다.
단위 테스트에서는 객체의 복잡한 의존관계에서 벗어나 하나의 객체만 집중적으로 테스트하게 됩니다.
때문에 Mockito 라이브러리의 @Mock과 같은 어노테이션을 사용하기도 합니다.
이번 케이스에서 Machine을 테스트하게 된다면 TwoInserter를 Mocking을 하여 단위 테스트를 진행할 수 있습니다.
하지만 단위테스트 도중 이런 생각이 들지도 모릅니다.
public void addNumbers(List<Integer> numbers) {
numbers.add(1);
twoInserter.insertNumber(numbers);
}
Mock 객체 이전의 행위는 어떻게 테스트하면 좋을까?
만약 반환없이 void로 메서드 타입이 정해진다면 더욱 테스트하기 까다로울지도 모릅니다.
만약 twoInserter의 insertNumber에 들어오는 인자를 확인할 수 있다면 numbers.add(0) 라는 코드를 테스트할 수 있지않을까? 라는 호기심이 들었습니다.
해당 기능을 해주는 것이 오늘의 주제인 ArgumentCaptor를 이용한 메소드에 들어가는 인자값 검증입니다.
ArgumentCeptor : 메소드에 들어가는 인자값 검증
ArgumentCatptor의 기능을 활용하면 메소드에 들어가는 인자들을 중간에 가로채어 테스트를 진행할 수가 있습니다.
먼저, 테스트코드에서 해당 기능을 사용하기 위해서는 다음과 같은 정의를 해주어야합니다.
@Captor
ArgumentCaptor<List<Integer>> listArgumentCaptor;
# ArgumentCaptor<$T> argumentCaptor;
# T는 가져오고 싶은 인자의 타입형
@Mock이나 @InjectMock을 사용하는 것처럼 테스트 코드에 다음과 같은 어노테이션을 가진 ArgumentCaptor를 추가해주면 됩니다. 이 때, 메소드에서 받는 인자가 List<Integer> 형이기 때문에 저희는 Argument<List<Integer>>를 사용하였습니다.
그 다음은 중간에 가져올 인자를 받는 메소드를 지정하는 부분입니다.
verify(twoInserter).insertNumber(listArgumentCaptor.capture());
#verify($Target되는 인스턴스).$Target메서드(정의된ArgumentCaptor.capture());
다음과 같이 twoInserter라는 인스턴스의 insertNumber() 메소드에 들어가는 인자를 받아주기 위해서 선언된 ArgumentCaptor 인스턴스 및 capture() 메서드를 넣어주었습니다.
List<Integer> argumentNumbers = listArgumentCaptor.getValue();
# $T 받아온 인자 = 선언된 ArgumentCaptor 인스턴스.getValue();
이후에는 다음과 같이 argumentCaptor의 메서드 getValue()를 활용하여 메서드에 전달된 인자를 값으로 할당할 수가 있습니다.
이를 검증하기 위한 전체 테스트 코드는 다음과 같습니다.
@Captor
ArgumentCaptor<List<Integer>> listArgumentCaptor;
@Test
void test() {
List<Integer> numbers = new LinkedList<>();
machine.addNumbers(numbers);
verify(twoInserter).insertNumber(listArgumentCaptor.capture());
List<Integer> argumentNumbers = listArgumentCaptor.getValue();
assertEquals(1, argumentNumbers.get(0));
}
메서드에 두개 이상의 인자를 받고 싶을 때는
메서드에는 하나의 인자가 아닌 여러 개의 인자가 전달될 수도 있습니다.
그럴 때는 메서드 인자만큼 ArgumentCaptor를 추가를 해주어야합니다.
@Captor
ArguemntCaptor<T> firstArgumentCaptor;
@Captor
ArgumentCaptor<T> secondArgumentCaptor;
verify(targetInstance).targetMethod(firstArgumentCaptor.capture(), secondArgumentCaptor.capture());