Project/Boilerplate

WebMvcTest + Spring REST doc + Swagger 적용 ( 2 / 2 - Spring REST doc + Swagger )

조용우 2025. 3. 29. 08:59
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.3'
    id 'io.spring.dependency-management' version '1.1.7'
    id 'com.epages.restdocs-api-spec' version '0.19.4' // ✅ OpenAPI spec 생성용
}

dependencies {
    // ✅ REST Docs
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

    // ✅ OpenAPI 3 스펙 변환 라이브러리
    testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.19.4'
}

openapi3 {
    servers = [
        { url = 'http://localhost:8080' }
    ]
    title = 'Boilerplate API'
    description = 'Boilerplate API description'
    version = '1.0.0'
    format = 'json'
    outputFileNamePrefix = 'openapi3'
    outputDirectory = file("build/api-spec") // 🔹 openapi3.json 생성 위치
}

의존성 추가

 

✅ 실제 적용 코드

@AutoConfigureRestDocs(outputDir = "build/generated-snippets") // ✅ 추가
@WebMvcTest(JoinController.class)
@DisplayName("유저 회원가입 Controller")
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@AutoConfigureMockMvc(addFilters = false)
class JoinControllerTest {

    @MockitoBean
    private JoinService joinService;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    private Long id = 1L;
    private String email = "email@email.com";
    private String username = "email";
    private String password = "password";

    @Nested
    class 회원가입_성공 {

        @Test
        void 회원가입_성공_정상_회원가입() throws Exception {
            // given
            JoinRequest request = new JoinRequest(email, password);
            JoinResponse response = JoinResponse.builder()
                .id(id)
                .username(username)
                .build();
            given(joinService.join(any(JoinRequest.class))).willReturn(response);

            // when & then
            mockMvc.perform(post("/api/join")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value(id))
                .andExpect(jsonPath("$.username").value(username))
                .andDo(document("join-success", // ✅ 스니펫 이름
                    resource(
                        ResourceSnippetParameters.builder()
                            .tag("회원가입")
                            .summary("회원가입 API")
                            .description("회원가입 후 사용자 ID와 이름을 반환")
                            .requestFields(
                                fieldWithPath("email").description("이메일"),
                                fieldWithPath("password").description("비밀번호")
                            )
                            .responseFields(
                                fieldWithPath("id").description("유저 ID"),
                                fieldWithPath("username").description("사용자 이름")
                            )
                            .requestSchema(schema("JoinRequest"))
                            .responseSchema(schema("JoinResponse"))
                            .build()
                    )
                ));
        }
    }
}

 

document 의존성 변경

import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;

 

테스트 실행으로 OpenAPI 스펙 생성

./gradlew clean test openapi3

 

openapi3.json 명세 파일 src/main/resources/static/docs로 복사

mkdir -p src/main/resources/static/docs
cp build/api-spec/openapi3.json src/main/resources/static/docs/openapi3.json

 

도커 실행

docker rm -f swagger-ui # 기존 컨테이너 제거 (optional)

docker run -d -p 8081:8080 `
  -e "SWAGGER_JSON=/tmp/openapi3.json" `
  -v "$PWD\src\main\resources\static\docs:/tmp" `
  --name swagger-ui `
  swaggerapi/swagger-ui

 

자동화 스크립트 ps1

# run-swagger.ps1

Write-Host "📦 Running tests and generating OpenAPI spec..."
./gradlew.bat clean test openapi3

if (!(Test-Path -Path "build\api-spec\openapi3.json")) {
    Write-Host "❌ openapi3.json not found! Check your REST Docs generation."
    exit 1
}

Write-Host "📄 Copying OpenAPI spec to static docs folder..."
New-Item -ItemType Directory -Force -Path "src\main\resources\static\docs" | Out-Null
Copy-Item "build\api-spec\openapi3.json" "src\main\resources\static\docs\openapi3.json" -Force

Write-Host "🧼 Cleaning up old Swagger UI container (if exists)..."
docker rm -f swagger-ui | Out-Null

Write-Host "🚀 Starting Swagger UI on http://localhost:8081"

$swaggerJsonPath = "$PWD\src\main\resources\static\docs"

docker run -d -p 8081:8080 `
  -e "SWAGGER_JSON=/tmp/openapi3.json" `
  -v "${swaggerJsonPath}:/tmp" `
  --name swagger-ui `
  swaggerapi/swagger-ui

Write-Host "✅ Swagger UI is now available at: http://localhost:8081"