이메일을 전송하는 비즈니스 로직의 테스트코드를 작성해야한다고 해보자
어떻게 작성해야할까?
'벤더의 SMTP를 통하여 클라이언트로부터 받은 정보를 데이터로 만들어 이메일을 전송한다'라는 로직을 수행해야하는데 일반적으로 테스트코드를 작성한다면 실제 이메일이 테스트코드를 실행할때마다 날라가게된다. 만약 회원가입시에 이메일을 통해 인증번호를 받고 인증번호를 서버와 일치하는지 확인하고.. 이런테스트 코드를 작성하려면 너무 많은 고민을 해야하고 시간과 자원을 낭비하게 될 수 있다.
"만약 이메일을 전송한셈 치면어떨까?" 라는 생각으로 사용하는 테스트 라이브러리가 Mock이다.
Mock은 "가짜"라는 의미로 테스트 코드에서 사용하고자하는 컴포넌트를 가짜 컴포넌트 대상으로 만들어 해당 컴포넌트에 대한 반환값 또는 동작 기능을 테스트코드 내에서 임의로 설정할 수 있다.
이러한 편리한 특징을 가지고있을 뿐 아니라 Mock을 사용하면 실제 개발 코드에서는 스프링과 같은 프레임워크에 의존하더라도 테스트코드에서는 독립성을 갖도록 하여 단위 테스트를 원활하게 수행할 수 있도록 도와준다.
만약 스프링 프레임워크를 사용하여 테스트 코드를 짠다면 테스트를 위한 의존성 설정은 아래의 코드와 같을 것이다.
@SpringBootTest
class MailServiceTestBySpring {
@Autowired
MailSendClient mailSendClient;
@Autowired
MailSendHistoryRepository mailSendHistoryRepository;
@Autowired
MailService mailService;
@Test
@DisplayName("test")
void test() {
// given
assertThatThrownBy(() -> mailSendClient.sendEmail("", "", "", "")).isInstanceOf(IllegalArgumentException.class);
//when
//then }
}
@SpringBootTest
를 사용하여 컴포넌트에 의존성을 불러올 준비를 하고,@Autowired
를 통해 해당 컴포넌트에 의존성을 부여해주어야한다.
이 과정에서 자연스레 단위 테스트가 어려워지게 된다.
이제 Mock을 어떻게 사용하는지 보자
class MailServiceTest {
@Test
@DisplayName("메일 전송 테스트")
void sendMail() {
// given
MailSendClient mailSendClient = mock(MailSendClient.class);
MailSendHistoryRepository mailSendHistoryRepository = mock(MailSendHistoryRepository.class);
MailService mailService = new MailService(mailSendClient, mailSendHistoryRepository);
when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
.thenReturn(true);
//when
boolean result = mailService.sendMail("", "", "", "");
//then
assertThat(result).isTrue();
}
given
절에서 각각의 컴포넌트에 mock
메서드를 통하여 가짜 의존성(?)을 주입해주는 것을 확인할 수 있다
MailSendClient mailSendClient = mock(MailSendClient.class);
mailSendClient
를 선언해줌과 동시에 실제 사용하는 객체인 MailSendClient.class
의존성을 부여해주지만 해당 객체를 임의로 선언해준다.
when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
.thenReturn(true);
Mock객체를 사용하기위해서는 해당 객체가 개발과정에서 정의된 A라는 메서드의 동작을 했을때, B라는 동작이 나온다는 것을 임의로 정의해주어야한다.
정의 해줄때는 when메서드를 사용하여 정의해주고 그에 대한 반환값으로 thenReturn
등의 메서드를 체이닝 방식으로 설정해준다.
anyString()
은 Mock에서 제공해주는 편의메서드로 어떤 문자열이든 들어온다는 의미를 갖는다.
Mock선언을 어노테이션으로 편리하게하기
테스트 메서드 내부에서 Mock객체를 하나씩 선언해주는 것은 가독성이 떨어질 뿐만아니라 복잡하다.
Mock객체의 주입을 스프링의 @Autowired와 유사하게 할 수 있는 어노테이션을 제공해준다.
@Mock
MailSendClient mailSendClient;
@Mock
MailSendHistoryRepository mailSendHistoryRepository;
@InjectMocks
MailService mailService;
@InjectMocks
은 실제로 내가 테스트하고자하는 컴포넌트에 붙혀주면된다.
전체 코드를 살펴보면 아래와 같은데 무언가 어색한 부분이 있다.
@ExtendWith(MockitoExtension.class)
class MailServiceTest {
@Mock
MailSendClient mailSendClient;
@Mock
MailSendHistoryRepository mailSendHistoryRepository;
@InjectMocks
MailService mailService;
@Test
@DisplayName("메일 전송 테스트")
void sendMail() {
// given
when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
.thenReturn(true);
//when
boolean result = mailService.sendMail("", "", "", "");
//then
assertThat(result).isTrue();
verify(mailSendHistoryRepository, times(1))
.save(any(MailSendHistory.class));
}
바로 //given, //when, //then을 사용하여 테스트 역할의 구분을 명시하고 있는데 Mock을 사용하니 given절이 없고 given절안에 when
이라는 메서드가 사용되고 있어 혼란을 야기한다.
그런 점을 보완하기위해 BDDMockito라는 기능을 제공하여 해당 경계를 명확하게 나타내도록하여 가독성을 높혀준다. when메서드가 given으로 바뀌고, thenReturn부분이 willReturn으로 바뀌는 등의 문법 차이가 있으니 주의하자.
BDDMockito.given(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
.willReturn(true);
'테스트' 카테고리의 다른 글
@Mock과 @InjectMock은 언제 사용하나요? (0) | 2024.09.01 |
---|
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!