목차
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")
)));
}
}
처음하는 문서화 작업에서 정말 공식문서랑 코드들을 수도없이 많이 본 것 같다
참고
'공부기록 > Spring' 카테고리의 다른 글
Spring-Response 공통 포맷 (0) | 2023.01.30 |
---|---|
Spring-Rest Docs(3) (0) | 2022.08.16 |
Spring-Rest Docs(1) (0) | 2022.08.09 |
Spring boot CORS 설정 (0) | 2022.08.03 |
DTO관련 고민 (0) | 2022.07.13 |