Java의 대표적인 Mock 프레임워크인 Mockito에서 제공하는 핵심 기능들에 대한 학습테스트 코드를 작성합니다.
우선 테스트를 위해 목킹 대상이 될 협력 클래스(Collaborator)와 협력에 사용할 메시지 클래스(Message)를 정의합니다.
// 협력 클래스
public class Collaborator {
public int supply() {
return 0;
}
public void consume(Message message) {
System.out.printf("message id = %s, value = %d", message.getId(), message.getValue());
}
}
// 메시지
@RequiredArgsConstructor
@EqualsAndHashCode
@Getter
public class Message {
private final String id;
private final int value;
}
그리고 학습 테스트 코드를 단순화 하기 위해 Mockito 클래스를 정적으로 import 하고, Collaborator 및 Captor 객체를 어노테이션을 통해 생성하도록 하겠습니다.
import static org.mockito.Mockito.*;
public class MockTest extends MockitoBaseTest {
@Mock
Collaborator collaborator;
@Captor
ArgumentCaptor<Message> argumentCaptor;
...
Mockito에서 제공하는 어노테이션 기반 목 생성 기능을 사용하기 위해서는 아래와 같은 설정이 필요한데 역시 학습 테스트 코드를 단순히 하기 위해 Super 클래스로 분리해서 설정은 수행하도록 하겠습니다. 이제 주요 API 들을 학습하는 테스트 코드를 작성할 준비가 되었습니다.
public class MockitoBaseTest {
private AutoCloseable autoCloseable;
@BeforeEach
void beforeEach() {
autoCloseable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void afterEach() throws Exception {
autoCloseable.close();
}
}
목킹한 객체의 특정 메서드를 호출 할 때, 미리 정의한 특정한 값(또는 예외)을 반환하도록 설정할 수 있습니다.
목 객체가 특정한 값을 반환하도록 설정합니다.
@Test
void mock_returns_specific_predefined_value() {
// Given
int fixed = collaborator.supply();
Assertions.assertEquals(fixed, collaborator.supply());
when(collaborator.supply()).thenReturn(fixed + 10);
// When
int actual = collaborator.supply();
// Then
Assertions.assertEquals(fixed + 10, actual);
}
목 객체가 특정한 예외를 반환하도록 설정합니다.
@Test
void mock_throws_specific_predefined_exception() {
// Given
when(collaborator.supply()).thenThrow(RuntimeException.class);
// Expected
Assertions.assertThrows(RuntimeException.class, collaborator::supply);
}
목 객체가 람다식에 의한 계산 결과로서 특정 값을 반환하도록 설정할 수 있습니다.
@Test
void mock_returns_value_through_lambda_expression() {
// Given
when(collaborator.supply()).thenAnswer(invocation -> {
// Do something
return 123;
});
// When
int actual = collaborator.supply();
// Then
Assertions.assertEquals(123, actual);
}
목 객체의 특정 메서드가 연속적으로 호출 될 때 각각의 반환 값(또는 예외)를 설정할 수 있습니다.
@Test
void mock_simulates_variety_stub_pattern() {
// Given
when(collaborator.supply())
.thenThrow(RuntimeException.class)
.thenReturn(10);
// Expected
Assertions.assertThrows(RuntimeException.class, collaborator::supply);
Assertions.assertEquals(10, collaborator.supply());
}
목킹한 객체의 특정 메서드 호출 횟수, 호출 파라미터 값 등을 캡처하여 검증할 수 있습니다.
목 객체의 특정 메서드를 호출할 때 전달된 파라미터 타입을 캡처하여 검증할 수 있습니다.
@Test
void mock_capture_output_type() {
// When
collaborator.consume(new Message("A", 1));
// Then
verify(collaborator, times(1)).consume(any(Message.class));
}
목 객체의 특정 메서드를 호출할 때 전달된 파라미터 값을 캡처하여 검증(equals 메서드 사용)할 수 있습니다.
@Test
void mock_capture_output_message() {
// When
Message message = new Message("A", 1);
collaborator.consume(message);
// Then
verify(collaborator, times(1)).consume(message);
}
목 객체의 특정 메서드가 연속적으로 호출될 때 전달된 파라미터 값 목록을 캡처하여 검증(equals 메서드 사용)할 수 있습니다.
@Test
void mock_capture_output_message_list() {
// When
Message message1 = new Message("A", 1);
Message message2 = new Message("B", 2);
collaborator.consume(message1);
collaborator.consume(message2);
// Then
verify(collaborator, times(2)).consume(argumentCaptor.capture());
List<Message> allOutputs = argumentCaptor.getAllValues();
Assertions.assertEquals(allOutputs.get(0), message1);
Assertions.assertEquals(allOutputs.get(1), message2);
}
목 객체의 특정 메서드가 특정 파라미터와 함께 호출되었는지 제한시간을 가지고 일정 시간 반복 검증할 수 있습니다.
@Test
void mock_verify_with_timeout() {
// When
Message message = new Message("A", 1);
Executor delayedExecutor = CompletableFuture.delayedExecutor(1000, TimeUnit.MILLISECONDS);
delayedExecutor.execute(() -> collaborator.consume(message));
// Then
verify(collaborator, timeout(10000).times(1)).consume(message);
}