이번 포스팅에서는 지난번 문서화 설정에 이어 어떻게 테스트 코드를 작성하여 문서화를 할 수 있는지에 대해 다뤄보고자 합니다.
앞 게시물 에서는 Rest Docs 관련 설정에 대한 내용이니 참고 부탁드립니다.
Spring Rest Docs 적용 2 - Swagger 연동
지난번 마지막 포스팅을 한 후 한참이 지나서야 다시 포스팅을 하게 됐습니다. 그래도 시작한 거에 대해 마무리를 지어야 하니 개인적으로 정리했던 내용들을 블로그에 다시 정리해 보려 합니
jaess.tistory.com
- 코드에서 필요한 부분들을 발췌하여 보여드리도록 하겠습니다. Company 라는 엔티티가 있고 해당 엔티티의 생성 및 조회에 대하여 정리해 보도록 하겠습니다.
- 다음은 Controller, Entity, Request, Response 객체들에 대한 코드 입니다
1) Company Entity
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String address;
public static Company create(CompanyCreateRequest request){
return Company.builder().name(request.getCompanyName())
.address(request.getAddress())
.build();
}
}
2) CompanyCreateRequest
@Getter
@Builder
public class CompanyCreateRequest {
@NotEmpty(message = "companyName is required")
private String companyName;
@NotEmpty(message = "address is required")
private String address;
}
3) CompanyCreateResponse
@Getter
@Builder
public class CompanyCreateResponse {
private Long id;
public static CompanyCreateResponse from(Company company) {
return CompanyCreateResponse.builder()
.id(company.getId())
.build();
}
}
4) CompanyResponse
@Getter
@Builder
public class CompanyResponse {
private Long id;
private String companyName;
private String companyAddress;
public static CompanyResponse from(Company company){
return CompanyResponse.builder()
.id(company.getId())
.companyAddress(company.getAddress())
.companyName(company.getName())
.build();
}
}
5) CompanyController
@RestController
@RequiredArgsConstructor
@RequestMapping("/company")
public class CompanyController {
private final CompanyService companyService;
@PostMapping("")
public ResponseEntity<CompanyCreateResponse> createCompany(
@RequestBody
@Validated CompanyCreateRequest request) {
Company createdCompany = companyService.createCompany(Company.create(request));
return ResponseEntity.status(HttpStatus.CREATED)
.body(CompanyCreateResponse.from(createdCompany));
}
@GetMapping("/{id}")
public ResponseEntity<CompanyResponse> findOne(@PathVariable Long id){
return ResponseEntity.ok(CompanyResponse.from(companyService.findOne(id)));
}
}
1. Controller 테스트 코드 작성 전 준비
1) 스프링의 @WebMvcTest 를 사용하여 테스트를 진행합니다.
@AutoConfigureRestDocs //rest docs 문서화를 위한 어노테이션
@WebMvcTest(CompanyController.class)
class CompanyControllerTest {
2) MockMvc 및 @MocktoBean 을 통해 요청 mocking 및 service 객체 mocking 을 해줍니다.
- MockMvc 를 통해 Http 요청을 보내고 응답을 받을 수 있습니다.
- CompanyService 는 Spring Boot가 수행되어 주입되어야 하기 때문에 MockBean 으로 주입해주어 추후 service의 응답을 원하는 결과로 반환하도록 하기 위해 사용합니다.
@Autowired
private MockMvc mockMvc;
@MockitoBean
private CompanyService companyService;
3) 문서화 할 때 그룹화 해줄 리소스 빌더 객체를 선언합니다.
- 해당 객체로 테스트 코드에서 swagger 에 들어갈 내용들을 정의할 수 있습니다.
- tag 에 들어가는 값은 추후 swagger 에서 요청들을 그룹화하여 표현할 때 사용되는 값입니다.
private final ResourceSnippetParametersBuilder resourceBuilder = ResourceSnippetParameters.builder().tag("Company");
2. Controller Test 코드 작성
Get 요청에 대한 테스트 코드
Given
get 요청 테스트 코드에서 사용될 데이터를 준비합니다.
- 응답 요청에 필요한 파라미터
- mocking 된 Service 에서 반환될 객체
Company company = Company.builder().id(1L).name("company name").address("company address").build();
Long companyId = 1L;
given(companyService.findOne(any())).willReturn(company);
When
실제 요청에 대해 수행한다.
ResultActions perform = mockMvc.perform(
get("/company/{id}",companyId)
.contentType(MediaType.APPLICATION_JSON)
);
Then
요청된 결과의 스키마에 대해 응답을 확인한다.
ConstrainedFields fields = new ConstrainedFields(CompanyResponse.class);
perform.andDo(
MockMvcRestDocumentationWrapper.document(
"{class-name}/{method-name}", // identifier
//요청과 응답을 예쁘게 포맷팅하여 찍도록 설정
Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
Preprocessors.preprocessResponse(Preprocessors.prettyPrint()),
resource(resourceBuilder
//swagger 에 들어갈 정보들
.summary("company 조회")
.description("company id를 통해 정보를 조회할 수 있다.")
//응답에 기대되는 responseFields
.responseFields(
fields.withPath("id").type(JsonFieldType.NUMBER).description("companyId"),
fields.withPath("companyName").type(JsonFieldType.STRING).description("company name "),
fields.withPath("companyAddress").type(JsonFieldType.STRING).description("company address")
)
//스웨거에 표기될 스키마 이름
.responseSchema(Schema.schema("CompanyResponse"))
.build())
));
위와 같이 작성할 수 있다.
전체 코드
@Test
@DisplayName("회사를 조회할 수 있다.")
void getCompany() throws Exception {
//given
Company company = Company.builder().id(1L).name("company name").address("company address").build();
Long companyId = 1L;
given(companyService.findOne(any())).willReturn(company);
//when
ResultActions perform = mockMvc.perform(
get("/company/{id}",companyId)
.contentType(MediaType.APPLICATION_JSON)
);
//then
ConstrainedFields fields = new ConstrainedFields(CompanyResponse.class);
perform.andDo(
MockMvcRestDocumentationWrapper.document(
"{class-name}/{method-name}", // identifier
Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
Preprocessors.preprocessResponse(Preprocessors.prettyPrint()),
resource(resourceBuilder
.summary("company 조회")
.description("company id를 통해 정보를 조회할 수 있다.")
.responseFields(
fields.withPath("id").type(JsonFieldType.NUMBER).description("companyId"),
fields.withPath("companyName").type(JsonFieldType.STRING).description("company name "),
fields.withPath("companyAddress").type(JsonFieldType.STRING).description("company address")
)
.responseSchema(Schema.schema("CompanyResponse"))
.build())
));
}
해당 테스트 코드를 돌려보면 다음과 같이 성공한 결과를 볼 수 있다.
만약 응답에 대한 필드가 정의가 제대로 되어 있지 않은 경우에는 다음과 같은 예외가 발생합니다
- companyAddress 를 누락한 경우 테스트는 실패하게 되고 메시지에는 해당 필드가 문서화가 되지 않았다면 예외가 나타납니다.
The following parts of the payload were not documented:
{
"companyAddress" : "company address"
}
따라서, 문서화가 되지 않은 필드가 있는 경우 테스트가 실패하므로 제대로된 요청과 응답을 작성하지 않으면 문서화가 되지 않아 문서의 정확성을 더 높일 수 있습니다.
이렇게 해서 테스트 코드 작성해 대해 살펴봤고 실제 문서화가 되면 다음과 같이 get 요청이 문서화 된걸 확인할 수 있습니다.
- 스키마 영역을 보면 작성한 desciption 정보들이 잘 나오는 걸 확인할 수 있습니다.
테스트 코드 작성 결과로 Swagger 에 어떻게 표기가 되는지까지 살펴봤습니다.
끝으로 프로젝트에 도입하면서 느꼈던 장점과 적용 과정에서 발생한 이슈에 정리 후 마치려 합니다.
장점
- 비즈니스 로직과 문서화 코드 분리
Swagger 문서 관련 설정과 코드가 비즈니스 로직과 분리되어 있어 가독성이 향상되고, 유지보수가 용이해졌습니다. - 신뢰도 높은 API 명세 제공
테스트 기반 문서화 방식을 통해, 실제 동작하는 API를 기준으로 명세가 생성되어 신뢰도가 높습니다. - 테스트 작성 유도 및 품질 향상
문서 생성을 위해 테스트 코드 작성이 선행되기 때문에, 자연스럽게 테스트 작성 문화가 정착되었습니다. - 팀 차원의 안정성 증가
초기에는 문서화에 어려움을 겪던 팀원들도 점차 익숙해졌고, 그 결과 시스템 전반의 안정성이 향상되었습니다.
특히 컨트롤러 레벨의 예외로 인한 버그가 눈에 띄게 줄었습니다.
발생한 이슈
- 학습 곡선에 따른 초기 개발 속도 저하
모든 개발자가 테스트 기반 문서화에 익숙해질 때까지 일정 수준의 학습 시간이 필요했고, 이에 따라 초기에는 개발 속도가 다소 느려졌습니다. - Mock 데이터 및 대형 객체 관리의 어려움
요청/응답 객체가 복잡하거나 크기가 클 경우, 테스트 코드 내에서 데이터를 직접 관리하면 유지보수에 어려움이 있었습니다.
대응 방안 및 개선
- JSON 외부 파일 분리
크기가 큰 요청/응답 데이터는 .json 파일로 분리하여 관리함으로써 테스트 코드의 가독성과 재사용성을 높였습니다. - 공통 객체 및 설정의 모듈화
자주 사용되는 요청/응답 스펙이나 문서 설정은 공통화하여 중복을 제거하고, 관리 편의성을 향상시켰습니다.
'Java & Spring' 카테고리의 다른 글
Awaitility로 비동기 이벤트 테스트 하기: Spring @Async와 함께 쓰는 법 (3) | 2025.07.29 |
---|---|
Java Enum 다형성으로 Notification 처리 리팩토링하기 - 조건문 없는 전략 설계 (1) | 2025.07.26 |
Spring Cloud Config 도입기: 구성부터 실시간 설정 반영까지 (3) | 2025.07.24 |
Spring Rest Docs 적용 2 - Swagger 연동 (1) | 2025.07.21 |
Spring Rest Docs 적용 (4) | 2024.10.17 |