레이어드 아키텍처의 단위 테스트 코드를 작성하다보면 Mock과 AssertJ를 사용하여 값을 검증하는 테스트 코드를 작성하게 된다. 이 때 테스트 코드 입문과정 시 헷갈리는 부분들을 정리하여 작성하게 되었다. 해당 테스트 코드는 BDD 스타일로 작성된 코드이다.
메서드의 호출 여부가 중요한 경우 - then()
- 입력값이 중요하지 않고 메서드의 호출 여부만 확인하고 싶을 때가 있다.
@Test
@DisplayName("해당 메서드가 실제로 호출 되었는지 확인할 때")
void callMethod() {
//given
given(mockService.process(anyString(), anyInt()))
.willReturn(mock(MockingString.class));
//when
MockingString mocked = mockService.process("Mocked", 42);
//then
then(mockService).should().process(anyString(), anyInt());
}
그럴 때는 값이 중요한게 아니기 때문에 matchers를 통해 인자의 타입을 정해주고, then구문 을 통해 해당 메서드가 호출 되었는지 되지 않았는지 확인할 수 있다.
⛔️ willReturn구문에는 matchers를 사용할 수 없기 때문에 객체만 Mocking 해주거나 실제 객체의 인스턴스를 넣어주어야 한다.
값 검증이 중요한 경우 - AssertJ
- 입력값이 중요하여 내가 예상한 객체 혹은 값이 반환이 잘 되는 것이 중요한 경우가 있다.
@Test
@DisplayName("내가 예상한 데이터가 정확히 반환되는지가 중요할 때")
void callMethod() {
//given
given(mockService.process("Mocked", 42))
.willReturn(new MockingString("Mocked", 42));
//when
MockingString mocked = mockService.process("Mocked", 42);
//then
assertThat(mocked).extracting(
MockingString::getNumber,
MockingString::getRequest
).containsExactly(42, "Mocked");
}
번외로 두 인자중에 하나의 인자에 any() matchers를 사용하고 하나의 인자에는 실제 값을 사용할 때에는 eq() matchers를 사용하여 실제 값을 감싸준다.
즉 일부 인자는 정확한 값이어야 하고, 일부 인자는 조건만 만족하면 되는 경우에 사용한다.
void callMethod() {
//given
given(mockService.process(anyString(), eq(42)))
.willReturn(new MockingString("Mocked", 42));
//when
MockingString mocked = mockService.process("Mocked", 42);
//then
assertThat(mocked).extracting(
MockingString::getNumber,
MockingString::getRequest
).containsExactly(42, "Mocked");
}
값 검증과 메서드의 호출 둘다 검증이 필요한 경우
- 값 검증과 해당 메서드의 호출 여부 둘 다 검증이 필요한 경우의 테스트가 있다.
void callMethod() {
//given
given(mockService.process("Mocked", 42))
.willReturn(new MockingString("Mocked", 42));
//when
MockingString mocked = mockService.process("Mocked", 42);
//then
assertThat(mocked).extracting(
MockingString::getNumber,
MockingString::getRequest
).containsExactly(42, "Mocked");
then(mockService).should().process(anyString(), anyInt());
}
- 값 검증에 대해서는 실제 값을 넣어주어야 한다.
- 그리고 메서드 호출 여부는 then 메서드를 통해 검증할 수 있다.
shouldHaveNoInteractions
목적 : 해당 Mock 객체에 대해 하나도 호출이 이루어지지 않았음을 검증하는 것.
예시 : 테스트 시작 후 특정 mock 객체가 전혀 사용되지 않아야 한다
then(mock).shouldHaveNoInteractions();
해당 호출은 mock에 대해 단 한건의 메서드 호출이 없었음을 증명하기 위해서 사용
shouldHaveNoMoreInteractions
목적 : 이미 검증한 메서드 호출 이외에 추가적인 호출이 없었음을 확인하는 것.
예시 : 여러 메서드의 호출을 검증한 후에 mock에 예기치 않은 호출이 없었는지 마지막으로 점검하고 싶을 때 사용한다.
void reportWhenKeyExists() {
// given
String key = "report:users:1"; // generateKey(userId)가 이 값을 반환한다고 가정
Duration ttl = Duration.ofDays(1);
given(template.opsForValue()).willReturn(operations);
given(template.hasKey(key)).willReturn(true);
given(operations.increment(key)).willReturn(2L);
// when
Long result = reportRedisRepository.report(1L, ttl);
// then
assertThat(result).isEqualTo(2L);
// key가 존재하므로, increment()만 호출되어야 함.
then(operations).should(times(1)).increment(key);
then(operations).should(never()).set(anyString(), any(String.class), any(Duration.class));
// template에서는 hasKey와 opsForValue()가 각각 한 번씩 호출되어야 함.
then(template).should(times(1)).hasKey(key);
then(template).should(times(1)).opsForValue();
// 추가 검증: 위에서 검증한 외에 다른 호출은 없어야 함.
then(operations).shouldHaveNoMoreInteractions();
then(template).shouldHaveNoMoreInteractions();
// 만약 어떤 mock은 전혀 사용되지 않아야 한다면,
// then(someOtherMock).shouldHaveNoInteractions();
}
정리
• AssertJ와 Mockito.verify() / then()은 서로 다른 검증 목적을 가지고 있다.
• 값 검증은 AssertJ를, 메서드 호출 검증은 Mockito.verify()/then() 를 사용해야 한다.
• 둘 다 필요할 경우, 두 가지 방법을 함께 사용하여 검증하는 게 좋다.
'프로젝트 이슈 및 몰랐던점 정리 > CommunityAPI' 카테고리의 다른 글
| [학습 포인트] 💡Redis SortedSet을 활용한 실시간 인기글 구현하기 (1) | 2025.03.30 |
|---|---|
| [학습 포인트] 💡Redis를 활용한 toggle(좋아요)기능 구현하기 (0) | 2025.03.30 |
| [학습 포인트]💡 MockMvc 컨트롤러 단위 테스트 시 인증정보 부재 (0) | 2025.03.30 |
| [학습 포인트] 💡Redis 단위 테스트 작성하기 feat.ValueOperations, Operations (0) | 2025.03.30 |
| [트러블 슈팅] ⚠️A component required a bean named 에러 원인 (0) | 2025.03.30 |