728x90

현재 스웨거 문서로만 적용이 되어 있는데 아무래도 문서 명세서로 보기에는 restdocs이 편할 듯하여 내가 주로 담당하는 리소스에 적용할려고 한다.

일단 스웨거와 restDocs 의 차이

 

  • Spring Rest Dcos
    • 장점
      • 실제 코드에 영향이 없음
      • 테스트코드 성공시에만 문서작성이 완료됨.
    • 단점
      • 적용하기 어려움
  • Swagger
    • 장점
      • API 테스트화면 제공
      • 적용이 매우 쉬운편
    • 단점
      • 실제 코드(프로덕션)에 어노테이션 추가
      • 실제코드와 동기화 안될 수 있음

 

와 같은 특징이 있지만 실제로 써본 결과 프론트 엔드 쪽 작업자가 있는 경우 스웨거가 있는것이 좀더 편하다고 하는사람이 많았다.
그러나 퇴사자 혹은 입사자가 많이 생기는 경우 restDoc 문서를 만들어 두는것이  파악하기에는 훨씬 좋다고 느껴져
2가지 다 적용이 되면 좋을 것 같다.

 

 

 

restDocs 작성 방법

작성에 앞서 몇까지 선택 해야하는 것들이 있는데
1. AsciiDoc VS Markdown => 일단 가이드 문서에 있는 adoc을 사용 했는데 markdown도 사용이 가능 한걸로 확인했다.
장점은 작성이 쉽다는데 공식문서를 따르기로 했다.

2. MockMvc(@WebMvcTest) VS Rest Assured(@SpringBootTest) => 문서 작성시 Mocking을 사용하여 작성
Rest Assured 는 BDD 스타일로 직관적이지만 별도의 구성없이는 @SpringBootTest 로 수행
전체 컨테스트를 로드하여 빈을 주입하기에 속도가 많이 느림.
MockMvc 는 @WebMvcTest 수행  controller Layer만 테스트하여 속도가 빠름 
단순 컨트롤러 테스트만 할 경우 MockMvc가 좋고 아닌 경우 는 Rest Assured가 좋음

JUNIT 외에  Spock라는게 있는데 작업을 할 프로젝트에는 Jnut 이기때문에 고려하지 않기로 한다.

 

 

적용 Project 버전 확인

Sprting boot  2.6.7
java 17
gradle 7

 

 

build.gradle 설정 추가

실제 적용된 코드 

id "org.asciidoctor.jvm.convert" version "3.3.2" //asciidoc 파일 변환 및  복사 플러그인 

configurations {
    asciidoctorExt 
}

ext {// snippets 파일이 저장될 경로 변수 설정
    snippetsDir = file('build/generated-snippets')
}


//dependency 추가
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:2.0.6.RELEASE'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:2.0.6.RELEASE'

test {// 테스트 실행 시 snippets를 저장할 경로로 snippetsDir 사용
	outputs.dir snippetsDir
}

// API 문서 생성
asciidoctor {
    dependsOn test
    inputs.dir snippetsDir
    attributes 'snippets': snippetsDir
}

tasks.register('copyDocument', Copy) {
    dependsOn asciidoctor
    from file("build/docs/asciidoc")
    into file("src/main/resources/static/docs")
}

build {
    dependsOn copyDocument
}
bootJar {
    dependsOn asciidoctor
    copy {
        from "${asciidoctor.outputDir}"
        into 'src/main/resources/static/docs'
    }

}

 

 

TestCode 작성

//실제 작성된 프로젝트 적용됨으로 컨트롤러와DTO 등 추가로 작성이 필요함

@AutoConfigureMockMvc // -> webAppContextSetup(webApplicationContext)
@AutoConfigureRestDocs // -> apply(documentationConfiguration(restDocumentation))
@SpringBootTest // @WebMvcTest(NaverOrderController.class) 사용하여도 무관
class NaverOrderControllerTest {
		@Autowired
    private MockMvc mockMvc;
		@MockBean
    private NaverOrderService naverOrderService;
		
		@Test
    @DisplayName("메뉴얼 네이버 상세 호출 RestDoc")
    void callManualNaverProductOrderChangedRestDoc() throws Exception {
			mockMvc.perform(patch("/api/partner/v1/naver/order/hope-delivery")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(arrivalDateRequest))
                        .accept(MediaType.APPLICATION_JSON)
                )
                .andDo(print())
                .andExpect(status().isOk())
                .andDo(document("hopeDeliveryRequest"
                        , preprocessRequest(prettyPrint())
                        , preprocessResponse(prettyPrint())
                        , requestFields(
                                fieldWithPath("data").type(JsonFieldType.ARRAY).description("data List")
                                , fieldWithPath("data[].orderSrl").type(JsonFieldType.STRING).description("의뢰번호")
                                , fieldWithPath("data[].productOrderId").type(JsonFieldType.STRING).description("상품주문번호")
                                , fieldWithPath("data[].hopeDeliveryYmd").type(JsonFieldType.STRING).description("희망일자")
                                , fieldWithPath("data[].hopDeliveryHm").type(JsonFieldType.STRING).description("희망시간").ignored()
                                , fieldWithPath("data[].region").type(JsonFieldType.STRING).description("지역").ignored()
                                , fieldWithPath("data[].changeReason").type(JsonFieldType.STRING).description("변경사유")
                                , fieldWithPath("data[].accountId").type(JsonFieldType.STRING).description("판매사계정")
                                )
                        , responseFields(
                                fieldWithPath("successList").type(JsonFieldType.ARRAY).description("success data")
                                , fieldWithPath("[]").description("성공주문번호").optional().type(JsonFieldType.STRING)
                                , fieldWithPath("failList").type(JsonFieldType.ARRAY).description("fail data")
                                ,fieldWithPath("failList[].productOrderId").type(JsonFieldType.STRING).description("실패 상품 주문 번호")
                                ,fieldWithPath("failList[].code").type(JsonFieldType.STRING).description("실패 코드")
                                ,fieldWithPath("failList[].desc").type(JsonFieldType.STRING).description("실패 설명")
                                )
                        )
                );
		}

//파라메터와 response 객체 및 호출 mocking이 필요함

}

 

위 테스트코드 실행 시 아래와 같은 파일들이 생성된다.

 

이후 해당 파일을 추출해서 만들려면 adoc 파일이 필요하다.

 

 

참고문서

https://spring.io/guides/gs/testing-restdocs/

728x90
728x90

신규 API를 작성하여 배포할 일이 생겨 복습겸 스웨거 어노테이션을 정리하고자 한다.

@ApiOperation(
        value = "유저 정보 조회"
        , notes = "유저의 ID 를 통해 해당 아이디의 상세 정보 조회 한다.")
@ApiImplicitParams(
        {
            @ApiImplicitParam(
                name = "id"
                , value = "자격증 아이디"
                , required = true
                , dataType = "string"
                , paramType = "path"
                , defaultValue = "None"
            )
        ,
            @ApiImplicitParam(
                name = "fields"
                , value = "응답 필드 종류"
                , required = false
                , dataType = "string"
                , paramType = "query"
                , defaultValue = ""
            )
        })
		@ApiResponses({
        @ApiResponse(code = 200, message = "성공입니다.")
        , @ApiResponse(code = 400, message = "접근이 올바르지 않습니다.")
    })
    @GetMapping("/user/{id}")
    public  ResponseEntity<UserResponse> getUser(@PathVariable(name = "id") String id) {
        return userService.findUserInfoById(id);
    }

 

@ApiOperation = Method 설명

  • Controlloer 안에 method 의 설명을 추가할 수 있다.

 

@ApiImplicitParam = RequestParaemter 설명

  • 해당 API Method 호출에 필요한 Paraemter들의 설명르 추가할 수 있다.
  • ApiImplicitParams을 통해 복수개의 정보를 사용 할 수 있다.

 

@ApiResponse = Response 설명

  • 해당method의 Response에 대한 설명을 작성할 수 있다.
  • 복수개의 Response 에 대한 설명을 추가하고 싶다면, ApiResponse를 통해 사용 가능하다.

 

 

스웨거의 기본 Response message 삭제방법

 

  • 위와 같이 기본 response 가 노출이 되는데 이를 제거 하기 위헤서는 Swagger config에 Docket에 useDefaultResponseMessages(false)를 설정해주면 된다. (설정 시 401, 403, 404 응답이 사라진다.)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UserRequest {
    
    @ApiModelProperty(
        name = "id"
        , example = "gillog"
    )
    @ApiParam(value = "사용자 ID", required = true)
    private String id;

		@ApiModelProperty(example = "teter문")
    @ApiParam(value = "사용자 이름")
    private String name;

    @ApiParam(value = "token 갱신 값")
    private String tokenRefresh;
}

 

@ApiModelProperty = DTO 예제 설명

  • ApiModelProperty를 DTO Class Field에 추가하면, 해당 DTO Field의 예제 Data를 추가할 수 있다.
  • property의 name은 생략할 수 있다.

@ApiParam = DTO field 설명

  • DTO field에 대한 설명을 추가할 수 있다.
  • Controlloer PathVarialbe 기타 파라메터에 대한 설명도 추가 가능
    @ApiIgonre
		@GetMapping("/user/{id}")
    public  ResponseEntity<UserResponse> getUser(@PathVariable(name = "id") String id) {
        return userService.findUserInfoById(id);
    }

 

@ApiIgnore Swagger UI 표기제외

  • Controller 나 메소드를 스웨거 상 비노출 처리 할 수 있다.
  • ApiOperation 옵션 hidden을 true로 만들어서 엔드포인트를 숨길 수 있다.
728x90

+ Recent posts