이 가이드는 MockMvc@WebMvcTest를 사용하여 컨트롤러 계층을 테스트하는 표준 방법을 정의합니다. 특히 우리 프로젝트의 ResponseAspect(HTTP Status 변환)와 Security 인증 처리를 올바르게 적용하는 데 중점을 둡니다.

1. 테스트 원칙

  1. Slice Test 사용: @SpringBootTest 대신 **@WebMvcTest*를 사용하여 컨트롤러와 관련된 빈(Bean)만 가볍게 로드합니다.
  2. AOP 적용 필수: ResponseDto를 처리하는 Aspect가 동작해야 하므로 @Import(ResponseAspect.class) 가 필수입니다.
  3. Security 수동 주입: 복잡한 설정 대신 Helper 메서드를 통해 필요한 인증 객체를 주입합니다.

2. 표준 테스트 클래스 구조 (Template)

모든 컨트롤러 테스트는 아래 템플릿을 기반으로 작성합니다.

package com.bugzero.rarego.domain.in; // 패키지명 예시

// 1. Static Imports (필수)
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; // csrf(), authentication()
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockitoBean;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.web.servlet.MockMvc;
import com.fasterxml.jackson.databind.ObjectMapper;

// [Core Imports] 우리 프로젝트 클래스
import com.bugzero.rarego.global.aspect.ResponseAspect;
import com.bugzero.rarego.global.security.MemberPrincipal;
// ... DTOs & Facade

// 2. 클래스 레벨 설정
@WebMvcTest(TargetController.class) // 테스트할 컨트롤러 지정
@Import(ResponseAspect.class)       // ★ 핵심: Status 변환 AOP 활성화
@EnableAspectJAutoProxy             // AOP 프록시 활성화
class TargetControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private TargetFacade targetFacade; // Facade/Service Mocking

    @Autowired
    private ObjectMapper objectMapper;

    // 3. 인증 객체 생성 Helper 메서드 (복사해서 사용)
    private Authentication createAuth(String publicId, String role) {
        MemberPrincipal principal = new MemberPrincipal(publicId, role);
        return new UsernamePasswordAuthenticationToken(
            principal,
            null,
            List.of(new SimpleGrantedAuthority("ROLE_" + role))
        );
    }

    // 4. 테스트 케이스 작성...
}

3. 케이스별 작성 방법

Case 1: 성공 케이스 (GET 요청)

@Test
@DisplayName("성공: 조회 요청 시 200 OK 반환")
void read_success() throws Exception {
    // given
    String memberId = "user-1";
    given(targetFacade.readSomething(eq(memberId))).willReturn(responseDto);

    // when & then
    mockMvc.perform(get("/api/v1/resource")
            .param("page", "0")
            .with(authentication(createAuth(memberId, "USER")))) // 인증 주입
        .andDo(print())
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.data.id").value(1L));
}

Case 2: 성공 케이스 (POST/PUT/PATCH/DELETE)

@Test
@DisplayName("성공: 생성 요청 시 201 Created 반환")
void create_success() throws Exception {
    // given
    RequestDto request = new RequestDto("data");
    given(targetFacade.create(any())).willReturn(responseDto);

    // when & then
    mockMvc.perform(post("/api/v1/resource")
            .with(csrf()) // ★ 필수
            .with(authentication(createAuth("admin", "ADMIN")))
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.status").value(201));
}

Case 3: 예외 발생 케이스 (404, 400 등)