[Spring] @Mock VS @Spy 정리

동긔·2024년 10월 25일

Spring

목록 보기
4/6

1. @Mock 이란?

@MockMockito에서 제공하는 어노테이션으로, 테스트 코드에서 의존성을 갖는 객체의 가짜(Mock) 객체를 만들어주는 데 사용됩니다. 실제 객체 대신 Mock 객체를 생성하고, 이 객체는 실제 로직을 실행하는 대신 미리 정의된 동작을 수행합니다. 이를 통해 외부 의존성을 제거하고 테스트 대상 코드에만 집중할 수 있습니다.

1.1 @Mock의 특징

  • 가짜 객체 생성: 실제 객체의 동작을 수행하지 않고, 가짜 동작을 정의하여 테스트할 수 있습니다.
  • 모든 메서드 기본값 반환: 기본적으로 모든 메서드는 아무 동작도 하지 않으며, null, 0, false 등의 기본 값을 반환합니다.
  • 동작 시뮬레이션: 특정 메서드 호출 시 반환할 값을 지정하여 동작을 시뮬레이션할 수 있습니다.

1.2 @Mock의 동작 방식

  • @Mock인터페이스 또는 클래스가짜(Mock) 객체를 만듭니다. 이 객체는 실제 구현체와는 다르며, 메서드를 호출했을 때 기본적으로 아무 동작도 하지 않고 기본값(null, 0, false 등)을 반환합니다.
  • 중요한 점@Mock은 객체의 구현체 없이도 사용할 수 있다는 것입니다. 인터페이스를 Mocking 할 수 있으며, 인터페이스의 메서드는 기본적으로 null을 반환합니다.
@Mock
private MyService myService; // 인터페이스도 가능

@Test
void testMock() {
    // getData() 호출 시 아무 동작도 하지 않고 null 반환
    assertNull(myService.getData());
}
  • 인터페이스 또는 클래스 모두에서 사용할 수 있습니다.
  • 모든 메서드가 기본값을 반환하며, 지정된 동작이 없으면 아무 동작도 하지 않습니다.
  • 기본값: null, 0, false 등 Java의 기본값이 반환됩니다.

@Mock 사용 예시 코드

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

class ServiceTest {

    @Mock
    private ExternalService externalService; // 가짜 객체 생성

    @Test
    void testService() {
        // Mockito로 Mock 객체 생성
        externalService = Mockito.mock(ExternalService.class);

        // getData() 메서드가 호출되면 "Mocked Data"를 반환하도록 설정
        when(externalService.getData()).thenReturn("Mocked Data");

        // 테스트 대상 서비스에 Mock 객체 주입
        MyService myService = new MyService(externalService);

        // Mock 동작 검증
        assertEquals("Mocked Data", myService.getDataFromService());
    }
}
  • @Mock 사용: ExternalService의 가짜 객체를 생성하여 실제 서비스 호출 없이 테스트할 수 있습니다.
  • Mockito.when(): 특정 메서드 호출 시 반환할 값을 미리 지정합니다.
  • assertEquals(): Mock 객체로 설정된 값이 올바르게 반환되는지 검증합니다.

2. @Spy 란?

@Spy실제 객체를 기반으로 부분적인 Mocking을 할 수 있는 어노테이션입니다. 즉, 스파이 객체는 실제 객체처럼 동작하되, 일부 메서드만 가짜 동작을 수행하도록 설정할 수 있습니다. 이 방식은 실제 객체의 로직을 대부분 유지하면서 필요한 부분만 Mocking할 수 있어 더 유연한 테스트를 지원합니다.

2.1 @Spy의 특징

  • 실제 객체 기반: 스파이 객체는 실제 객체와 동일하게 동작하며, 기본적으로 모든 메서드는 실제 로직을 수행합니다.
  • 부분적 Mocking: 특정 메서드만 Mocking하여 테스트할 수 있습니다.
  • 실제 로직 감시: 테스트 중에 실제 객체의 메서드 호출 여부나 결과를 확인할 수 있습니다.

2.2 @Spy의 동작 방식

  • @Spy구현체를 기반으로 동작합니다. 즉, @Spy실제 객체를 감시하는 것이기 때문에, 반드시 구현된 클래스가 필요합니다. 인터페이스가 아닌, 구체적인 구현체를 사용해야 합니다.
  • @Spy는 실제 객체의 동작을 대부분 유지하면서, 필요한 경우 일부 메서드를 Mocking하여 동작을 정의할 수 있습니다. 그렇기 때문에 실제 객체가 존재해야만 동작할 수 있습니다.
@Spy
private MyServiceImpl myService = new MyServiceImpl(); // 구현체 필요

@Test
void testSpy() {
    // 실제 메서드는 원래 동작을 수행
    assertEquals("Real Data", myService.getData());

    // 특정 메서드만 가짜로 설정 가능
    doReturn("Spied Data").when(myService).getData();
    assertEquals("Spied Data", myService.getData());
}
  • 구현체를 기반으로 동작하므로, 실제 클래스 인스턴스가 필요합니다.
  • 기본적으로 모든 메서드는 실제 구현된 동작을 수행하지만, 특정 메서드는 Mocking하여 가짜 동작을 설정할 수 있습니다.
  • 실제 객체의 일부 메서드만 가짜로 설정할 수 있으며, 나머지는 실제 동작을 그대로 수행합니다.

3. @Mock과 @Spy의 차이점

특징@Mock@Spy
객체 생성가짜(Mock) 객체를 생성하여 모든 메서드가 기본값을 반환실제 객체(구현체)를 기반으로 동작, 일부 메서드만 가짜로 설정
인터페이스 사용 가능 여부인터페이스와 클래스를 모두 Mocking 가능구현체만 사용 가능 (인터페이스 불가)
기본 동작모든 메서드는 아무 동작도 하지 않으며 기본값 반환실제 메서드는 원래 동작을 수행하며, 필요한 경우만 Mocking 가능
Mocking 대상모든 메서드가 Mocking 처리됨특정 메서드만 Mocking하고 나머지는 실제 동작
  • @Mock은 인터페이스든 클래스든 가짜 객체를 만들 수 있으며, 모든 메서드는 기본적으로 아무 동작도 하지 않거나 기본값을 반환합니다. 그래서 외부 의존성을 제거한 상태에서 완전히 가짜 객체로 테스트를 진행할 수 있습니다.
  • @Spy는 실제 구현체가 필요하며, 구현된 클래스의 인스턴스를 사용해야 합니다. 실제 객체를 사용하면서도 특정 메서드만 Mocking할 수 있습니다. 기본적으로는 객체의 모든 메서드가 실제 동작을 수행합니다.

4. @Mock과 @Spy를 이용하여 서비스 레이어 테스트

보통 @Mock@Spy 어노테이션은 서비스 레이어(Service Layer)에서 테스트를 진행할 때 많이 사용됩니다. 그 이유는 서비스 레이어가 비즈니스 로직을 처리하는 핵심 부분이며, 이 로직이 주로 외부 의존성(예: 데이터베이스, 외부 API, 리포지토리, 다른 서비스)을 가지고 있기 때문입니다. 이 의존성들을 Mocking 또는 Spying하여, 테스트 대상이 되는 핵심 비즈니스 로직만 집중해서 테스트할 수 있습니다.

4.1 서비스 레이어에서 @Mock 사용

서비스 레이어의 메서드들이 리포지토리외부 API와 같은 의존성에 의존하여 데이터를 가져오거나 조작하는 경우가 많습니다. 이런 외부 의존성을 Mock 처리함으로써 실제 호출을 하지 않고, 가짜 동작을 정의하여 비즈니스 로직을 검증할 수 있습니다.

@Mock을 서비스 레이어에서 사용한 예시 코드

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class) // MockitoExtension을 통해 Mockito 기능 활성화
class UserServiceTest {

    @Mock
    private UserRepository userRepository; // 외부 의존성 Mocking

    @InjectMocks
    private UserService userService; // 실제 테스트할 서비스 객체

    @Test
    void testFindUserById() {
        // 가짜 동작 정의
        when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "John")));

        // 실제 서비스 메서드 호출
        User result = userService.findUserById(1L);

        // 결과 검증
        assertEquals("John", result.getName());

        // userRepository의 findById() 메서드가 정확히 한 번 호출되었는지 검증
        verify(userRepository, times(1)).findById(1L);
    }
}
  • Mockito 확장 활성화: @ExtendWith(MockitoExtension.class)JUnit 5에서 Mockito 기능을 활성화하는 어노테이션입니다. 이를 통해 @Mock@InjectMocks 어노테이션이 정상적으로 동작하게 됩니다.
  • Mock 객체 초기화: @Mock으로 선언된 객체들은 MockitoExtension이 실행되면서 자동으로 Mock 객체로 초기화됩니다. @InjectMocks도 의존성 주입을 통해 자동으로 실제 객체에 Mock 객체를 주입해줍니다.
  • @Mock: UserRepository는 외부 의존성이므로, 실제 데이터베이스를 호출하지 않고 가짜 데이터를 반환하도록 설정했습니다.
  • @InjectMocks: 테스트하려는 서비스인 UserServiceMock된 리포지토리를 주입합니다.
  • 서비스 레이어에서는 이러한 외부 의존성을 Mocking하여, 비즈니스 로직이 올바르게 동작하는지에 집중합니다.

4.2 서비스 레이어에서 @Spy 사용

@Spy는 실제 객체의 일부 동작을 유지하면서, 특정 메서드만 Mocking해야 할 때 유용합니다. 예를 들어, 서비스 내 일부 메서드의 로직은 실제로 테스트하면서, 다른 부분은 Mocking하여 원하는 대로 제어할 수 있습니다.

@Spy를 서비스 레이어에서 사용한 예시 코드

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

class OrderServiceTest {

    @Spy
    private OrderService orderService = new OrderService(); // 실제 구현체를 사용

    @Mock
    private PaymentService paymentService; // 외부 의존성 Mocking

    @InjectMocks
    private OrderProcessingService orderProcessingService; // 실제 테스트할 서비스

    @Test
    void testProcessOrder() {
        // 특정 메서드만 가짜 동작을 설정
        doReturn(true).when(orderService).checkInventory(anyLong());

        // 결제 서비스 Mock 동작 설정
        when(paymentService.processPayment(any())).thenReturn(true);

        // 실제 서비스 메서드 호출
        boolean result = orderProcessingService.processOrder(123L);

        // 결과 검증
        assertTrue(result);

        // checkInventory 메서드가 호출되었는지 검증
        verify(orderService).checkInventory(123L);
    }
}
  • @Spy: OrderService는 실제 객체로 생성되며, 이 객체의 모든 메서드는 기본적으로 실제 동작을 수행합니다. 하지만 checkInventory 메서드만 가짜 동작으로 설정했습니다.
  • @Mock: PaymentService는 외부 의존성이므로 완전히 가짜 객체로 만들어 Mocking 처리했습니다.
  • 서비스 레이어에서는 실제 로직과 외부 의존성의 복합적인 상호작용이 많으므로, 부분적으로 Spying과 Mocking을 혼용할 수 있습니다.

5. 왜 서비스레이어에서 @Mock과 @Spy를 사용하는가?

5.1 서비스 레이어의 역할

  • 서비스 레이어는 비즈니스 로직을 포함하고 있으며, 리포지토리(데이터베이스), 외부 API, 다른 서비스와의 의존성을 처리하는 역할을 합니다.
  • 실제로 외부 시스템과 연동하는 작업은 시간이 많이 소요되거나 불안정할 수 있기 때문에, 이러한 외부 시스템 의존성을 제거하고 단위 테스트를 집중적으로 수행하는 것이 중요합니다.

5.2 @Mock을 사용해야 하는 이유

  • 외부 의존성 제거: DB나 외부 API 호출을 제거하고, 가짜 데이터를 사용함으로써 테스트의 신뢰성을 높입니다.
  • 테스트의 독립성 보장: 외부 시스템의 상태에 관계없이 비즈니스 로직만을 검증할 수 있습니다.
  • 빠른 테스트: 실제 DB 연결 없이 빠르게 테스트를 실행할 수 있습니다.

5.3 @Spy를 사용해야 하는 이유

  • 부분적인 로직 테스트: 서비스 로직의 일부는 실제로 테스트하고 싶지만, 특정 메서드는 Mocking하고 싶을 때 유용합니다.
  • 메서드 호출 감시: 실제 메서드 호출 여부를 확인하면서도 필요시 특정 메서드를 대체할 수 있습니다.

6. 심화 내용

6.1 @Mock과 @InjectMocks의 조합: 외부 의존성을 제거하여 서비스 로직만 검증

@Mock@InjectMocks 조합은 외부 의존성을 제거한 상태에서 서비스 로직을 검증하는 것이 주된 목적입니다.

  • @InjectMocks는 서비스 객체의 실제 메서드를 호출하지만, 의존성을 주입할 때 @Mock으로 설정된 가짜 객체를 주입합니다.
  • 즉, 서비스 로직을 테스트하면서, 외부 의존성(Repository)의 호출을 가짜로 설정하여 데이터베이스나 외부 시스템에 영향을 주지 않고 독립적으로 테스트할 수 있게 됩니다.
  • 이 방식은 서비스의 모든 메서드가 실제로 동작하기 때문에 서비스 로직 전체를 테스트하는 데 유리하며, @Mock은 주로 의존성을 대체하는 용도로 사용됩니다.

6.2 @Spy의 의도와 사용 목적: 실체 객체를 사용하되, 특정 메서드만 Mocking

@Spy는 실제 객체를 사용하는데, 특정 메서드만 가짜로 동작시키거나 감시하는 데 초점이 맞춰져 있습니다. 즉, @Spy는 객체의 전체 동작을 검증하기보다, 일부 동작을 Mocking하면서 특정 메서드가 실제로 호출되었는지, 의도된 대로 실행되는지를 검증하고자 할 때 사용합니다.

  • @Spy는 실제 객체를 기반으로 하기 때문에, 특정 메서드를 Mocking하여 원하는 값을 반환하거나, 때로는 예외를 던지게 설정할 수도 있습니다.
  • 부분적인 Mocking이 필요하거나, 테스트 중 특정 메서드가 호출되었는지 감시하고 싶을 때 주로 사용됩니다.

6.3 @Mock과 @Spy의 차이 정리

기능@Mock + @InjectMocks 조합@Spy
객체 생성 방식가짜 객체로 생성된 의존성을 주입하여 서비스 로직 검증실제 객체를 생성하여 기본적으로 모든 동작을 실제로 수행
주 목적외부 의존성을 제거하고 서비스 로직 자체만 검증일부 메서드를 Mocking하면서, 특정 메서드가 호출되는지 감시
Mocking 대상의존성(Repository) 등 외부 시스템과의 의존성을 Mocking특정 메서드만 Mocking하고 나머지는 실제 동작 수행
테스트의 초점서비스 로직 검증 (의존성 제거)메서드 호출 감시 또는 부분적 Mocking

6.4 실제 사용에서의 예시

  • @Mock + @InjectMocks: 서비스 로직의 정확성을 검증하고자 할 때 사용합니다. 이 조합은 서비스 레이어의 메서드가 주로 외부 시스템과 의존 관계가 있어도, 외부 시스템을 호출하지 않고 테스트할 수 있게 해줍니다.
  • @Spy: 서비스 또는 다른 레이어의 특정 객체를 감시하거나, 메서드 호출 여부 또는 메서드 결과를 부분적으로 Mocking할 필요가 있을 때 사용합니다. 특정 메서드만 가짜로 동작하도록 설정하면서, 다른 메서드의 실제 동작을 테스트하고 싶을 때 유리합니다.
profile
배우고 느낀점들을 기록합니다. 열정 넘치는 백엔드 개발자로 남고싶습니다.

0개의 댓글