Web/Spring

[트러블슈팅/개념정리] WebMvcTest와 MockBean이 함께 쓰이는 이유(Feat. Mockito)

TLdkt 2023. 3. 3. 18:25
728x90
반응형

 

들어가며

컨트롤러 테스트를 하려고 코드를 열심히 적었따.. 

기억에 의존해서 썼더니.... 실패는 물론이고 애초에 contextloader가 작동하지 않았다고 해서 몇 시간 동안 해결책을 찾아 헤맨 끝에 해결했다. 들인 시간에 비해 허무한 수준의 트러블슈팅이지만.. 테스트용 애너테이션을 학습할 수 있는 기회였다.

 

 

 

직관과 감으로 쓴 야생의 테스트 코드

틀린 테스트 코드

@WebMvcTest
@Import( {TestSecurityConfig.class, TestJpaConfig.class} )
@ExtendWith(SpringExtension.class)
@AutoConfigureRestDocs
public class FeedbackControllerTest {
    @Autowired
    MockMvc mockMvc;
    @Autowired
    ObjectMapper objectMapper;
    @InjectMocks
    FeedbackController feedbackController;
    @Mock
    FeedbackServiceImpl feedbackService;
    @Test
    @DisplayName(" 요청 인입 시 정상적으로 리턴한다 ")
    void writeFeedback_suc() throws Exception {
    //... 이하 생략
}

 

 

사람이 대충 코드를 짜면 이런 봉변을 당하는 거다... 공식문서를 읽자

이미지 클릭하면 공식문서로 이동합니다

불친절해 가독성 떨어져 ... 

어쨌든 밑줄 친 부분만 해석해봐도 웹 레이어 테스트에 필요한 컨트롤러, 컨버터만 제공해주는 경량 테스트 애너테이션이라는 걸 알 수 있따.. SpringSecurity와 MockMvc 세팅도 자동으로 해준다고 한다..

@MockBean, @Import와 함께 쓰라고 나와 있는데 나는 .... @Mock으로 두 시간째 뻘짓을 하고 있었다 

 

 

더보기

공식문서, 가이드 확인하려면 클릭

어쨌든.. 테스트할 대상을 뒤에 적으라고 나온다 

이렇게 말하면 누가 알아들어요 예?

 

클릭 시 가이드로 이동

죄송합니다 가이드에 있네요 ^^;;

 

 

수정한 코드

@WebMvcTest( FeedbackController.class )
@Import( TestSecurityConfig.class )
@ExtendWith(SpringExtension.class)
@AutoConfigureRestDocs
public class FeedbackControllerTest {
    @Autowired
    MockMvc mockMvc;
    @Autowired
    ObjectMapper objectMapper;
    @MockBean
    FeedbackServiceImpl feedbackService;
    @MockBean
    ModelMapper modelMapper;
    @Test
    @DisplayName(" 요청 인입 시 정상적으로 리턴한다 ")
    void writeFeedback_suc() throws Exception {
       //test code
    }
}

 

바뀐 점

1. @InjectMocks 삭제
    FeedbackController가 테스트 대상이라는 걸 이미 WebMvcTest에 명시했으므로 삭제해도 된다

2. @Mock 대신 @MockBean 

    공식문서에 나온 대로 MockBean을 활용해 의존성이 있는 클래스를 Bean으로 바꿔치기했다

 

 

 

그렇다면 @ExtendWith, @InjectMocks 등 나머지 애너테이션도 정리해보자

 

@ExtendWith(SpringExtension.class) 

스프링에서 쓰이는 기능을 JUnit 테스트에서도 사용할 수 있게 해준다

package org.springframework.test.context.junit.jupiter;

그래서 JUnit에서 제공하는 클래스다. 

 

참고로 내부에는 이런 메서드가 있는데 

내부적으로 @Autowired가 잘 사용되고 있는지(메서드에 @Autowired가 붙으면 안 된다) 확인하고, 캐싱해서 두 번 검증하지 않도록 하는 기능이다. 

 

@InjectMocks

Mockito 프레임워크의 애너테이션이다. 

예를 들어, 상품서비스가 상품 레파지토리에 의존성이 있다면

@InjectMocks ProductService productService;
@Mock ProductRepository productRepository;

이렇게 작성해주면 된다

 

왜 WebMvsTest와 Mock을 함께 쓰면 안 될까?

기본적으로 WebMvcTest는 스프링 프레임워크 소속이다. 그래서 Bean을 활용한다

반면, Mockito는 Spring Application Context와 무관하다. 따라서 Bean을 로드하지 않는다

 

즉, WebMvcTest에서 @Mock을 써도 되는 경우는 없으며, @Mock을 써서 추가해봤자 SpringContext에 포함되지 않으므로 사용할 수 없다. 

따라서 목 객체를 만들고 싶다면 @WebMvcTest에 Bean으로 등록될 수 있도록 @MockBean을 써주어야 한다. 

 

나가며

 

정리하면,

1. 컨트롤러 테스트를 하고 싶을 때에는 통합 테스트가 아닌 이상 @WebMvcTest로 가볍게 진행하는 것이 좋다.

2. 이떄 @WebMvcTest(타겟 클래스.class) 형태로 작성하며,

3. 고립 테스트를 하고 싶은 경우에는 @Mock이 아닌 @MockBean을 써야 한다.

3-1. 이유는 @WebMvcTest 자체가 스프링 컨텍스트에서 일부 웹 레이어 관련된 Bean만 로드해서 테스트해주는 원리로 동작하기 때문이다. 즉, Bean으로 등록해야 WebMvcTest에서 인식할 수 있기 때문이다. 

 

 

Reference

https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testing

https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html

728x90
반응형