Spring Rest Docs 적용 3 - Controller Test 작성을 통한 문서화

2025. 7. 22. 23:45·Java & Spring

이번 포스팅에서는 지난번 문서화 설정에 이어 어떻게 테스트 코드를 작성하여 문서화를 할 수 있는지에 대해 다뤄보고자 합니다.

 

앞 게시물 에서는 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 에 어떻게 표기가 되는지까지 살펴봤습니다.

 

끝으로 프로젝트에 도입하면서 느꼈던 장점과 적용 과정에서 발생한 이슈에 정리 후 마치려 합니다.

 

장점

  1. 비즈니스 로직과 문서화 코드 분리
    Swagger 문서 관련 설정과 코드가 비즈니스 로직과 분리되어 있어 가독성이 향상되고, 유지보수가 용이해졌습니다.
  2. 신뢰도 높은 API 명세 제공
    테스트 기반 문서화 방식을 통해, 실제 동작하는 API를 기준으로 명세가 생성되어 신뢰도가 높습니다.
  3. 테스트 작성 유도 및 품질 향상
    문서 생성을 위해 테스트 코드 작성이 선행되기 때문에, 자연스럽게 테스트 작성 문화가 정착되었습니다.
  4. 팀 차원의 안정성 증가
    초기에는 문서화에 어려움을 겪던 팀원들도 점차 익숙해졌고, 그 결과 시스템 전반의 안정성이 향상되었습니다.
    특히 컨트롤러 레벨의 예외로 인한 버그가 눈에 띄게 줄었습니다.

 

발생한 이슈

  1. 학습 곡선에 따른 초기 개발 속도 저하
    모든 개발자가 테스트 기반 문서화에 익숙해질 때까지 일정 수준의 학습 시간이 필요했고, 이에 따라 초기에는 개발 속도가 다소 느려졌습니다.
  2. Mock 데이터 및 대형 객체 관리의 어려움
    요청/응답 객체가 복잡하거나 크기가 클 경우, 테스트 코드 내에서 데이터를 직접 관리하면 유지보수에 어려움이 있었습니다.

대응 방안 및 개선

  1. JSON 외부 파일 분리
    크기가 큰 요청/응답 데이터는 .json 파일로 분리하여 관리함으로써 테스트 코드의 가독성과 재사용성을 높였습니다.
  2. 공통 객체 및 설정의 모듈화
    자주 사용되는 요청/응답 스펙이나 문서 설정은 공통화하여 중복을 제거하고, 관리 편의성을 향상시켰습니다.

 

 

'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
'Java & Spring' 카테고리의 다른 글
  • Java Enum 다형성으로 Notification 처리 리팩토링하기 - 조건문 없는 전략 설계
  • Spring Cloud Config 도입기: 구성부터 실시간 설정 반영까지
  • Spring Rest Docs 적용 2 - Swagger 연동
  • Spring Rest Docs 적용
jaess
jaess
jaess 님의 블로그 입니다.
  • jaess
    개발하는 개발자
    jaess
  • 전체
    오늘
    어제
    • 분류 전체보기 (9)
      • 회고 (1)
      • API 설계 (1)
      • 생각정리 (0)
      • Java & Spring (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    junit5
    이벤트 리스너
    비동기 테스트
    비동기이벤트
    @Async
    RestDoc
    Spring Cloud Config Client
    TransactionalEventListener
    백엔드개발
    분기처리개선
    Git 설정 관리
    Spring Event
    스프링이벤트
    Spring Actuator
    springboot
    비동기 메시징
    Spring cloud
    CleanCode
    ifelse지옥탈출
    java
    REST API
    개발자
    코드리팩토링
    무중단설정변경
    Spring Cloud Config
    Awaitility
    Spring boot
    Spring Cloud Config Server
    Spring
    OOP (Object Oriented Programming)
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
jaess
Spring Rest Docs 적용 3 - Controller Test 작성을 통한 문서화
상단으로

티스토리툴바