Test Double이란?
- 객체를 mocking 하여 테스트 하는 방법들을 칭한다.
객체를 mocking해서 테스트하는 여러개의 방법들이 존재한다. 이 글은 외부 API를 stubbing해서 테스트하는 방법을 설명하는 글이여서 Test Dobule 에 관한 자세한 내용은 정리 잘되어있는 글의 링크(https://hudi.blog/test-double/)를 첨부한다.
Stubbing 이란?
- Stub은 특정 메서드나 함수 호출에 대한 기대 동작을 갖춘 객체로, 특정 상황에서 함수 호출 시 미리 정의된 값을 반환하거나 미리 정의된 동작을 수행합니다. Stub은 주로 특정 함수의 행동을 테스트하기 위해 사용되며, 실제 로직은 포함하지 않을 수 있습니다. 예를 들어, 데이터베이스나 외부 서비스 호출을 대신하여 미리 정의된 결과를 반환하는 등의 상황에서 사용할 수 있습니다.
- 보통적으로 외부 API(슬랙,메일 API, 결제 PG) 관련된 테스트를 할 때 많이 사용한다. 이유는, 외부 API 테스트 시 통신하는 시간 성능 등은 내가 제어할 수 없는 경우이며 테스트의 목적이 아니다. 외부 API 테스트는 보통 무엇을 전달했을때 무엇이 결과값으로 오고 어떻게 처리할지가 주 목적이다. 테스트의 독립성과 제어할 수 없는 테스트일 경우 Mockito를 사용하여 Stubbing 을 많이 사용한다.
Email을 보내는 외부 API 테스트
프로젝트를 진행하며 관리자에서 사용자들에 이메일을 전송하는 기능을 구현해야했다. 이메일을 전송하는 API에 대해 테스트 코드를 작성해야하는데 실제로 매번 보내기엔 시간/비용 등 테스트를 위한 불필요한 리소스 낭비가 많을거 같단 생각이 들었다. 그래서 Stubbing을 사용하여 테스트를 진행했다.
Service
@Service
@RequiredArgsConstructor
public class MailService {
private final MailSendClient mailSendClient;
private final MailSendHistoryRepository mailSendHistoryRepository;
public boolean sendMail(String fromEmail, String toEmail, String subject, String content) {
boolean result = mailSendClient.sendEmail(fromEmail, toEmail, subject, content);
if(result){
mailSendHistoryRepository.save(MailSendHistory.builder()
.fromEmail(fromEmail)
.toEmail(toEmail)
.subject(subject)
.content(content).build());
}
return result;
}
}
위 서비스 코드는 실질적으로 이메일 보내는 구현체는 mailSendClient가 담당한다. 메일 성공/실패시 응답값으로 Boolean값이 전달된다. 메일 보내기가 성공시 메일 내용이 저장되는 로직(mailSendHistoryRepository.save())을 테스트하고 싶다.
Test
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import test.doWork.api.client.MailSendClient;
import test.doWork.api.domain.history.mail.MailSendHistory;
import test.doWork.api.domain.history.mail.MailSendHistoryRepository;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RefactoredMailServiceTest {
@Mock
private MailSendClient mailSendClient;
@Mock
private MailSendHistoryRepository mailSendHistoryRepository;
@InjectMocks
private MailService mailService;
@Test
@DisplayName("메일 전송 테스트")
void refactoredSendMail(){
// given
when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
.thenReturn(true);
// when
boolean result = mailService.sendMail("","","","");
// then
Assertions.assertThat(result).isTrue();
Mockito.verify(mailSendHistoryRepository,times(1)).save(any(MailSendHistory.class));
}
}
- @ExtendWith(MockitoExtension.class) : Mocking(가짜 객체로 감싸는 행위) 를 사용하기위해 @ExtendWith(MockitoExtension.class)을 클래스 위에 붙혀 확장시켜줘야한다.
- @Mock: MailService클래스에서 MailSendClient와 MailSendHistoryRepository 클래스들은 필수 의존성임으로 @Mock을 사용해서 가짜 객체를 만들어 줘야한다.
- @IntjectMocks: @InjectMocks를 통해 @Mock으로 만든 가짜 객체들을 MailService에 주입해준다.
GIVEN
- when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))은 메일 전송 클라이언트의 sendEmail 메서드에 대한 스텁(Stub)을 정의했다.어떤 매개변수가 전달되어도 항상 true를 반환하도록 설정하고 있다.
THEN
- Assertions.assertThat(result).isTrue(); <- 이부분이 stubbing을 테스트한 코드다.
- Mockito.verify(mailSendHistoryRepository, times(1)).save(any(MailSendHistory.class))는 mailSendHistoryRepository Mock 객체의 save 메서드 호출이 한 번 되었는지를 검증하고 있습니다. 이 부분은 메일 서비스에서 메일이 전송된 후 메일 전송 기록을 저장하는지를 확인하기 위한 검증이다.
결론
- Test Double에 유래는 영화에서 배우들의 액션을 대신해주는 스턴트 더블에서 유래됬다. 즉, Test Double은 객체를 대신해서 만들어서 테스트를 도와주는 방법들로 보면된다. Stubbing은 Test Double방법중에 하나다. 간단하게 말하면 given에서 주어진 값이 함수를 호출했을때 내가 원하는 값을 넣는다고 보면된다. 그래서 제어할 수 없는 외부 API를 테스트할 때 많이 쓰인다. 보통 외부 API를 사용 후 어플리케이션에 맞는 추가적인 비즈니스 로직을 테스트할 때 외부 API를 Stubbing해서 많이 사용 후 추가 로직을 테스트한다.
Reference
'Java > Test' 카테고리의 다른 글
[JUnit] 공통 테스트 환경 구성 (0) | 2023.08.15 |
---|---|
MockMvc로 API 슬라이스 테스트 하기 (0) | 2023.08.09 |
JUnit을 사용하여 예제로 TDD 맛보기 (0) | 2023.08.02 |