솔솔

[Test] Mockito 테스트 어노테이션 : @Mock, @MockBean, @Spy, @InjectMocks 이해하기 본문

나의보물들/Test

[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 객체(가짜 객체)를 생성하여 테스트를 쉽게 작성하고 독립적으로 실행할 수 있도록 도와줌

 

https://site.mockito.org/

 

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));
    }
}