공부기록/Spring

Spring-Rest Docs(2)

jhs0129 2022. 8. 11. 14:49
320x100
반응형

목차

  1. Spring-Rest Docs(1)
  2. Spring-Rest Docs(2) <- 현재글
  3. Spring-Rest Docs(3)

Rest Docs을 적용하면서 json형식을 보기 좋게 표시하기 위해서 preprocessRequest
preprocessResponse을 계속해서 작성을 하고 documentation 저장 위치를 직접 작성을 하는 부분에서 반복적인 작업이라 생각이 들어 추가적으로 구글링과 공식문서를 참고해서 수정을 하였다

Parameterized Output Directories

  • {methodName}
  • {method-name}
  • {method_name}
  • {ClassName}
  • {class-name}
  • {class_name}

위의 6개를 사용해서 저장 위치를 변수화 시켜 사용할 수 있었다

같은 methodName이라도 {methodName}는 본래의 이름 그대로 ,{method-name}는 kebab case로 ,{method_name}는 snake_case로 directory 이름이 지정이 된다

{method-name} 형식을 선택해서 사용했는데 항상 package나 directory경우 대문자를 사용하지 않았기 때문인 것과 언더바 와 하이픈중에서는 단순히 설정파일에서 generated-snipptes로 하이픈을 사용했기에 동일하게 사용을 하였다

공식문서 샘플 예시를 보게되면 아래와 같은 코드를 볼수 있다

아래 코드를 통해서 추가적인 documentation작업 없이도 기본 default snippets 6개를 생성할 수 있게 된다

@Before
public void setUp() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
        .apply(documentationConfiguration(this.restDocumentation))
        .alwaysDo(document("{method-name}/{step}/",
                preprocessRequest(prettyPrint()),
                preprocessResponse(prettyPrint())))
        .build();
}

하지만 default snippets 6개만이 아니라 requestParameters, responseFields와 같이 추가적인 요소들도 문서화를 하길 원한다

이를 위해서는 alwaysDo안에 document를 따로 Bean으로 미리 등록하여 사용을 해야한다

스프링에서 설정파일이라하면 @Configuration만을 사용했지마
test-configuration-withtestconfiguration 해당 페이지에서 @TestConfiguration을 사용하는 것을 알게 되어 따로 설정파일을 분리하여 아래와 같이 Bean등록을 했다

@TestConfiguration
public class RestDocsConfig {

    @Bean
    public RestDocumentationResultHandler write(){
        return MockMvcRestDocumentation.document(
            "{class-name}/{method-name}",
            Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
            Preprocessors.preprocessResponse(Preprocessors.prettyPrint())
        );
    }
}
@Import(RestDocsConfig.class)
@ExtendWith(RestDocumentationExtension.class)
public abstract class AbstractRestDocsTests {

    @Autowired
    protected RestDocumentationResultHandler restDocs;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    protected MockMvc mockMvc;

    @BeforeEach
    void setUp(final WebApplicationContext context, final RestDocumentationContextProvider restDocumentation) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .apply(documentationConfiguration(restDocumentation))
                .alwaysDo(MockMvcResultHandlers.print())
                .alwaysDo(restDocs)
                .addFilters(new CharacterEncodingFilter("UTF-8", true))
                .build();
    }

    protected static String toJson(Object dto) throws JsonProcessingException {
        return objectMapper.writeValueAsString(dto);
    }
}

이렇게 따로 Bean등록을 하게되면 추가적인 snippets를 생성하길 원한다면 RestDocumentationResultHandler.document()를 사용해서 작성을 하면 된다

추가적으로 test에서 사용되는 objectMapper또한 같은 곳에서 선언을 해주었다

doucment() 두가지 비교

  • MockMvcRestDocumentation.document()
    public static RestDocumentationResultHandler document(String identifier, Snippet... snippets) {
        return new RestDocumentationResultHandler(
                new RestDocumentationGenerator<>(identifier, REQUEST_CONVERTER, RESPONSE_CONVERTER, snippets));
    }
  • RestDocumentationResultHandler.document()
    public RestDocumentationResultHandler document(Snippet... snippets) {
        return new RestDocumentationResultHandler(this.delegate.withSnippets(snippets)) {
            @Override
            public void handle(MvcResult result) throws Exception {
                Map<String, Object> configuration = new HashMap<>(retrieveConfiguration(result));
                configuration.remove(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS);
                getDelegate().handle(result.getRequest(), result.getResponse(), configuration);
        }};
    }

    public RestDocumentationGenerator<REQ, RESP> withSnippets(Snippet... snippets) {
        return new RestDocumentationGenerator<>(this.identifier, this.requestConverter, this.responseConverter,
            this.requestPreprocessor, this.responsePreprocessor, snippets);
    }

두개 클래스의 메소드를 보게되면 똑같이 RestDocumentationGenerator를 가진RestDocumentationResultHandler객체를 반환하게 된다

차이점은 첫번째 코드는 직접 identifier를 지정을 해주지만

두번째 코드는 앞선 설정파일에서 지정한 "{class-name}/{method-name}"를 사용한다는 점이다

테스트 적용

테스트를 작성을 할때는 만들어둔 AbstractRestDocsTests을 상속받아 사용하기만 하면된다

추가적으로 unit test를 진행을 할 것이기 때문에 @WebMvcTest(MemberController.class) 지정과 @MockBean protected MemberService memberService MockBean의 지정이 필요하다

@WebMvcTest(MemberController.class)
class MemberControllerTest extends AbstractRestDocsTests {

    @MockBean
    protected MemberService memberService;

    @Test
    @DisplayName("getMemberInfo - Get /member?memberId")
    void common() throws Exception {
        //given
        MemberInfoResponse response = MemberInfoResponse.builder()
                .memberId("memberA@naver.com")
                .memberName("memberA")
                .password("memberA")
                .age(10)
                .salary(100)
                .build();
        given(memberService.getMemberInfo("memberA@naver.com")).willReturn(response);

        String memberId = "memberA@naver.com";
        //when
        ResultActions action = mockMvc.perform(get("/member?memberId=" + memberId));

        //then
        SingleResponseData responseData = SingleResponseData.of(response);
        action.andExpect(status().isOk())
                .andExpect(content().json(toJson(responseData)))
                .andExpect(jsonPath("$.data.memberId").exists())
                .andDo(restDocs.document(
                        requestParameters(
                                parameterWithName("memberId").description("memberId to find")
                        ),
                        responseFields(
                                subsectionWithPath("data").description("DATA"),
                                fieldWithPath("statusCode").description("STATUS_CODE"),
                                fieldWithPath("message").description("MESSAGE")
                        )));
    }
}

처음하는 문서화 작업에서 정말 공식문서랑 코드들을 수도없이 많이 본 것 같다

참고

320x100
반응형