본문 바로가기

개발공부/SPRING

[Mockito] @Mock @ InjectMocks @MockBean @Spy @SpyBean 비교

테스트 코드를 처음 접할 때 @Mock, @InjectMocks, @MockBean, @Spy, @SpyBean 어노테이션이 너무나도 비슷해서 제일 헷깔렸던 부분이었습니다.

그래서 다시 한 번 정리를 해야겠다고 생각했습니다.

Mockito를 설명하기 이전 Test Double이 무엇인지 먼저 알아보겠습니다.

 

Test Double (Mockito)


테스트를 진행하기 어려운 경우 이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체를 의미합니다.

예를 들어 우리가 데이터베이스로부터 조회한 값을 연산하는 로직을 구현했다고 가정하면,

해당 로직을 테스트 하기 위해선 항상 데이터베이스의 영향을 받을 것이고,

이는 데이터베이스의 상태에 따라 다른 결과를 유발할 수도 있습니다. 

 

이렇게 테스트 하려는 객체와 연관 된 객체를 사용하기가 어렵고 모호할 때 대신해줄 수 있는 객체를 테스트 더블이라고 합니다.

Java 진영에서는 대표적으로 Mockito가 있습니다.

 

Mockito의 어노테이션 종류


아래의 어노테이션들은 이름도 비슷하지만, 쓰임새까지 비슷한 것을 알 수 있습니다.

@Mock
@MockBean
@Spy
@SpyBean
@InjectMocks

 

@Mock

(@ExtendWith(MockitoExtension.class) 사용)

테스트 코드에서 행위 조작이 가능한 형태만 같은 껍데기 객체를 생성합니다.

@ExtendWith(MockitoExtension.class)를 사용해야만 테스트 시작 전 @Mock 어노테이션을 감지해서 Mock 객체를 주입합니다.

 

@InjectMocks

(@ExtendWith(MockitoExtension.class) 사용)

해당 클래스가 필요한 의존성과 맞는 Mock 객체들을 감지하여 해당 클래스의 객체가 만들어질 때 사용하여 객체를 만들고 해당 변수에 객체를 주입하게 됩니다.

 

@MockBean

(@SpringBootTest, @WebMvcTest 사용)

Spring ApplicationContext에 Mock 객체를 빈으로 등록하는데 사용되는 어노테이션입니다.

즉, Spring 영역에 있는 어노테이션이기 때문에 Spring Context를 실행해야 합니다.

그렇기에 @Autowired처럼 스프링이 제공하는 의존성 해결방법으로 해당 Mock 객체를 찾을 수 있어,

주로 @SpringBootTest@WebMvcTest와 같이 사용합니다.

실제로 테스트할 객체는 @Autowired로 주입합니다.

 

@Spy

(@ExtendWith(MockitoExtension.class) 사용)

spy 객체를 만들어 실제 객체의 메소드를 호출할 수 있습니다.

(실제 인스턴스를 사용해서 Mocking합니다.) 

 

@SpyBean

(@SpringBootTest, @WebMvcTest 사용)

@MockBean과 마찬가지로 Spring ApplicationContext에 spy 객체를 추가합니다. 

주의해야 될 점은 @SpyBean이 Interface일 경우에는 해당 Interface를 구현하는 실제 구현체가 꼭 스프링 컨텍스트에 등록이 되어있어야 합니다. (그러지 않으면 error가 발생합니다.)

 

예시

예시는 여기를 참고했다. 구체적으로 설명이 되어있어서 더 이해가 잘 되었습니다.


@Mock, InjectMocks 예시

@ExtendWith(MockitoExtension.class)	// (1)
public class MockAndInjectMocksTest {
    @Mock	// (2)
    private OrderRepository orderRepository;
    @Mock	// (2)
    private NotificationClient notificationClient;
    @InjectMocks	// (3)
    private OrderService orderService;
    
    @Test
    public void createOrderTest_basic() {
        // given
        Mockito.when(orderRepository.findOrderList()).then(invocation -> {
            System.out.println("I'm mock orderRepository");
            return Collections.emptyList();
        });
        Mockito.doAnswer(invocation -> {
            System.out.println("I'm mock notificationClient");
            return null;
        }).when(notificationClient).notifyToMobile();
        
        // when 
        orderService.createOrder(true);
        
        // then
        Mockito.verify(orderRepository, Mockito.times(1)).createOrder();
        Mockito.verify(notificationClient, Mockito.times(1)).notifyToMobile();
    }
}

(1) : 단위테스트에 초점을 맞추기 때문에 MockitoJUnit으로 Test를 진행합니다. 

 

(2) : OrderServic의 테스트를 진행할 때 영향을 주지 않는 임의의 OrderRepository, NotificationClient 객체를 주입합니다.

 

(3) : 임의의 객체인 OrderRepository, NotificationClient를 OrderService에 주입합니다.

 

 

@MockBean 예시

@SpringBootTest	// (1)
public class MockAndInjectMocksTest {
    @MockBean	// (2)
    private OrderRepository orderRepository;
    @MockBean	// (2)
    private NotificationClient notificationClient;
    @Autowired	// (3)
    private OrderService orderService;

    @Test
    public void createOrderTest_basic() {
        // given
        Mockito.when(orderRepository.findOrderList()).then(invocation -> {
            System.out.println("I'm mock orderRepository");
            return Collections.emptyList();
        });
        Mockito.doAnswer(invocation -> {
            System.out.println("I'm mock notificationClient");
            return null;
        }).when(notificationClient).notifyToMobile();

        // when
        orderService.createOrder(true);

        // then
        Mockito.verify(orderRepository, Mockito.times(1)).createOrder();
        Mockito.verify(notificationClient, Mockito.times(1)).notifyToMobile();
    }
}

(1) : 위의 예시인 MockitoExtension과 비교해서 @SpringBootTest는 각 객체 간 연관작용을 test하기에 적합합니다. ApplicationContext을 loading하기 때문에 MockitoExtension보다는 느린 단점이 있습니다. 

 

(2) : Spring ApplicationContext에 mock 객체를 등록하게 되고, Spring ApplicationContext에 의해 @Autowired가 동작할 때 등록 된 mock 객체를 사용할 수 있도록 동작합니다.

 

(3) : Spring ApplicationContext에서 알아서 생성된 객체를 주입받아 테스트를 진행할 수 있도록 합니다. 

 

@Spy 예시

@ExtendWith(MockitoExtension.class)
public class MockAndInjectMocksTest {
    @Spy	// (1)
    private OrderRepository orderRepository;
    @Spy	// (1)
    private NotificationClient notificationClient;
    @InjectMocks	// (2)
    private OrderService orderService;

    @Test
    public void createOrderTest_basic() {
        // given
        Mockito.when(orderRepository.findOrderList()).then(invocation -> {
            System.out.println("I'm mock orderRepository");
            return Collections.emptyList();
        });
        Mockito.doAnswer(invocation -> {
            System.out.println("I'm mock notificationClient");
            return null;
        }).when(notificationClient).notifyToMobile();

        // when
        orderService.createOrder(true);

        // then
        Mockito.verify(orderRepository, Mockito.times(1)).createOrder();
        Mockito.verify(notificationClient, Mockito.times(1)).notifyToMobile();
    }
}

(1) : OrderRepository의 메소드 중 일부 기능은 실제 기능을 실행하고 싶을 때가 있을 수 있습니다.

예를 들면 OrderRepository의 createOrder()는 stub하고, findOrderList()는 실제 실행시킵니다.

 

stub이란?

@Mock, InjectMocks 예시안에 있는 OrderRepository.findOrderList(), notificationClient.notifyToMobile()에 기대행위를 작성하여 테스트에서 원하는 상황을 설정할 수 있게 되었습니다. 

이런 기대 행위를 작성하는 것을 stub이라고 합니다.

 

(2) : @Mock, InjectMocks 예시안에 있는 @InjectMocks처럼 실제 실행시킬 메소드를 가진 OrderRepository, notificationClient객체를 주입해줍니다

 

 

@SpyBean 예시

@SpringBootTest
public class MockAndInjectMocksTest {
    @SpyBean	// (1)
    private OrderRepository orderRepository;
    @SpyBean	// (1)
    private NotificationClient notificationClient;
    @Autowired	// (2)
    private OrderService orderService;

    @Test
    public void createOrderTest_basic() {
        // given
        Mockito.when(orderRepository.findOrderList()).then(invocation -> {
            System.out.println("I'm mock orderRepository");
            return Collections.emptyList();
        });
        Mockito.doAnswer(invocation -> {
            System.out.println("I'm mock notificationClient");
            return null;
        }).when(notificationClient).notifyToMobile();

        // when
        orderService.createOrder(true);

        // then
        Mockito.verify(orderRepository, Mockito.times(1)).createOrder();
        Mockito.verify(notificationClient, Mockito.times(1)).notifyToMobile();
    }
}

(1) : @MockBean에서 spy의 개념만 변경된 것입니다.

 

(2) : 실행시킬 메소드를 가진 OrderRepository, NotificationClient를 OrderService에 주입하게 됩니다.

 

 

References

https://velog.io/@sa833591/SpringBootTest-1

https://velog.io/@sileeee/%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%97%90%EC%84%9C-MockBean%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EA%B8%B0

https://cobbybb.tistory.com/16

https://velog.io/@lxxjn0/Test-Double%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

반응형