업데이트:

Java에서 테스트 코드를 작성하고, Test Double을 사용하려 하면 거의 필수적으로 마주치게 되는 것이 Mockito 입니다. 오늘은 이 Mockito에서 제공하는 @Mock, @Spy과 이와 비슷한 용도로 Spring에서 제공하는 @MockBean, @SpyBean에 대해서 포스팅해보도록 하겠습니다.

Mockito?


먼저 Mockito란 무엇일까요??

MockitoJava용 오픈 소스 테스트 프레임워크이며, 테스트를 위한 Test Double을 쉽게 생성할 수 있고, 관련 검증 메소드를 제공해주고 있습니다.

@Mock

원래는 Mockito.mock() 메소드를 통해 Mock 객체로 만들지만 Mockito@Mock 어노테이션을 통해 특정 객체를 Mock객체로 바인딩 해주기도 합니다.

@ExtendWith(MockitoExtension.class)
public class BoardServiceTest {

    @Mock
    private PostRepository postRepository;

    @InjectMocks
    private PostService postService;

    @Test
    void mockitoMockTest() {
        PostRepository mockPostRepository = mock(PostRepository.class);
    }
}

위의 샘플 코드에서 postRepository, mockPostRepository를 Mock 객체로 선언했습니다.

mock_annotation1

디버깅을 콘솔을 보면 둘다 Mock 객체가 되어있는 것을 확인 할 수 있습니다.

@Spy

Mock은 개발자가 지정한 Stub말고 원래의 기능은 동작하지 않습니다. (Mock이기 때문에)

하지만 지정한 Stub을 제외한 나머지 기능을 그대로 사용하고 싶은 경우가 생기게 되는데, 이때 사용할 수 있는 어노테이션이 @Spy입니다.

@ExtendWith(MockitoExtension.class)
public class BoardServiceTest {

    @Spy
    private PostRepository postRepository;

    @InjectMocks
    private PostService postService;

    @Test
    void mockitoMockTest() {
        final Long id = 1L;
        given(postRepository.findById(id))
            .willReturn(Optional.ofNullable(new Post(Tag.GAME, Category.FREE)));

        postRepository.realFindPost();
        Optional<Post> stubbingPost = postRepository.findById(id);
    }
}

위의 샘플 코드에서 PostRepositorySpy로 선언하였고, given() 메소드를 통해 findById()메소드에 대해 Stubbing 하였습니다. 그리고 테스트 메소드에선 postRepositoryrealFindPost() 메소드와 findById() 메소드를 실행 했습니다. realFindPost() 메소드는 다음과 같습니다.

public interface PostRepository extends JpaRepository<Post, Long> {

    default Post realFindPost(){
        System.out.println("real PostRepository realFindPost");
        return new Post(Tag.SHOPPING, Category.FREE);
    }
}

만약 Spy로 선언해서 진짜 realFindPost()가 실행된다면 real PostRepository realFindPost가 콘솔에 출력되겠죠??

mock_annotation2

테스트를 돌려보니 정상적으로 출력되는 것을 확인할 수 있습니다.

mock_annotation3

그리고 Stubbing한 객체로 정상적으로 반환되는 것을 확인할 수 있습니다.

@InjectMocks

@InjectMocks는 사용하면 위에서 사용한 @Mock, @Spy로 지정된 Mock 객체들 중 필요한 객체들을 주입시켜 줍니다. (SpringDI와 유사하다고 보면 됩니다.)

MockitoExtension

위의 샘플 코드에서 맨위를 보시면 @ExtendWith(MockitoExtension.class) 부분이 있는데, 테스트 코드를 작성할 때 위의 다양한 어노테이션(@Mock, @Spy, @InjectMocks)을 사용하기 위해선 MockitoExtension를 추가해줘야 합니다.

💡SpringExtension를 추가해줘도 됩니다. Spring안에 있는 MockitoTestExecutionListener를 통해 Mockito의 어노테이션, 메소드를 실행해줌을 확인할 수 있습니다.

mock_annotation4

Spring의 Test 어노테이션


Mockito에서 제공하는 @Mock, @Spy 처럼 Spring에서도 Mocking을 위한 다양한 어노테이션을 제공합니다.

@MockBean

Mockito@Mock과 비슷하게 동작합니다.

하지만 다른 점이라면 Spring ContextMockingBean에 등록되게 됩니다. 사용하기 위해선 @InjectMocks을 사용하는 것이 아닌 @Autowired를 사용하면 됩니다.

@SpringBootTest
public class BoardServiceTest {

    @Autowired
    ApplicationContext applicationContext;

    @MockBean
    private PostRepository postRepository;

    @Autowired
    private PostService postService;

    @Test
    void mockitoMockTest() {
        PostRepository mockBeanPostRepository 
						= (PostRepository) applicationContext.getBean("postRepository");
    }
}

샘플 코드를 보시면 @ExtendWith를 사용하지 않고, @SpringBootTest 어노테이션을 사용하여 Spring Context를 올렸고, Bean이 올라가도록 했습니다.

그리고 applicationContext.getBean()를 사용하여 postRepository Bean이 있는지 확인해보겠습니다.

mock_annotation5

결과를 보시면 PostRepositoryBean으로 올라가 있고, Mock 객체로 되어있음을 알 수 있습니다.

@SpyBean

Mockito@Spy와 비슷하게 동작합니다.

@MockBean과 마찬가지로 Spring Context에 등록되는 방식으로 동작합니다.

@Autowired

당연한 얘기겠지만 관리하는 영역이 다르기 때문에 @MockBean 사용시에는 @InjectMocks를 사용할 수 없습니다.

위의 샘플 코드에서 @Autowired 사용시에는 아래와 같이 정상적으로 MockBean을 가져와서 사용합니다.

mock_annotation6

하지만 @InjectMocks를 사용 시 아래와 같이 null로 출력되는 것을 확인할 수 있습니다.

mock_annotation7

마무리


오늘은 다양한 Test Double 사용 법에 대해 확인해 보였는데요, 개인적으로 단위 테스트를 주로 사용하는 이상 @MockBean이나 @SpyBean은 사용할 일이 많이 없을 것 같습니다.

테스트 코드를 작성하시는데 제 글이 도움이 되셨으면 좋겠습니다.

오늘도 제 긴글을 봐주셔서 감사합니다.😀

📌참고


Mockito @Mock @MockBean @Spy @SpyBean 차이점

댓글남기기