앞서 작성한 테스트 코드로는 중복이 많이 발생할 수 있어서 이를 방지하기 위해 리팩토링을 진행하였다.
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");
}
}
}
@WebMvcTest
원하는 Controller class를 지정하여 테스트 어노테이션을 붙여준다.
@MockBean
Controller에서 DI하는 또는 필요한 class를 MockBean으로 등록한다.
build - generated-snippets - 클래스이름 디렉토리 - 메서드이름 디렉토리 안에 snippet들이 생선된 것을 확인한다.
위의 테스트에서는 'group-create' - 'success'안에 스니펫들이 생성된다.
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']
api 제목 위에 커서를 두고 alt+enter를 누르면 아래와 같은 버튼이 나온다.
Extract include Directive를 선택하여 문서를 분리한다. 이후의 메서들은 이 문서에 작성한다.
*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");
}