기록

[Mock] 스프링 환경에서 외부 시스템 테스트하기 본문

교육/강의

[Mock] 스프링 환경에서 외부 시스템 테스트하기

youngyin 2025. 2. 6. 00:00

1. Mock을 사용하는 상황과 사용하지 않는 상황

1.1 Mock을 사용하는 상황

외부 시스템과 연계되는 테스트에서 Mock 객체를 사용하면 외부 의존성을 제거하고 독립적으로 동작을 검증할 수 있습니다. 예를 들어, 이메일 전송, API 호출 등 외부 서비스에 의존하는 로직은 Mock 객체로 대체하여 테스트에서 외부 시스템의 영향을 배제해야 합니다.

1.2 Mock을 사용하지 않는 상황

내부 로직만 검증하는 테스트에서는 실제 객체를 사용하여 테스트를 수행하는 것이 적합합니다. 이 경우 실제 객체의 동작을 통해 로직의 정확성을 확인할 수 있습니다.

1.3 Classicist vs Mockist 논쟁

Mock 사용 여부와 관련된 논쟁으로 Classicist 접근법과 Mockist 접근법이 있습니다.

  • Classicist 접근법: 실제 객체를 사용하여 테스트를 작성합니다. 외부 시스템과의 인터페이스나 컨트롤러 단에서만 Mock 객체를 사용합니다. 이를 통해 테스트가 실제 동작에 더 가깝도록 유지합니다.
  • Mockist 접근법: 모든 의존성을 Mock 객체로 대체하여 테스트를 작성합니다. 독립적이고 빠른 테스트가 가능하지만, Mock 설정 및 유지보수가 복잡해질 수 있습니다.

2. Mockito의 주요 어노테이션

Mockito는 다음과 같은 어노테이션을 통해 다양한 테스트 시나리오를 지원합니다.

2.1 스프링 컨텍스트를 사용하지 않는 어노테이션

  • @Mock: Mock 객체를 생성하여, 해당 객체의 동작을 개발자가 원하는 대로 정의할 수 있도록 합니다.
    외부 의존성을 제거하고 특정 동작만을 테스트할 때 사용합니다. 객체를 완전히 Mock으로 처리하며, 실제 로직은 동작하지 않습니다.
  • @Spy: 실제 객체를 생성하면서도 필요에 따라 Mocking 동작을 추가할 수 있습니다.
    객체의 일부 메서드는 실제 동작을 테스트하고, 다른 메서드는 Mocking이 필요할 때 사용합니다. 실제 객체와 Mock 객체의 장점을 결합하여 유연하게 사용할 수 있습니다.
  • @InjectMocks: @Mock 또는 @Spy로 생성된 객체를 주입합니다.
    테스트 대상 객체에 의존성을 자동으로 주입하여 테스트를 간소화합니다. 의존성 주입이 필요한 객체를 Mocking된 의존성을 사용해 초기화합니다.
어노테이션 실제 객체 생성 여부 Mocking 가능 범위 사용 사례
@Mock X 모든 메서드 외부 의존성 제거, 독립적인 단위 테스트 수행
@Spy O 선택적 메서드 부분 Mocking으로 실제 동작과 조합된 테스트 수행
@InjectMocks N/A N/A 의존성이 많은 객체를 테스트할 때

2.2 스프링 컨텍스트를 사용하는 어노테이션

  • @MockBean: 스프링 컨텍스트에 Mock 객체를 등록하여 통합 테스트에 사용합니다.
    스프링 컨텍스트에 직접 등록되며, 다른 빈과의 의존성 관계가 유지됩니다. 외부 의존성을 제거한 상태에서 통합 테스트를 수행할 때 주로 사용됩니다.
  • @SpyBean: 실제 빈을 Spy로 래핑하여 부분적인 Mocking이 가능합니다.
    실제 빈의 일부 메서드는 원래의 동작을 유지하며, 필요한 메서드만 Mocking이 가능합니다. 실제 동작을 테스트하면서 일부 Mocking을 병행해야 하는 경우 유용합니다.
어노테이션 실제 객체 생성 여부 스프링 컨텍스트와의 연계 사용 사례
@MockBean X O 외부 의존성 제거, 스프링 기반 통합 테스트 수행
@SpyBean O O 실제 동작과 부분적인 Mocking 조합이 필요한 경우

3. BDD 스타일과 Mockito.when()

Mockito는 전통적인 방식의 when() 메서드와 BDD 스타일의 given() 메서드를 지원합니다. BDD 스타일은 테스트 코드의 가독성을 높이고, "Given-When-Then" 흐름을 명확히 표현할 수 있도록 돕습니다.

  • Mockito.when(): 전통적인 방식으로 특정 조건에서 Mock 객체의 동작을 정의합니다.
  • BDDMockito.given(): BDD 스타일로 작성된 테스트에서 사용됩니다.
when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser)); // Mockito
given(userRepository.findById(1L)).willReturn(Optional.of(mockUser)); //BDD

4. MailServiceTest 예제 분석

MailService는 이메일 전송과 같은 외부 서비스를 호출하는 책임을 담당합니다. 실제 테스트에서 외부 시스템과의 통신은 네트워크 상태, 서비스 가용성 등의 변수로 인해 테스트의 신뢰성을 떨어뜨릴 수 있습니다. 따라서 이러한 외부 의존성을 배제하고, 안정적인 테스트 환경을 구성하기 위해 Mock 객체를 사용하는 것이 적합합니다.

4.1 Mock 객체 생성과 Stubbing

Mock 객체를 사용하여 외부 의존성을 모방하며, 테스트하려는 동작에만 집중할 수 있도록 합니다. 이를 통해 예측 가능한 결과를 얻고 테스트의 신뢰성을 높입니다.

@Mock
private EmailClient emailClient;

@InjectMocks
private MailService mailService;

@Test
void testSendEmail() {
    // Stubbing
    given(emailClient.send("test@example.com", "Hello")).willReturn(true);

    // 실행
    boolean result = mailService.sendEmail("test@example.com", "Hello");

    // 검증
    assertTrue(result);
    verify(emailClient).send("test@example.com", "Hello");
}

 

 
 
Comments