본문 바로가기
스프링/스프링부트

[JUnit] 스프링부트 + junit5 환경에서 MockMvc로 컨트롤러 테스트하기

by 책 읽는 개발자_테드 2021. 4. 29.
반응형

Mock이란?

 

 사전적 의미로 '테스트를 위해 만든 모형'을 의미하고, 테스트를 위해 실제 객체와 비슷한 모의 객체를 만드는 것을 모킹(Mocking), 모킹한 객체를 메모리에서 얻어내는 과정을 목업(Mock-up)이라 한다.

 

Mock의 필요성

 

 객체를 테스트하려면 테스트 대상 객체가 메모리에 있어야 한다. 하지만 생성하는 데 복잡한 절차가 필요하거나 많은 시간 이 소요되는 객체가(ex. 서비스레이어) 있을 수 있고, 웹 어플리케이션의 컨트롤러처럼 WAS나 다른 소프트웨어의 도움이 반드시 필요한 객체도 있을 수 있다. 

 

 이러한 복잡한 객체를 테스트하기 위해 실제 객체와 비슷한 가짜 객체를 만들어 테스트에 필요한 기능만 기지도록 모킹하면 테스트가 쉬워진다. 또한 복잡한 의존성을 가지고 있을 때, 모킹한 객체를 이용하면 의존성을 단절시킬 수 있어 쉽게 테스트할 수 있다. 웹 애플리케이션에서 컨트롤러를 테스트할 때, 서블릿 컨테이너를 모킹하려면 @WebMvcTest 또는 @AutoConfigureMockMvc를 사용한다.

 

서블릿 컨테이너를 모킹한다는 의미는?

웹 환경에서 컨트롤러를 테스트하려면 서블릿 컨테이너가 구동되고 DispatcherServlet 객체가 메모리에 올라가야 한다. 이때 서블릿 컨테이너를 모킹하면 실제 서블릿 컨테이너가 아닌 테스트용 모형 컨테이너를 사용해서 간단하게 컨트롤러를 테스트할 수 있다.

 

@WebMvcTest 사용하기

사용자 정보를 출력하는 스프링부트 애플리케이션을 테스트한다고 가정하고, 다음과 같은 구조를 만들자.

 

각 클래스의 코드는 다음과 같다. 

import lombok.Getter;

@Getter
public class User {
    String name;
    String hobby;
}
import com.junit.junitstudywithspringboot.Domain.User;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class UserController {

    @GetMapping("users/introduction")
    public ResponseEntity<?> introduction(@RequestBody User user)   {
            String introduction = "안녕하세요. 저의 이름은 " + user.getName()+ "입니다."
                    + " 저의 취미는 " + user.getHobby() + "입니다.";
            return ResponseEntity.ok(introduction);
    }
}

 

그리고 UserController의 테스트 코드를 작성하자.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;



//@ExtendWith(MockitoExtension.class), @ExtendWith(SpringExtension.class) 모두 가능
@ExtendWith(SpringExtension.class)
@WebMvcTest
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testUserIntroduction() throws Exception {
        String content = "{\"name\": \"Ted\", \"hobby\": \"만들기\"}";

        mockMvc.perform(get("/users/introduction")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(content))
                .andExpect(status().isOk())
                .andExpect(content().string("안녕하세요. 저의 이름은 Ted입니다. 저의 취미는 만들기입니다."))
                .andDo(print());
    }
}

 

위에서 아래로 코드를 설명하면 다음과 같다.

 

@ExtendWith(SpringExtension.class)

Junit5에서는 ExtendWith 어노테이션을 통해 테스트 클래스 또는 메서드의 기능을 확장할 수 있다(junit4 이하에서는 runwith 어노테이션 이용). 즉, 위의 @ExtendWith(SpringExtension.class)는 Spring TestContext Framework의 기능을Junit5의 Jupiter 프로그래밍 모델에 통합하는 역할을 한다.

 

public class SpringExtension
extends Object
implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver{..}

 

위의 코드는 @ExtendWith(SpringExtension.class)를 @ExtendWith(MockitoExtension.class)로 변경해도 정상작동한다. MockitoExtension은 SpringExtension 보다는 적지만, 다양한 기능을 구현하여 테스트 클래스의 기능을 확장한다.

 

@WebMvcTest

@WebMvcTest는 @Controller, @RestController가 설정된 클래스들을 찾아 메모리에 생성한다. 

 

 

MockMvc

MockMvc는 서블릿 컨테이너를 모킹한 객체다. testIntroduction() 메소드의 내부 코드는 MockMvc 객체를 사용하여 컨트롤러에 대해 작성한 테스트 코드다.

 

 

테스트를 실행하면, 톰캣 서버가 구동되지 않고, 모킹된 서블릿 요청과 응답에 대한 메시지를 확인할 수 있다.

 

@AutoConfigureMockMvc

@AutoConfigureMockMvc는 @SpringBootTest 애노테이션과 함께 작성하여, @WebMvcTest와 비슷하게 사용할 수 있다. 

 

@SpringBootTest에는 웹 애플리케이션 테스트를 지원하는 webEnvironment 속성이 있다. 이 속성을 생략하면 기본 값으로 WebEnvironment.Mock이 설정되어 있고, 이 설정에 의해 서블릿 컨테이너가 모킹된다. 즉, 테스트 케이스 실행 시에 서블릿 컨테이너를 구동하지 않는다. 

 

SpringBootTest(webEnvironment=WebEnvironment.MOCK)설정으로 모킹한 객체를 의존성 주입 받으려 @AutoConfirgureMockMvc를 클래스 위에 추가한다.

 

@WebMvcTest와 차이점은 @AutoConfigureMockMvc는 컨트롤러뿐만 아니라 @Service, @Repository가 분은 객체들도 모두 메모리에 올린다는 점이다. 즉, 컨트롤러만 테스트할 때는 @WebMvcTest를 이외에 컴포넌트들도 테스트하려면 @AutoConfigureMockMvc를 사용하자. 또한 @WebMvcTest, @SpringBootTest는 모두 MockMvc를 모킹하기 때문에 충돌이 발생하여 함께 사용할 수 없다.

 

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testUserIntroduction() throws Exception {
        String content = "{\"name\": \"Ted\", \"hobby\": \"만들기\"}";

        mockMvc.perform(get("/users/introduction")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(content))
                .andExpect(status().isOk())
                .andExpect(content().string("안녕하세요. 저의 이름은 Ted입니다. 저의 취미는 만들기입니다."))
                .andDo(print());
    }
}

 

출처

스프링 부트 퀵 스타트

stackoverflow.com/questions/61433806/junit-5-with-spring-boot-when-to-use-extendwith-spring-or-mockito

반응형

댓글