프로젝트/기술 면접 복습 플랫폼
단위 테스트 + API 문서화(MockMvc, RestDocs)
yoon4360
2025. 4. 25. 19:54
프로젝트를 진행하면서 테스트는 해야겠는데, 어디까지 어떻게 해야 하지? 라는 고민이 앞섰다.
그리고 API 문서는 RestDocs가 낫나, Swagger가 낫나? 라는 질문에는
둘 다 해보지 각 문서의 장단점을 비교해 보자는 생각을 했다.
이번 글에서는 DevInterview 프로젝트를 진행하며,
단위 테스트 → API 문서화 흐름을 어떻게 설계했는지 정리해보려 한다.
단위 테스트 – 서비스 테스트
목적: 서비스 메서드가 정상 동작하는지 검증하는 것이다.
auth, qna, writing, review, user 여러가지 도메인이 있지만 그 중 가장 중요하고 핵심이라고 생각되는
인증 도메인인 auth와 면접 질문을 관리하는 qna 도메인을 위주로 설명해보겠다.
1. AuthService 테스트
테스트 포인트
- 로그인 성공 시, 유저 인증 후 토큰 발급되는가?
- 비밀번호 불일치 시 예외 발생하는가?
- 존재하지 않는 이메일일 때 예외 발생하는가?
- 리프레시 토큰 재발급 로직이 정상 동작하는가?
- 리프레시 토큰 불일치/만료 시 예외 발생하는가?
테스트 방식
- Repository, JWT Provider 등 외부 의존성을 Mock 처리한다.
- 정상 로직 + 다양한 예외 상황을 테스트한다.
- 리턴되는 값, 상태 검증 + 메서드 호출 여부를 검증한다.
// 로그인 성공 테스트
@Test
void login_success() {
//given
String email = "test@example.com";
String password = "password";
UUID userId = UUID.randomUUID();
String encodedPassword = "encodedPassword";
User user = User.builder()
.id(userId)
.email(email)
.password(encodedPassword)
.build();
JwtToken token = new JwtToken("access-token", "refresh-token");
when(userRepository.findByEmail(email)).thenReturn(Optional.of(user));
when(passwordEncoder.matches(password, encodedPassword)).thenReturn(true);
when(jwtTokenProvider.generateTokens(userId)).thenReturn(token);
// when
LoginResponse response = authService.login(new LoginRequest(email, password));
// then
assertEquals(userId, response.userId());
assertEquals("access-token", response.accessToken());
assertEquals("refresh-token", response.refreshToken());
verify(refreshTokenRepository).save(any(RefreshToken.class));
}
2. QnaService 테스트
테스트 포인트
- 질문 저장 시 사용자/글 검증 후 정상 저장되는가?
- 질문 삭제 시, 본인 여부 확인 후 soft delete 되는가?
- 복습 대상 질문 리스트 정확히 조회되는가?
테스트 방식
- UserRepository, QnaRepository를 Mock 처리한다.
- 시간(LocalDateTime) 조건 기반 검증한다.
- 삭제 시 권한 예외 테스트한다.
// 오늘 리뷰 qna 조회
@Test
void getReviewQnasForToday_success() {
UUID userId = UUID.randomUUID();
Qna qna = Qna.builder()
.id(UUID.randomUUID())
.scheduledDate(LocalDateTime.now().minusDays(1))
.isDeleted(false)
.build();
when(qnaRepository.findAllByUserIdAndScheduledDateBeforeAndIsDeletedFalse(eq(userId), any(LocalDateTime.class)))
.thenReturn(List.of(qna));
List<QnaTodayResponse> result = qnaService.getReviewQnasForToday(userId);
assertEquals(1, result.size());
verify(qnaRepository).findAllByUserIdAndScheduledDateBeforeAndIsDeletedFalse(eq(userId), any(LocalDateTime.class));
}
단위 테스트 – 컨트롤러 테스트
목적: API가 외부에 잘 노출되는지 + 문서 자동화
1. AuthController 테스트
테스트 포인트
- /api/auth/login 요청 → JSON 요청/응답 형식을 문서화한다.
- /api/auth/reissue 요청 → 쿼리 파라미터 + 응답 문서화한다.
테스트 방식
- MockMvc 사용, HTTP 요청 시나리오를 테스트한다.
- requestFields, responseFields, queryParameters 문서화한다.
- API 문서 생성을 위한 정적 검증 (RestDocs)한다.
// 코드 재발급 성공 테스트
@Test
@DisplayName("토큰 재발급 API 성공 테스트")
void reissue_success() throws Exception {
// given
String oldRefreshToken = "old-refresh-token";
String newAccessToken = "new-access-token";
String newRefreshToken = "new-access-token";
JwtToken newToken = new JwtToken(newAccessToken, newRefreshToken);
when(authService.reissue(oldRefreshToken)).thenReturn(newToken);
// when & then
mockMvc.perform(post("/api/auth/reissue?refreshToken="+oldRefreshToken)
.contentType(MediaType.APPLICATION_JSON))
.andDo(document("reissue-success",
queryParameters(
parameterWithName("refreshToken").description("기존 리프레시 토큰")
),
responseFields(
fieldWithPath("accessToken").description("새로운 엑세스 토큰"),
fieldWithPath("refreshToken").description("새로운 리프레시 토큰")
)
))
.andExpect(status().isOk())
.andExpect(jsonPath("$.accessToken").value(newAccessToken))
.andExpect(jsonPath("$.refreshToken").value(newRefreshToken));
}
QnaController 테스트
테스트 포인트
- /api/qna/{writingId} POST → 질문 저장 API 요청/응답을 문서화한다.
- /api/qna/today GET → 쿼리 파라미터 기반 조회를 문서화한다.
- /api/qna/user/{userID} GET → PathVariable 문서화한다.
테스트 방식
- MockMvc + RestDocs 조합
- Path Parameters, Query Parameters, Response 구조 상세 문서화한다.
- 문서화를 위한 예외 없이 성공 케이스 중심 테스트
// 질문 저장 성공 테스트
@Test
@DisplayName("GPT 질문저장 API 성공테스트")
void createQna_success() throws Exception {
//given
UUID writingId = UUID.randomUUID();
UUID userId = UUID.randomUUID();
String question = "Spring 이란?";
String answer = "Spring 은 자바 웹 프레임워크입니다.";
QnaCreateRequest request = new QnaCreateRequest(userId, question, answer);
Qna saved = Qna.builder()
.id(UUID.randomUUID())
.user(User.builder().id(userId).build())
.writing(Writing.builder().id(writingId).build())
.question(question)
.answer(answer)
.scheduledDate(LocalDateTime.now())
.isDeleted(false)
.build();
when(qnaService.saveQna(eq(userId), eq(writingId), eq(question), eq(answer), any(LocalDateTime.class)))
.thenReturn(saved);
// when & then
mockMvc.perform(post("/api/qna/{writingId}", writingId)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andDo(document("qna-create-success",
pathParameters(
parameterWithName("writingId").description("작성 글 ID")
),
requestFields(
fieldWithPath("userId").description("사용자 ID"),
fieldWithPath("question").description("질문 내용"),
fieldWithPath("answer").description("질문에 대한 답변")
),
responseFields(
fieldWithPath("qnaId").description("저장된 QnA ID")
)
))
.andExpect(status().isOk())
.andExpect(jsonPath("$.qnaId").value(saved.getId().toString()));
}
테스트 정리
테스트 3단계 흐름
- given: 테스트에 필요한 값/상태 준비한다.
- when: 테스트 대상 코드를 실행한다.
- then: 결과를 검증한다. (예상 결과 vs 실제 결과)
테스트 도구
MockMvc | 가짜 HTTP 요청/응답 수행 |
Mockito | 서비스, 레포지토리 Mocking |
ObjectMapper | 객체 ↔ JSON 변환 |
REST Docs
동작 흐름
- 테스트 코드 안에서 .andDo(document(...)) 작성
- 테스트 성공 시 → generated-snippets 폴더에 adoc 파일 생성
- index.adoc 작성 후 asciidoctor 빌드
- 정적 HTML 문서 생성 (예: build/docs/asciidoc/index.html)
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userDto)))
.andDo(document("user-create",
requestFields(
fieldWithPath("email").description("이메일"),
fieldWithPath("password").description("비밀번호")
),
responseFields(
fieldWithPath("id").description("회원 ID"),
fieldWithPath("email").description("가입한 이메일")
)
));
REST Docs 장점
- 테스트 결과 기반 → 항상 정확한 문서가 만들어 진다.
- 정적 HTML 파일 → 배포/공유에 용이하다.
- 협업 시 → 신뢰도 높은 API 스펙을 공유할 수 있다.
Swagger
설정 방법
- springdoc-openapi 라이브러리 추가
- 컨트롤러 메서드에 @Operation, @Parameter 등 애노테이션 추가
접속 URL
- http://localhost:8080/swagger-ui/index.html
Swagger 장점
- 실시간 API 확인이 가능하다.
- 개발 중 편리하게 인터페이스를 테스트 할 수 있다.
- 자동화된 문서화 → 수정이 간편하다.
RestDocs vs Swagger 비교
구분 | RestDocs | Swagger |
문서 생성 방식 | 테스트 기반 (MockMvc 필요) | 애노테이션 기반 (실시간 문서화) |
결과물 | 정적 HTML 문서 | 동적 웹 페이지 |
공유 방식 | HTML 파일 배포 | 서버 배포 필요 |
유지보수 | 테스트 시 자동 갱신 | 애노테이션 수정 필요 |
인터랙티브 테스트 | 불가능 | 가능 |
정확성 | 테스트 기반 – 매우 정확 | 동적, 가끔 누락 가능 |
마무리
이번 테스트와 문서화는
테스트 없는 문서는 신뢰하기 어렵고, 문서 없는 테스트는 공유하기 어렵다는 것을 되새기며 열심히 설계했다.
RestDocs + Swagger 병행해서 두 방식을 비교 해볼 수 있어
나름 유의미 했던 노력이었던 것 같다.
외부 공유는 RestDocs, 개발 중엔 Swagger로 실시간 확인하는 것이 좋다.