[Spring REST Docs] 적용 과정 및 가이드 - 2

Coastby·2023년 1월 29일
0

Daengnyang 프로젝트

목록 보기
12/12
post-custom-banner

앞서 작성한 테스트 코드로는 중복이 많이 발생할 수 있어서 이를 방지하기 위해 리팩토링을 진행하였다.

Group API를 작성하며 관련 controller test를 리팩토링한 구조를 적용하여 만들었다. 

그리고 현재 구조에서의 테스트 코드 작성법 및 REST doc 작성법을 적으려고 한다.

리팩토링한 구조

중복되는 코드들을 ContorllerTest 클래스로 빼내었다.

@WithMockUser
@AutoConfigureRestDocs
@Import(RestDocsConfiguration.class)
@ExtendWith(RestDocumentationExtension.class)
public class ControllerTest {
    @Autowired
    protected MockMvc mockMvc;
    @Autowired
    protected RestDocumentationResultHandler restDocs;

    protected final ObjectMapper objectMapper;
    public ControllerTest(){
        this.objectMapper = new ObjectMapper();
    }
    @BeforeEach
    void setUp(final WebApplicationContext context,
               final RestDocumentationContextProvider provider) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .apply(MockMvcRestDocumentation.documentationConfiguration(provider))  // rest docs 설정 주입
                .alwaysDo(MockMvcResultHandlers.print()) // andDo(print()) 코드 포함
                .alwaysDo(restDocs) // pretty 패턴과 문서 디렉토리 명 정해준것 적용
                .addFilters(new CharacterEncodingFilter("UTF-8", true)) // 한글 깨짐 방지
                .build();
    }
}

그리고 이를 상속하는 GroupControllerTest를 작성한다.

@WebMvcTest(GroupRestController.class)		// 1)
class GroupRestControllerTest extends ControllerTest{
    @MockBean				// 2)
    protected GroupService groupService;

    @Nested
    @DisplayName("그룹 생성")
    class GroupCreate{
        GroupMakeRequest groupMakeRequest = new GroupMakeRequest("그룹이름", "엄마");
        GroupMakeResponse groupMakeResponse = GroupMakeResponse.builder()
                .id(1L)
                .name("그룹이름")
                .ownerId(1L)
                .ownerUserName("user")
                .build();


        @Test
        @DisplayName("그룹 생성 성공")
        void success() throws Exception {
            given(groupService.create(groupMakeRequest, "user")).willReturn(groupMakeResponse);

            mockMvc.perform(
                            post("/api/v1/groups")
                                    .content(objectMapper.writeValueAsBytes(groupMakeRequest))
                                    .contentType(MediaType.APPLICATION_JSON))
                    .andExpect(status().isCreated())
                    .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                    .andExpect(jsonPath("$.result.ownerId").value(1L))
                    .andDo(
                            restDocs.document(			// 3)
                                    requestFields(
                                            fieldWithPath("name").description("그룹 이름"),
                                            fieldWithPath("roleInGroup").description("그룹 내 역할")
                                    ),
                                    responseFields(
                                            fieldWithPath("resultCode").description("결과코드"),
                                            fieldWithPath("result.id").description("그룹 번호"),
                                            fieldWithPath("result.name").description("그룹 이름"),
                                            fieldWithPath("result.ownerId").description("그룹주인 번호"),
                                            fieldWithPath("result.ownerUserName").description("그룹주인 아이디"))
                            )
                    );
            verify(groupService).create(groupMakeRequest, "user");
        }
    }
}

Controller Test 작성 방법

1) @WebMvcTest

원하는 Controller class를 지정하여 테스트 어노테이션을 붙여준다.

2) @MockBean

Controller에서 DI하는 또는 필요한 class를 MockBean으로 등록한다.

3) api 문서를 만드는 코드를 작성한다. 

REST doc 작성 방법

1) 위의 테스트를 작성하고 통과한다.

2) 스니펫 생성된 것을 확인한다.

build - generated-snippets - 클래스이름 디렉토리 - 메서드이름 디렉토리 안에 snippet들이 생선된 것을 확인한다.

위의 테스트에서는 'group-create' - 'success'안에 스니펫들이 생성된다.

3) 문서를 작성한다.

src - docs - asciidoc - index.adoc 에 아래 형식으로 문서를 붙여 넣는다.

[[엔티티이름-API]]	//link
== API 이름		 //api 제목

[[엔티티-기능]]
=== 엔티티 기능 제목	//method 제목
operation::만들어진 디렉토리 경로[snippets='http-request,request-fields,http-response,response-fields']

예시)

[[Group-API]]
== Group API

[[Group-만들기]]
=== Group 그룹 만들기
operation::group-create/success[snippets='http-request,request-fields,http-response,response-fields']

4) api 단위로 문서를 분리한다.

api 제목 위에 커서를 두고 alt+enter를 누르면 아래와 같은 버튼이 나온다.

Extract include Directive를 선택하여 문서를 분리한다. 이후의 메서들은 이 문서에 작성한다.

🚫 pathVariable이 있는 get 테스트

*pathParameters*를 추가하면 아래와 같은 에러가 나온다.

urlTemplate not found. If you are using MockMvc did you use RestDocumentationRequestBuilders to build the request?

path variable을 표시하기 위해서는 MockMvcBuilders보다 RestDocumentationRequestBuilders를 이용하는 것이 좋다고 한다.

따라서 static 메서드 앞에 클래스를 명시해주고 url template 도 수정해준다.

@Test
@DisplayName("그룹 생성 성공")
void success() throws Exception {
    GroupUserListResponse groupUserResponse = new GroupUserListResponse(
            List.of(new GroupUserResponse(1L, "user", "mom", true),
                    new GroupUserResponse(2L, "user2", "dad", false)),
            2);
    given(groupService.getGroupUsers(1L, "user")).willReturn(groupUserResponse);

    mockMvc.perform(
                    RestDocumentationRequestBuilders.get("/api/v1/groups/{groupId}/users", 1L)
                            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
            .andExpect(jsonPath("$.result.users").exists())
            .andDo(
                    restDocs.document(
                            pathParameters(
                                    parameterWithName("groupId").description("그룹 번호")
                            ),
                            responseFields(
                                    fieldWithPath("resultCode").description("결과코드"),
                                    fieldWithPath("result.users").description("그룹 내 유저 리스트"),
                                    fieldWithPath("result.users[].id").description("유저 번호"),
                                    fieldWithPath("result.users[].userName").description("유저 아이디"),
                                    fieldWithPath("result.users[].roleInGroup").description("그룹 내 역할"),
                                    fieldWithPath("result.users[].owner").description("그룹장 여부"),
                                    fieldWithPath("result.count").description("그룹 내 유저 수"))
                    )
            );
    verify(groupService).getGroupUsers(1L, "user");
}
profile
훈이야 화이팅
post-custom-banner

0개의 댓글