Springboot Controller 테스트 해보기 1탄 (Mock, MockMvc)

참고
https://gocheat.github.io/spring/spring_test-1/
https://ktko.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81Spring-MockMvc-%ED%85%8C%EC%8A%A4%ED%8A%B8
https://we1cometomeanings.tistory.com/65

실무에서 단위테스트로 간단한 진행만 하지 TDD로 개발은 하지 않고 있는데 개발하면 할 수록 중요성이 너무 느껴서 열심히 사용해 보려고 한다.

Mock

  • 네트워크, 데이터베이스 등에 의존하고 있는 메서드를 테스트하기 위해 가짜 객체를 만들어서 진행하는 테스트
  • 테스트 작성을 위한 환경 구축이 어렵고 메소드 내에 타 외부 서비스 혹은 미들웨어에 의존적인 경우 사용

Mockito 프레임워크

  • Mock 객체를 만드는 번거로움을 줄여주기 위한 프레임워크

@ExtendWith(MockitoExtension.class): Mockito의 Mock 객체를 사용
@Mock: Mock 객체
@InjectMocks: @Mock 객체를 주입할 주체 객체

public class AuthController {
    @PostMapping("/save")
    public ApiResponse save(@RequestBody User user) {
        if (userService.getUser(user.getUserName()) != null) {
            return ApiResponse.fail();
        }
        userService.save(user);
        return ApiResponse.success(HttpStatus.OK.name(), null);
    }
}

위의 코드를 테스트 하려고 할 때면
@Mock 어노테이션으로 UserService를 Mock 객체로 만들어주고
@InjectMocks 어노테이션으로 AuthController를 객체를 주입 할 주제 객체로 선언해주면 될 줄 알았으나 생각해보니 userService를 Test 코드 자체에서 사용하는 것이 아니라 Controller 안에서 사용하니까 다른 방법이 필요해 보였다.

MockMvc

그 다른 방법으로 사용하는 것이 MockMvc 방법이다.
웹 애플리케이션을 애플리케이션 서버에 배포하지 않고 스프링의 MVC 동작을 테스트 할 수 있는 클래스이다.
사실 처음에는 비즈니스 로직을 구현하는 서비스단을 테스트하면 컨트롤러 단을 테스트 할 필요가 있을까? 싶었지만 일반적인 단위 테스트 형태가 아니라 통합 테스트 관점으로 생각하면 의미가 있다고 한다.
웹 환경에서 컨트롤러를 테스트하기 위해서는 서블릿 컨테이너 구동, DispatcherServlet 객체가 메모리에 올라갸아함. 서블릿 컨테이너를 모킹하면 실제가 아니라 테스트용 모형 컨테이너를 사용하면 간단하게 컨트롤러를 테스트 할 수 있음. --> 이 부분을 위해서 @WebMvcTest 또는 @AutoConfigureMockMvc를 사용한다.

완성된 테스트 코드

package com.studygram;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.studygram.common.oauth.ProviderType;
import com.studygram.common.oauth.RoleType;
import com.studygram.controller.AuthController;
import com.studygram.domain.User;
import com.studygram.service.UserService;

import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class AuthControllerTest {

    @Mock
    private User user;

    @Autowired
    private MockMvc mockMvc;

    AutoCloseable openMocks;

    ObjectMapper objectMapper = new ObjectMapper();

    @BeforeEach
    public void setup() {
        openMocks = MockitoAnnotations.openMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(AuthController.class).build();
    }

    @Test
    @DisplayName("로컬 회원 가입 테스트")
    public void saveTest() throws Exception
    {
        // given
        user = User.builder()
                .userName("minchoi")
                .passwd("1234")
                .providerType(ProviderType.LOCAL)
                .roleType(RoleType.USER)
                .build();

        // when & then
        mockMvc.perform(MockMvcRequestBuilders
                        .post("/api/v1/auth/save")
                        .content(objectMapper.writeValueAsString(user))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }
}

우선 @AutoConfigureMockMvc@WebMvcTest 중에 고민을 했는데 친구가 회사에서 사용한다는 테스트 코드 훔쳐보니까 @AutoConfigureMockMvc을 사용하고 있어서 일단 사용해봤다. 차이점은 더 알아보자.

파라미터 설정
params vs content
get 으로 보낼 때는 params로 붙여서 보내고
body에 집어넣어서 보낼 때는 content로 붙여서 json으로 보낸다.

@WebMvcTest

  • Http server를 실행하지 않고 테스트가 가능
  • 웹 상에서의 요청과 응답에 대한 테스트를 할 수 있음
  • 모든 설정 정보가 로드 되는 것이 아니기 때문에 조금 가볍다.
  • 많은 어노테이션들 (ex: Spring Security, @AutoConfigureWebMvc 등) 다른 설정을 포함하기 때문에 테스트를 용이하게 한다.
  • @Controller, @RestController가 설정된 클래스를 찾아 메모리에 생성한다.

@ActiveProfiles

  • dev 설정 파일을 로드한다 (?)

좋은 웹페이지 즐겨찾기