JAVA/Junit

[Junit5] Mockito - Controller 단위 테스트 작성

Yuco 2023. 10. 28. 17:14

 

Spring boot 단위 테스트의 정석 Junit5 + Mockito의 정보가 궁금하다면?

2023.10.28 - [Junit5] Mockito를 사용하는 방법

 

[Junit5] Mockito를 사용하는 방법

1. Mockito의 제공 Mockito는 spring boot에서 사용하는 가장 기본적인 Mocking 프레임워크입니다. spring boot의 maven이나 gradle에 spring-boot-starter-test가 있을 경우, mockito는 junit과 함께 자동적으로 제공됩니다.

yuna-story.tistory.com

 


 

Junit5 : Spring boot Contoller test code 작성법

 

이해를 돕기 위해 간단한 예시를 통해 Controller 테스트 코드 작성법을 설명하도록 하겠습니다. 

 

 

1. 실제 Controller Code

@RestController
@RequestMapping("/users")  
public class UserController {

    private final UserService userService;

    public UserController(
        UserService userService
    ) {
        this.userService = userService;
    }
    
    @PostMapping("/sign-up")
    public ResponseEntity<SignUpResponseBody> signup(
        @RequestBody() SignUpRequestBody signUpRequestBody
    ) {
        return ResponseEntity.ok(
            this.userService.createUser(signUpRequestBody)
        );
    }
}

위의 코드를 살펴보면 userController는 userService의  createUser()라는 함수를 호출하여 이로부터 SignUpResponseBody라는 객체를 return 받고 있습니다. 

 

단위 테스트의 목적은 독립적으로 테스트 하고자 하는 대상 클래스만 테스트하는 것입니다.

 

그러므로 대상 클래스가 아닌 다른 클래스의 객체는 Mocking을 통해 적절한 return 값을 반드시 설정해줘야 합니다. 

 

 

2. Controller 단위 테스트 세팅

@ExtendWith(MockitoExtension.class)
class UserControllerTest {

    @InjectMocks
    private UserController userController;
    
    @Mock
    private UserService userService;
    
    @Mock
    private MockMvc mockMvc;
    
    private ObjectMapper objectMapper;

    @BeforeEach
    public void init() {
        mockMvc = MockMvcBuilders
            .standaloneSetup(userController)
            .build();
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
    }

 

2.1. 테스트 대상 클래스 - @InjectMocks

@InjectMocks
private UserController userController;

테스트 대상 클래스에는 @InjectMocks 어노테이션을 사용하는 게 일반적입니다.

 

@InjectMocks 어노테이션은 실제 UserControlle Class에서 생성하고 있는 객체인 UserService가 자동적으로 주입되도록 합니다. 그러므로 실제 테스트 코드에 아래와 같은 UserService 객체를 주입하는 코드를 작성 하지 않아도 됩니다. 

private final UserService userService;

public UserController(
    UserService userService
) {
    this.userService = userService;
}

 

2.2. 대상 클래스 이외 클래스 - @Mock

대상 클래스의 독립적인 테스트를 위해 외부에서 주입되는 대상 클래스 이외의 클래스들에 대한 @Mock 설정이 필요합니다. @Mock 어노테이션을 사용하게 되면, stub 설정을 통해 해당 클래스의 함수의 반환 값을 설정해줌으로써, 실제 서비스와의 통신 없이도 다른 서비스의 함수에서 반환해주는 값을 설정할 수 있습니다. 

@Mock
private UserService userService;

 

2.3. MockMvc

MockMvc는 스프링 프레임워크에서 제공하는 테스트 도구로서, 웹 애플리케이션을 mock 하는데 사용됩니다. 이를 사용하여 컨트롤러의 행위를 테스트하고 HTTP 요청 및 응답을 시뮬레이션 할 수 있습니다.

 

말 그대로 MockMvc를 통해 HTTP 테스트에 대한 가짜 요청을 만들 수 있습니다. 이를 통해 GET / POST / PATCH / DELETE 등의 요청을 만들어 보낼 수 있습니다. 

 

아래 코드와 같이 MockMvc 객체를 초기화 함으로써 userController를 스탠드얼론(StandAlone) 모드로 설정하고, 컨트롤러에 독립적으로 테스트할 수 있는 환경을 제공할 수 있습니다. 결과적으로 MockMvc를 통해 실제 HTTP 서버 없이도 컨트롤러의 동작을 테스트할 수 있습니다. 

@Mock
private MockMvc mockMvc;

@BeforeEach
public void init() {
    mockMvc = MockMvcBuilders
        .standaloneSetup(userController)
        .build();
}

 

2.4. ObjectMapper 

MockMvc를 사용하여 HTTP 요청을 테스트할 때, 요청 본문의 데이터를 Json 형식으로 보내거나 읽기 위해 ObjectMapper가 필수적입니다. ObjectMapper를 사용하여 객체를 Json 문자열로 직렬화하여 HTTP POST 요청의 본문을 생성할 수 있으며, MockMvc 객체로부터 반환 받은 HTTP 응답의 내용을 검증할 수 있습니다. 

 

* objectMapper.registerModule(new JavaTimeModule()); 는 java.time 패키지에서 제공하는 날짜와 시간 관련 클래스를 Json으로 직렬화 및 역직렬화할 대 사용되며, LocalDateTime, LocalDate 등이 포함됩니다.

 private ObjectMapper objectMapper;

    @BeforeEach
    public void init() {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
    }

 

 

3. 컨트롤러 단위 테스트 코드

    @DisplayName("createUser 테스트 성공")
    @Test
    void testCreateUser() throws Exception {
        SignUpRequestBody signUpRequestBody = new SignUpRequestBody(); 
        SignUpResponseBody signUpResponseBody = new SignUpResponseBody();

        when(userService.createUser(any(SignUpRequestBody.class)).thenReturn(signUpResponseBody);

        ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders
                                                          .post("/users/sign-up")
                                                          .contentType("application/json")
                                                          .content(objectMapper.writeValueAsString(request)));

        MvcResult mvcResult = resultActions
        	.andExpect(status().isOk())
        	.andReturn();
            
        String responseBody = mvcResult.getResponse().getContentAsString();
        
        assertEquals(
        	objectMapper.writeValueAsString(signUpResponseBody), responseBody
            );
    }

 

3.1 request, response 객체 생성

해당 컨트롤러의 signUp() 함수를 테스트 하기 위해 필요한 객체 생성이 필요합니다. 

(실제 객체 필드 값의 설정이 필요하나, 예시인 관계로 set을 생략하였습니다.) 

 

 

3.2 외부 클래스 Mocking

독립적인 컨트롤러에 대해서만 테스트를 작성해야 하므로, 테스트 대상 클래스가 아닌 userService의 creatreUser() 라는 함수를 mocking 해주는 게 필요합니다. 

실제 코드에서 SignUpResponseBody() 라는 객체를 return 하고 있으므로, SignUpResponseBody() 라는 객체 생성을 통해 userService.createUser() 라는 함수를 mocking 해줍니다. 

 

아래와 같이 when 과 thenReturn()을 통해 객체 stub을 생성해줍니다.

when과 thenReturn()을 통해 userService의 createUser()라는 실제 메서드의 호출을 가로채고, 해당 메서드가 호출될 때 반환 값을 signUpResponseBody라는 값으로을 설정하는 게 가능합니다. 

when(userService.createUser(any(SignUpRequestBody.class)).thenReturn(signUpResponseBody);

 

3.3 MockMvc HTTP 요청 

컨트롤러 테스트에 필요한 stub을 모두 설정한 후, MockMvc 객체를 사용하여 HTTP POST 요청을 보내는 걸 테스트할 수 있습니다. 아래 코드는 /users/sign-up 경로로 POST 요청을 보내는 예시입니다. 

 

3.3.1. perform()

perform() 메서드는 HTTP 요청을 시작하고, 요청의 결과를 확인하고 검증할 수 있는 ResultActions 객체를 반환합니다. 

 

3.3.2 MockMvcRequestBuilders.post()

/users/sign-up 경로로 POST 요청을 보내기 위해 사용합니다. 

 

3.3.3 contetnType("application/json")

이 부분은 HTTP 요청의 Content-Type 헤더를 설정함으로써, 요청이 Json 객체를 포함한다는 걸 나타냅니다.

 

3.3.4 content(objectMapper.writeValueAsString(request)) 

이 부분은 HTTP 요청의 본문 내용을 설정하는 부분으로 objectMapper.writeValueAsString() 을 사용하여 request(SignUpRequestBody) 객체를 Json 문자열로 직렬화하며, 이 Json 문자열을 요청의 본문에 포함시키는 역할을 합니다. 

ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders
                                                          .post("/users/sign-up")
                                                          .contentType("application/json")
                                                          .content(objectMapper.writeValueAsString(request)));

 

3.4 검증 

반환된 ResultActions 객체를 사용하여 요청의 결과를 검증하거나 분석할 수 있습니다.

아래 코드는 MockMvc를 사용하여 수행한 HTTP POST 요청의 결과를 검증하고, 서버에서 반환된 응답을 확인하는 코드입니다. 

 

3.4.1 MvcResult

이는 andExpect()를 사용하여 ResultActions 객체의 상태 코드의 값이 200(OK) 인지 확인하며, 200이 아닐 경우 테스트 코드는 실패하게 됩니다. 또한 andReturn() 을 통해 MockMvc 요청의 결과를 반환하며, 이를 MvcResult 변수에 저장합니다. 

 

또한 getResponse().getContentAsString()을 통해 MvcResult의 변수에 저장된 응답값의 본문 내용을 문자열로 추출하여 responseBody 변수에 저장합니다.  

MvcResult mvcResult = resultActions
    .andExpect(status().isOk())
    .andReturn();

String responseBody = mvcResult.getResponse().getContentAsString();

 

3.4.2 assertEquals

assertEquals() 메서드를 사용하여 Json으로 변환된 signUpResponseBody 값과 실제 테스트로부터 응답받은 reponseBody를 비교할 수 있습니다. 이는 서버가 올바른 응답을 생성하고 반환했는지 확인하기 위한 코드이며, 테스트가 성공 여부를 검증할 수 있습니다.

 

assertEquals() 이외에도 assertNotNull(), assertNull(), assertThat() 등을 통해 테스트 코드의 검증이 가능합니다.

assertEquals(
    objectMapper.writeValueAsString(signUpResponseBody), responseBody
    );

 

 

 

이는 실제 테스트 코드를 작성해보며 적어보는, 저의 경험이 담긴 글입니다. 혹시 틀린 부분이 있다면 댓글로 알려주세요 😀

'JAVA > Junit' 카테고리의 다른 글

[Junit] MockMvc - Body / Query / Path 작성 방법  (0) 2023.10.31
[Junit5] Mockito를 사용하는 방법  (0) 2023.10.28