Java

[JUnit]Spring Boot에서 JUnit으로 테스트하는 방법

연신내고독한늑대 2024. 9. 9. 20:00

이 글에서는 Spring Boot에서 JUnit을 사용하여 컨트롤러 테스트를 수행하는 방법을 설명하겠습니다. 특히, 사용자 인증CSRF 보호와 같은 보안 관련 사항을 고려하여 테스트 코드를 작성하는 방법에 대해 다룰 것입니다.

 

아래 코드는 Spring Security가 적용된 환경에서 컨트롤러를 테스트하는 예시입니다. 이 코드에서는 카카오 지도 api를 사용하고 사용자 인증, CSRF 토큰 검증을 고려한 테스트 환경을 구성합니다.

 

아래의 **testGetRoute()**는 MapController의 /api/map/route 경로에 대한 단위 테스트를 수행하는 코드입니다. 각각의 메소드를 이렇게 단위별로 테스트할 수 있으며, 이 예시에서는 /api/map/route로의 POST 요청을 처리하는 컨트롤러 메소드를 검증하고 있습니다. 이를 통해 특정 경로와 메소드에 대한 로직을 독립적으로 테스트할 수 있습니다.

package taxi.share.back.controller;

import org.junit.jupiter.api.Test;
import org.springframework.security.test.context.support.WithMockUser;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.web.servlet.MockMvc;
import taxi.share.back.controller.MapController;
import taxi.share.back.model.Routes;
import taxi.share.back.service.MapService;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;

@WebMvcTest(MapController.class)
public class MapControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private MapService mapService;

    @Test
    @WithMockUser(username = "testuser", roles = {"USER"})  // 가상의 인증 추가
    public void testGetRoute() throws Exception {
        // Mocking service response
        Mockito.when(mapService.route(Mockito.any(Routes.class))).thenReturn("Mocked Kakao API response");

        // Request body
        String requestBody = """
        {
            "origin": "여의도",
            "originLatitude": 37.4682787075426,
            "originLongitude": 127.039136433366,
            "destination": "노량진",
            "destinationLatitude": 36.9878099898812,
            "destinationLongitude": 127.301187652392
        }
        """;

        mockMvc.perform(post("/api/map/route")
                        .with(SecurityMockMvcRequestPostProcessors.csrf()) // CSRF 토큰 추가
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(requestBody))
                .andExpect(status().isOk())  // 200 OK 기대
                .andExpect(content().string("Mocked Kakao API response"));
    }
}

 

주요 코드 설명

 

  • @WebMvcTest(MapController.class):
    • 특정 컨트롤러(MapController)만 테스트하는 데 사용됩니다. Spring MVC 테스트를 위한 환경을 설정하며, 보안과 관련된 빈도 포함됩니다.
  • @Autowired MockMvc:
    • MockMvc는 실제 서블릿 환경 없이 컨트롤러를 테스트할 수 있는 객체입니다. HTTP 요청을 시뮬레이션하고 응답을 검증할 수 있습니다.
  • @MockBean MapService:
    • MapService는 테스트 시 실제 서비스 로직을 호출하는 대신, 모킹된 객체로 대체됩니다. 이로써 데이터베이스나 외부 API 호출 없이 로직만 검증할 수 있습니다.
  • @WithMockUser(username = "testuser", roles = {"USER"}):
    • 가상의 인증된 사용자를 만들어서 테스트 환경에 적용합니다. 이 어노테이션이 없을 경우, Spring Security는 인증되지 않은 사용자로 판단하여 401 Unauthorized 오류가 발생합니다.
    • 401 Unauthorized 오류가 발생하는 이유
      • Spring Security는 기본적으로 모든 요청에 대해 인증을 요구합니다. @WithMockUser 어노테이션을 통해 가상의 사용자가 로그인된 상태를 시뮬레이션합니다. 이 어노테이션이 없으면 테스트 시 인증되지 않은 사용자로 간주되어 401 Unauthorized 응답이 반환됩니다.
  • .with(SecurityMockMvcRequestPostProcessors.csrf()):
    • CSRF 토큰을 추가하는 코드입니다. Spring Security는 기본적으로 POST, PUT, DELETE 요청에서 CSRF 보호를 활성화합니다. CSRF 토큰이 없을 경우 403 Forbidden 오류가 발생합니다.
    • 403 Fobidden 오류가 발생하는 이유
      • Spring Security는 CSRF(Cross-Site Request Forgery) 공격을 방지하기 위해 POST, PUT, DELETE 요청 시 CSRF 토큰을 요구합니다. CSRF 토큰을 제공하지 않으면 보안 위반으로 간주되어 403 Forbidden 오류가 발생합니다. 이를 해결하기 위해서는 .with(SecurityMockMvcRequestPostProcessors.csrf()) 메소드를 사용하여 요청에 CSRF 토큰을 추가해야 합니다.
  • Mockito.when(mapService.route(Mockito.any(Routes.class))).thenReturn("Mocked Kakao API response");:
    • MapService의 route 메소드를 모킹하여, 실제 서비스 로직 대신 미리 정의된 값을 반환하도록 설정합니다.
  • mockMvc.perform(post("/api/map/route")...):
    • /api/map/route 경로로 POST 요청을 보냅니다. 이 때, CSRF 토큰을 포함하고 JSON 형태의 데이터를 전송합니다.
  • status().isOk():
    • 응답이 200 OK인지 검증하는 부분입니다.
  • content().string("Mocked Kakao API response"):
    • 응답 본문이 우리가 모킹한 "Mocked Kakao API response" 문자열과 일치하는지 확인합니다.

 

JUnit 테스트에서 디버깅 가능

JUnit을 사용해 테스트할 때, 디버깅 포인트를 실제 코드의 메소드에 설정하면 디버깅이 가능합니다. 즉, 테스트가 실행되면서 실제 서비스나 컨트롤러의 메소드를 호출할 때 디버깅 포인트에서 코드가 멈추고 상태를 확인할 수 있습니다. 이를 통해 테스트 실행 중에도 코드의 내부 로직을 추적하고 버그를 찾아낼 수 있습니다.