솔솔
[Test] Mockito 테스트 어노테이션 : @Mock, @MockBean, @Spy, @InjectMocks 이해하기 본문
[Test] Mockito 테스트 어노테이션 : @Mock, @MockBean, @Spy, @InjectMocks 이해하기
솔솔하네 2025. 3. 27. 23:33시스템 환경
Java 17
Spring boot 3.4.3
Gradle 8.1
테스트 프레임워크
Junit5
AssertJ
🍀 Mockito란?
Java 기반의 단위 테스트 프레임워크로, 실제 객체 대신 Mock 객체(가짜 객체)를 생성하여 테스트를 쉽게 작성하고 독립적으로 실행할 수 있도록 도와줌
Mockito framework site
Intro Why How More Who Links Training Why drink it? Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produc
site.mockito.org
🍀 Mockito를 사용하는 이유
테스트하려는 클래스가 외부 의존성을 가진 경우, 이 의존성을 실제로 호출하지 않고도 테스트할 수 있음.
예를 들어, 사용자가 DB에 데이터를 저장하는 기능을 테스트한다고 가정했을 때, 실제 DB 호출 대신 Mock 객체를 사용하여 해당 동작을 검증함
🍀 Mockito의 핵심 개념
Mock: 실제 객체를 대체하는 가짜 객체
Stubbing: 특정 입력에 대해 원하는 동작(반환 값 등)을 정의
when(mockObject.method()).thenReturn(value);
Verification: 특정 메서드가 호출되었는지, 호출 횟수를 검증
when(mockObject.method()).thenReturn(value);
🍀 Mockito 어노테이션 : @Mock와 @InjectMocks
@ExtendWith(MockitoExtension.class)
- Mockito를 사용해 Mockito의 @Mock 및 @InjectMocks가 동작할 수 있도록 설정
@Mock
- 실제 구현체 대신 Mock 객체를 생성
- 테스트 중 MailSendClient와 MailSendHistoryRepository에 대한 실제 구현이 아니라, Mockito가 제공하는 가짜 객체(Mock 객체)를 사용하여 외부 의존성을 제거함
- 특히 MailSendClient에서 실제로 메일을 보내는 기능이 구현되어있다면 테스트 할 때마다 실제로 메일을 보내는건 비효율적임으로 Mock 객체를 만들어서 테스트하여 메일 전송 기능을 검증(외부 시스템과의 의존성을 제거)
@InjectMocks
- MailService 인스턴스를 생성
- 이 클래스가 의존하는 객체(MailSendClient, MailSendHistoryRepository)를 @Mock으로 설정된 Mock 객체로 자동 주입
@ExtendWith(MockitoExtension.class)
class MailServiceTest {
@Mock
private MailSendClient mailSendClient;
@Mock
private MailSendHistoryRepository mailSendHistoryRepository;
@InjectMocks
private MailService mailService;
@DisplayName("메일 전송 테스트")
@Test
void sendMail() {
//given
// mailSendClient.sendEmail()메서드가 호출되었을 때 항상 true를 반환하도록 설정
when(mailSendClient.sendEmail(any(String.class), any(String.class), any(String.class), any(String.class)))
.thenReturn(true);
//when
boolean result = mailService.sendMail("", "", "", "");
//then
assertThat(result).isTrue();
// mailSendHistoryRepository.save() 메서드가 한 번 호출되었는지 검증
verify(mailSendHistoryRepository, times(1)).save(any(MailSendHistory.class));
}
}
🍀 Mockito 어노테이션 : @MockBean
* Spring Boot 3.4.0 이후로는 기존의 @MockBean 어노테이션이 더 이상 사용되지 않으며, 이를 대체하는 @MockitoBean 어노테이션이 도입됨.
@MockitoBean
- Spring Boot Test에서 제공하는 어노테이션
- Spring Application Context에 Mock 객체를 등록하는 역할
- 실제 빈(Bean)을 교체하거나 의존성을 대체하는 Mock 객체를 주입할 수 있어서 특정 서비스나 의존성의 실제 구현을 호출하지 않고 테스트 환경에서 필요한 동작만을 흉내 내는 Mock 객체를 활용할 수 있음
@WebMvcTest(controllers = ProductController.class)
class ProductControllerTest {
@Autowired
// MockMvc : 실제 서블릿 환경 없이도 HTTP 요청과 응답을 테스트할 수 있는 도구
protected MockMvc mockMvc;
@MockitoBean
// ProductService의 Mock 객체를 Spring Context에 등록하고, 테스트 환경에서 주입
protected ProductService productService;
@DisplayName("판매 상품을 조회한다.")
@Test
void getSellingProducts() throws Exception {
// given
List<ProductResponse> result = List.of();
// productService.getSellingProducts()가 호출되었을 때 빈 리스트를 반환하도록 Mock 객체의 동작을 지정
when(productService.getSellingProducts()).thenReturn(result);
// when // then
mockMvc.perform(
get("/api/v1/products/selling")
)
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("200"))
.andExpect(jsonPath("$.status").value("OK"))
.andExpect(jsonPath("$.message").value("OK"))
.andExpect(jsonPath("$.data").isArray());
}
}
🍀 Mockito 어노테이션 : @Spy
@ExtendWith(MockitoExtension.class)
- Mockito를 사용해 Mockito의 @Spy가 동작할 수 있도록 설정
@Spy
- 실제 객체를 생성하거나 기존 객체를 감싸서 부분적으로 Mocking을 할 때 사용
- 객체 전체를 Mock으로 대체하는 대신, 특정 메서드만 Mocking하면서 나머지는 실제 동작을 테스트할 수 있음
@ExtendWith(MockitoExtension.class)
class MailServiceTest {
@Spy
private MailSendClient mailSendClient; // 실제 객체 생성
@Mock
private MailSendHistoryRepository mailSendHistoryRepository;
@InjectMocks
private MailService mailService;
@DisplayName("메일 전송 테스트")
@Test
void sendMail() {
//given
// mailSendClient.sendEmail() 특정 메서드 Mocking
doReturn(true)
.when(mailSendClient)
.sendEmail(anyString(), anyString(), anyString(), anyString());
//when
boolean result = mailService.sendMail("", "", "", "");
//then
assertThat(result).isTrue();
verify(mailSendHistoryRepository, times(1)).save(any(MailSendHistory.class));
}
}
'나의보물들 > Test' 카테고리의 다른 글
[Test] Layered Architecture 구조적 이해와 단계별 테스트 작성법 (0) | 2025.03.25 |
---|---|
[Test/SpringBoot] Spring Boot 테스트 어노테이션 제대로 알기: @SpringBootTest vs @DataJpaTest (0) | 2025.03.20 |
[Junit] 반복되는 테스트 코드 줄이기: @ParameterizedTest로 여러 케이스 한 번에 실행하기 (0) | 2025.03.18 |