목차
1. ArgumentCaptor란?
ArgumentCaptor는 Mockito에서 메소드에 전달된 Parameters를 캡처하는 기능입니다.
Mock 객체에 전달된 Parameters를 캡처하여 검증을 도와줍니다.
Mockito 라이브러리에 포함되어 있으며, 행위를 검증하는 테스트 코드에서 사용 할 수 있습니다.
기본적인 테스트 방법은 아래와 같습니다.
- 특정 타입의 ArgumentCaptor를 생성합니다.
- 이 캡처를 사용하여 인자를 캡처하도록 Mockito에 지시합니다.
- 테스트 코드를 실행합니다.
- 캡처된 인자를 검사합니다.
위에서 말씀드렸다시피
ArgumentCaptor는 행위 검증 테스트에서 사용됩니다.
행위 검증 테스트는 무엇인지 알아 봅시다.
2. 상태(state) vs 행위(behavior) 검증 테스트
상태 검증은 메서드의 내부 구현보다 결과에 초점을 맞춥니다.
코드가 어떻게 동작하는지가 아니라 무엇을 달성했는지를 검증하므로, 구현이 변경되어도 테스트코드는 유지 될 수 있습니다.
행위 검증은 메서드 호출 순서, 횟수 등 구현 세부사항을 검증합니다.
즉, 상태 검증과 달리 구현이 변경 되었을 때 테스트 코드도 변경해줘야 할 가능성이 있습니다.
행위 검증은 상호작용 검증이라고도 합니다.
아래는 상태 검증과 행위 검증의 예시입니다.
// 상태 검증
User savedUser = userService.createUser("name", "email");
assertNotNull(savedUser.getId());
assertEquals("name", savedUser.getName());
// 행위 검증
verify(userRepository).save(any(User.class));
대개 유닛테스트나 통합 테스트에서 행위 검증 보다 상태 검증을 권장합니다.
최종 결과물(상태)를 검증하기 때문에 보다 정확한 동작을 테스트 할 수 있기 때문입니다.
적은 의존성, 유지보수성, 실제 응답 데이터 검증 등의 특징이 이를 뒷바쳐줍니다.
3. 그럼에도 불구하고 행위 검증 테스트?
그럼에도 불구하고 행위 검증 테스트를 해야할 케이스가 있습니다.
대상 소프트웨어가 외부 의존성을 가지고 있을 때 입니다.
- 이메일 전송
- 메시지 발행
- 외부 API 호출
이를 포함한 비즈니스 로직을 테스트 할 경우에는
행위를 검증하는 것이 적절해 보입니다.
더불어 ArgumentCaptor 컨셉을 활용하면 얕은 수준으로 상태 검증을 적용 할 수 있습니다.
Parameter 체크 방식이기 때문에 완벽한 상태 검증이라고는 보기 어렵겠습니다.
4. ArgumentCaptor 사용법
아래와 같은 서비스 코드가 있습니다.
@Service
class MemberService(
private val customMailSender: CustomMailSender
) {
fun sendVerificationCodeToEmail(mailAddress: String) {
if(!ValidationUtil.isValidEmail(mailAddress)) {
throw CustomRuntimeException(HttpStatus.BAD_REQUEST, "Invalid email address")
}
val code = GeneratorUtil.generateRandomNumber(6)
val args = mapOf(
"##mailAddress##" to mailAddress,
"##code##" to code
)
// send mail
customMailSender.sendMimeMessage("Code for sign up", mailAddress, args)
}
}
회원가입을 위한 랜덤 코드를 생성하여 메일을 보내는 코드입니다.
위 기능을 검증하는 테스트 코드 작성해봅시다.
@ExtendWith(MockitoExtension::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MemberServiceTest(
@Mock
val customMailSender: CustomMailSender
) {
private lateinit var memberService: MemberService
@BeforeAll
fun setup() {
memberService = MemberService(customMailSender)
}
@Test
fun `send verifiy code mail test`() {
val to = "abc@gmail.com"
memberService.sendVerificationCodeToEmail(to)
// inti Captor
val stringCaptor = argumentCaptor<String>()
val mapCaptor = argumentCaptor<Map<String, String>>()
// verifiy call sendMimeMessage once
verify(customMailSender, times(1)).sendMimeMessage(
stringCaptor.capture(),
stringCaptor.capture(),
mapCaptor.capture()
)
// check parameters
Assertions.assertEquals("Code for sign up", strValues[0])
Assertions.assertEquals("abc@gmail.com", strValues[1])
}
}
위와 같이 argumentCaptor를 사용하여
memberService.sendVerificationCodeToEmail 함수의 행위를 테스트 해볼 수 있습니다.
Captor를 사용하여 parameter까지 검증에 활용 해 볼 수 있구요.
상태를 검증하는 것과 비슷해보입니다.
5. Kotlin 코드 작성 시, 이슈
Java 진영의 라이브러리인 mockito내에서 null을 리턴하는 코드가 있기 때문에
Kotlin으로 작성된 테스트 코드와 Platform type(kotlin의 타입)간에 충돌이 발생할 수 있습니다.
즉, Null이 허용되지 않은 테스트코드가 NPE를 발생 시킬 가능성이 있는 것이죠.
그렇기 때문에 kotlin + springboot 에서 argumentCaptor 사용 시, 반드시 아래 의존성을 추가해줘야합니다.
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.mockk:mockk:1.10.4")
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") // 추가됨
'개발 이야기 > Springboot' 카테고리의 다른 글
JUnit 5에서 조건부 @Disabled 적용하기 (0) | 2025.04.20 |
---|---|
httpclient thread limit exceeded replacing blocked worker 에러 (1) | 2025.04.17 |
TestContainers에서 Can't get Docker image: RemoteDockerImage에러 발생 시 (0) | 2025.02.02 |
Springboot App 시작, 종료시 로직 실행 (0) | 2024.08.31 |
Springboot 에서 Gzip으로 압축(with Kotlin) (0) | 2024.07.06 |