[MockTest] Mocking하여 Controller Test

Hayoon·2023년 8월 2일
0

Spring 정리

목록 보기
7/11

Domain model

프로젝트 진행 중 Toss payments API를 활용하여 카드 결제 시스템을 구현 중이었다. 토스 외부 API 서버를 사용하는 통합테스트가 아닌 단위 테스트를 하려고 했다.

그 이유는 다음과 같다.

  1. 독립성: 외부 서버를 Mocking하면 테스트 시에 외부 서버의 상태나 가용성에 영향을 받지 않는다. 이는 테스트의 안정성과 신뢰성을 높여준다.
  2. 빠른 테스트 속도: 외부 서버를 호출하는 테스트는 네트워크 지연으로 인해 느려질 수 있다. 하지만 Mocking을 통해 실제 네트워크 호출을 회피하면 테스트가 더 빠르게 실행된다.
  3. 테스트 제어: Mocking을 통해 응답을 조작할 수 있으므로 다양한 시나리오를 시뮬레이션하여 테스트를 보다 철저하게 수행할 수 있다. 예를 들어, 다양한 오류 조건이나 응답 상태를 강제로 시뮬레이션하여 애플리케이션의 안정성과 견고성을 테스트할 수 있다.
  4. 테스트 환경의 안정성: 외부 서버가 계속해서 변경되는 경우, 테스트 환경이 불안정해질 수 있다. 하지만 Mocking을 사용하면 외부 서버의 변경에 영향을 받지 않으므로 테스트의 일관성을 유지할 수 있다.

Mocking을 하면 위에 도메인 모델을 아래와 같이 바꿀 수 있다.

WebMvcTest Code

@RunWith(SpringRunner.class)
@WebMvcTest(PaymentController.class)
@MockBean(JpaMetamodelMappingContext.class)
@AutoConfigureMockMvc(addFilters = false)
class PaymentControllerTest extends CreatePaymentData {

    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private TossAPIService tossAPIService;
    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void testKeyIn() throws Exception {

        CardPaymentDto.Response mockResponse = new CardPaymentDto.Response(cardPayment, "카드");

        // Serialize the mock response to JSON
        String mockResponseJson = objectMapper.writeValueAsString(mockResponse);

        given(tossAPIService.callExternalAPI(any(CardPaymentDto.Request.class))).willReturn(mockResponseJson);

        CardPaymentDto.Request request = new CardPaymentDto.Request(cardPayment);

        mockMvc.perform(post("/pay/v1/payments/card")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.orderName").value("커피 외 3개"))
                .andExpect(jsonPath("$.orderId", endsWith("00000")))
                .andExpect(jsonPath("$.totalAmount").value("3000.0"))
                .andExpect(jsonPath("$.method").value("카드"))
                .andExpect(jsonPath("$.cardNumber", containsString("12341234")));
    }
}

테스트 코드에서 사용된 mockMvc는 Spring MVC의 Mock 객체로 컨트롤러를 테스트하기 위한 가상의 요청과 응답을 처리하는 데 사용된다.tossAPIServiceMock 객체로 대체되어 있으며, given(tossAPIService.callExternalAPI(any(CardPaymentDto.Request.class))).willReturn(mockResponseJson) 구문을 통해 callExternalAPI 메서드가 호출될 때 가짜 응답 데이터를 반환하도록 설정한다. 이렇게 함으로써 외부 API 호출과 관련된 로직을 분리하고 테스트할 수 있다.
(@WebMvcTest를 통해 지정한 Bean이 갖는 의존성은 @MockBean으로 등록해야 한다. -> 외부 API서버를 호출하는 tossAPIService는 MockBean으로 지정)

마주친 문제들

  1. Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty!

@WebMvcTest는 JPA 생성과 관련된 기능이 전혀 존재하지 않는 테스트 어노테이션이다.

JPA Auditing 기능을 사용할때  @SpringBootApplication에 @EnableJPaAuditing을 추가하여 사용하고 있기 때문에 발생하는 에러이다.

@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {

} // 따로 @Configration으로 Bean 설정해주면 테스트 클래스 일일이 어노테이션 붙일 필요 없음
  1. forbidden 403 Authorized Error
MockHttpServletResponse:
           Status = 403
    Error message = null
          Headers = [Content-Type:"text/plain"]
     Content type = text/plain
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

컨트롤러는 모델 단의 테스트와 다르게 서블릿과 연관된 부분이 많아 컨텍스트를 로드하고 테스트한다.
컨트롤러 테스트는 컨텍스트를 올리고 진행하기 때문에 security, filter, config 등의 필터문제로 @AutoConfigureMockMvc 어노테이션을 통해 filter, 웹 드라이버 등을 지정해 줄 수 있기에 @AutoConfigureMockMvc(addFilters = false)로 처리하면 CORS FIlter에서 Origin을 * 로 처리하고 호출하면 정상적 작동한다. 하지만 cors는 와일드카드(*)로 모든 출처 허용해주게 된다면 보안성이 낮아지므로 출처를 명시해주는게 좋다고 생각한다.

@Override
public void addCorsMappings(CorsRegistry registry) {
  registry.addMapping("/**")
      .allowedMethods("*")
    .allowedOrigins("http://localhost:8080","http://localhost:3000");
}
mockMvc.perform(post("/pay/v1/payments/card")
                        .contentType(MediaType.APPLICATION_JSON)
                        ...; // (중략)
                        

초록색 ✅

이외의 외부 API 테스트 방법은 없나?

MockWebServer는 주로 HttpClient보다는 WebClient와 같은 라이브러리를 사용하여 웹 클라이언트를 구현할 때에 많이 사용된다.

MockWebServer은 실제로는 외부 API 호출을 시뮬레이트하는 가짜 웹 서버이기 때문에, 웹 클라이언트 라이브러리가 MockWebServer와 통신하면서 가짜 응답을 받을 수 있다. 따라서 웹 클라이언트 라이브러리의 동작을 테스트하고 싶을 때에는 MockWebServer를 사용하는 것이 가장 효율적이다.

하지만 HttpClient를 사용하는 경우에는 직접적인 외부 API 호출이 이루어지기 때문에, MockWebServer를 사용하지 않고도 Mock 객체를 활용하여 테스트할 수 있다. Mocking을 사용하면 외부 의존성을 제거하고 원하는 방식으로 응답을 가짜 데이터로 설정할 수 있으므로, 테스트를 더 쉽게 구현할 수 있다.

profile
Junior Developer

1개의 댓글

comment-user-thumbnail
2023년 8월 2일

정보 감사합니다.

답글 달기