개발 낙서장

[TIL] 내일배움캠프 44일차 - Controller 테스트 본문

Java/Sparta

[TIL] 내일배움캠프 44일차 - Controller 테스트

권승준 2024. 2. 27. 21:47

오늘의 학습 키워드📚

Controller 테스트

Controller 테스트란 해당 컨트롤러의 요청과 응답이 정상적으로 이루어지는지 테스트하는 것이다.
요청하는 값, 응답하는 값에 대한 검증 보다는 요청과 응답의 행위 자체를 중점으로 두고 테스트한다.
클래스 상단에 @WebMvcTest 어노테이션을 달아주면 MVC 기반 테스트가 가능하다.
어노테이션 내부에서 filter라던가 property 등 여러 설정이 가능하다.

@WebMvcTest(
    controllers = {???Controller.class},
    excludeFilters = {
        @ComponentScan.Filter(
            type = FilterType.ASSIGNABLE_TYPE,
            classes = WebSecurityConfig.class
        )
    }
)

해당 프로젝트에서 스프링 시큐리티를 사용하기 때문에 미리 예외 설정을 해주어야 한다.

또한 Mock 필터를 만들어 실제 인가 과정을 거치는 것이 아닌 테스트용 인가를 해줘야 한다.

public class MockSpringSecurityFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
        SecurityContextHolder.getContext()
            .setAuthentication((Authentication) ((HttpServletRequest) req).getUserPrincipal());
        chain.doFilter(req, res);
    }

    @Override
    public void destroy() {
        SecurityContextHolder.clearContext();
    }
}

테스트를 하기 전 setUp 메소드를 통해 MockMvc를 설정하고 principal 객체를 생성해주어야 한다.
그리고 해당 컨트롤러의 생성자에 들어가는 클래스를 @MockBean 어노테이션을 통해 Bean으로 등록해주어야 한다.

    private MockMvc mockMvc;
    private Principal principal;

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    OrderDetailService orderDetailService;
    
    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .apply(springSecurity(new MockSpringSecurityFilter()))
            .build();

        mockSetup();
    }

여기서 ObjectMapper는 RequestBody의 객체를 Json 타입으로 보낼 수 있게 해주는 클래스이다.
mockSetup 메소드를 통해 기존에 필요한 값(User, Principal, 여러 Entity들)들을 설정해 줬다.

방식은 이러하다. 요청에 필요한 값들을 미리 설정해 given으로 지정해 두고 mockMvc의 perform 메소드를 통해 요청을 전달해 응답을 검증하면 된다.

뭔가를 추가하는 POST 요청을 테스트하고 싶다면 다음과 같이 하면 된다.

    @Test
    void addTest() throws Exception {
        // given
        TestRequestDto requestDto = new TestRequestDto("test", 5);
        String postInfo = objectMapper.writeValueAsString(requestDto);

        TestResponseDto responseDto = new TestResponseDto("test", 5);

        given(TestService.addTest(any(TestRequestDto.class), anyLong(),
            any(User.class))).willReturn(responseDto);

        // when
        var action = mockMvc.perform(post("/tests/1").content(postInfo).contentType(
            MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).principal(principal));

        // then
        action.andDo(print());
        action.andExpect(status().isCreated());
        verify(TestService, times(1)).addTest(any(TestRequestDto.class), eq(1L), eq(user));
    }

먼저 RequestBody에 넘겨줄 RequestDto를 설정하고 반환받을 ResponseDto를 만들어 행위에 대한 결과를 지정해 준다.
여기서 any를 사용하지 않고 직접 값이나 객체로 넘겨줄 경우 404 에러가 발생하게 된다.

이후 mockMvc를 통해 POST요청을 보낸다. content에는 body에 담길 json, contentType은 넘겨줄 데이터의 타입, accept는 응답받을 데이터의 타입, principal은 인가를 위한 유저 principal 객체를 넣어 요청을 보내면 된다.

이후 응답된 값을 출력하고 status나 호출 횟수, 파라미터 등을 검증하면 된다.

처음에 Controller 테스트를 진행하면서 착각했던 게 요청과 응답을 하면서 데이터의 처리가 잘 이루어지는지, 값은 잘 들어가고 정확히 나오는지 등을 테스트하려고 했어서 힘들었는데 Controller 테스트 행위는 요청과 응답이 잘 이루어지는지가 목적이기 때문에 값에 대한 검증보다는 요청과 응답이 원하는 대로 잘 이루어지는지를 검증해야 한다.


오늘의 회고💬

팀 프로젝트가 마무리 단계에 들어섰다. 테스트 코드는 작성하면서도 항상 헷갈리는 것 같다. 그래도 몇 번 하다 보니 또 나름대로 익숙해지긴 했다. 맞는 방법인지는 모르겠지만😅

 

내일의 계획📜

팀 프로젝트를 리팩토링하고 추가할 부분은 더 추가하고 그럴 시간이 있었으면 좋겠다.

Comments