[feature] 동아리 지원서 타입 설계 및 Mock API 개발#418
Conversation
- 클라이언트 사이드 API 모킹을 위한 MSW(Mock Service Worker) 설정 - './apply'에서 클럽 핸들러를 가져와 적용 - `setupWorker`를 통해 일관된 핸들러 초기화 보장
- 클럽 지원서 조회 및 제출을 위한 Mock API 핸들러 추가 - GET /api/club/apply: 유효하지 않은 클럽 ID에 대한 400 에러 처리 - GET /api/club/:clubId/apply: 클럽 ID 유효성 검사 및 모의 데이터 반환 - POST /api/club/:clubId/apply: 클럽 ID 유효성 검사 및 성공 메시지 반환
- 클럽 지원서 API 테스트 케이스 추가 (MSW) - 정상 요청: 클럽 지원서 조회 및 제출 테스트 - 필수 질문 항목이 비어있지 않은지 확인하는 테스트 - 에러 케이스: 잘못된 클럽 ID 및 클럽 ID 누락 시 400 에러 테스트 - 지원서 제출 성공 및 에러 케이스 확인
Walkthrough동아리 지원서 타입 설계, 목(mock) 데이터 정의, Mock API 및 테스트 코드가 추가되었습니다. MSW(Mock Service Worker) 기반의 API 핸들러와 유틸리티, 타입, 상수, 테스트 파일 등이 신규로 도입되었으며, 관련 의존성 패키지도 추가되었습니다. SDK 초기화 코드가 유틸리티로 분리되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Test as 테스트 코드
participant MSW as Mock Service Worker
participant Handler as applyHandlers
participant MockData as mockData
Test->>MSW: API 요청 (GET/POST/PUT /:clubId/apply)
MSW->>Handler: 해당 요청 핸들러 호출
Handler->>MockData: mockData 참조 (질문/폼 데이터)
Handler-->>MSW: 응답 데이터 생성
MSW-->>Test: Mock 응답 반환
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 사항 없음) Suggested labels
Suggested reviewers
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
✅ Deploy Preview for moadong ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (10)
frontend/src/mocks/api/index.ts (1)
1-3: 코드가 잘 구성되어 있습니다!API 핸들러를 중앙에서 관리하는 방식으로 잘 설계되었습니다. 현재는
clubHandlers만 포함하고 있지만, 향후 다른 기능의 핸들러를 추가할 때 확장하기 좋은 구조입니다.추후 다양한 API 모킹이 추가될 때 아래와 같이 구성하면 좋을 것 같습니다:
import { clubHandlers } from './apply'; import { userHandlers } from './user'; import { notificationHandlers } from './notification'; // 추가 핸들러들... export const handlers = [ ...clubHandlers, ...userHandlers, ...notificationHandlers, // 추가 핸들러들... ];frontend/jest.config.js (1)
4-4: 테스트 환경 설정이 변경되었습니다기본 jsdom 환경 대신 'jest-fixed-jsdom'을 사용하도록 설정이 변경되었습니다. 이는 MSW를 사용한 API 모킹 테스트에 필요한 변경으로 보입니다.
'jest-fixed-jsdom' 패키지가 어떤 문제를 해결하는지 PR 설명이나 코드 주석에 명시하면 좋을 것 같습니다. 추후 이 설정을 변경하거나 업데이트할 때 참고할 수 있도록 간단한 설명이 있으면 유지보수에 도움이 될 것입니다.
frontend/src/mocks/api/apply.ts (3)
4-8:validateClubId에서 숫자 변환을 두 번 하지 않도록 반환형 개선 제안
parseInt로 변환한 값을 그대로 반환하면 이후 로직에서 다시 파싱할 필요가 없습니다. 숫자0을 걸러내는 조건까지 포함하여 조금 더 선언적으로 작성해 보면 가독성과 성능이 모두 개선됩니다.-const validateClubId = (clubId: string | undefined) => { - if (!clubId) return false; - const numericClubId = parseInt(clubId, 10); - return !isNaN(numericClubId) && numericClubId > 0; -}; +const parseClubId = (raw: string | undefined): number | null => { + if (!raw) return null; + const id = Number(raw); + return Number.isInteger(id) && id > 0 ? id : null; +};
18-36:clubId파라미터 재파싱 중복
validateClubId(또는 개선안) 내부에서 이미 숫자로 변환한 값을 받아올 수 있다면 이 구간의parseInt는 불필요합니다. 하나의 유틸 함수에서 변환과 검증을 모두 담당하도록 통합하면 코드 일관성이 향상됩니다.
38-58: POST 핸들러에서 요청 본문 미검증
실제 제출 기능을 모킹한다고 가정하면, 최소한 JSON 파싱이 가능한지와 필수 답변 필드를 포함하는지 확인하도록 해두면 프론트엔드의 유효성 검사 누락을 빠르게 발견할 수 있습니다.-async ({ params, request }) => { +async ({ params, request }) => { const clubId = String(params.clubId); - if (!validateClubId(clubId)) { + if (!validateClubId(clubId)) { ... } - return HttpResponse.json( + // 간단한 예: 비어 있는 바디면 422 반환 + if (!(await request.clone().text())) { + return HttpResponse.json( + { message: '빈 본문입니다.' }, + { status: 422 }, + ); + } + + return HttpResponse.json( ... ); }frontend/src/mocks/api/clubHandlers.test.ts (3)
49-52: 제목 길이 제한 하드코딩 주의
제목이 20자를 초과하면 바로 실패하도록 되어 있는데, 실제 제품 요구사항이 변경될 경우 테스트가 불필요하게 깨질 수 있습니다. 요구사항을 상수로 분리하거나mockData에서 직접 검증하도록 수정하는 편이 유지보수에 유리합니다.
55-60: 필수 질문 검증 로직 개선 제안
현재는items?.length만 확인하지만, 주관식 질문(단답/서술형)의 경우items는 placeholder일 수 있어 실제 입력 가능 여부를 보장하지 않습니다. 타입별로 다른 검증 규칙을 적용하면 테스트 신뢰도가 높아집니다.
74-77: 테스트 코드에 남아있는console.log삭제
디버깅용console.log는 CI 출력 노이즈를 유발하므로 제거하거나debug라이브러리 등으로 대체해 주세요.- const data: ApiErrorResponse = await response.json(); - console.log(data); + const data: ApiErrorResponse = await response.json();frontend/src/mocks/data/mockData.ts (2)
1-8: 문자열 리터럴 유니온 대신enum사용 고려
타입 안정성과 자동완성 이점을 위해QuestionType을enum으로 선언하면 유지보수가 더 쉽습니다.-type QuestionType = - | 'CHOICE' - | 'MULTI_CHOICE' - | 'SHORT_TEXT' - | 'LONG_TEXT' - | 'PHONE_NUMBER' - | 'EMAIL' - | 'NAME'; +export enum QuestionType { + CHOICE = 'CHOICE', + MULTI_CHOICE = 'MULTI_CHOICE', + SHORT_TEXT = 'SHORT_TEXT', + LONG_TEXT = 'LONG_TEXT', + PHONE_NUMBER = 'PHONE_NUMBER', + EMAIL = 'EMAIL', + NAME = 'NAME', +}
80-93: 목업 데이터에 과도한 길이의 placeholder 문자열
테스트 목적이라도 수백 자의 반복 문자열은 가독성을 떨어뜨립니다. 텍스트 길이만이 중요하다면Array(20).fill('문장').join('')처럼 동적으로 생성하거나 줄여서 작성해도 충분합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (8)
frontend/jest.config.js(1 hunks)frontend/package.json(1 hunks)frontend/public/mockServiceWorker.js(1 hunks)frontend/src/mocks/api/apply.ts(1 hunks)frontend/src/mocks/api/clubHandlers.test.ts(1 hunks)frontend/src/mocks/api/index.ts(1 hunks)frontend/src/mocks/browser.ts(1 hunks)frontend/src/mocks/data/mockData.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
frontend/src/mocks/api/index.ts (1)
frontend/src/mocks/api/apply.ts (1)
clubHandlers(10-60)
frontend/src/mocks/browser.ts (1)
frontend/src/mocks/api/index.ts (1)
handlers(3-3)
frontend/src/mocks/api/apply.ts (1)
frontend/src/mocks/data/mockData.ts (1)
mockData(28-125)
🪛 Biome (1.9.4)
frontend/public/mockServiceWorker.js
[error] 276-276: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (2)
frontend/package.json (1)
24-31: 새로운 패키지가 추가되었습니다MSW와 관련 테스트 환경 설정을 위한 패키지가 적절히 추가되었습니다. 다음 사항을 확인해주세요:
Sentry 관련 패키지(@sentry/react, @sentry/webpack-plugin)가 추가되었는데, 현재 리뷰 중인 PR 범위에서 사용되는지 확인이 필요합니다. 에러 모니터링을 위한 것으로 보이지만, 관련 설정 코드가 보이지 않습니다.
jest-fixed-jsdom의 버전이 매우 낮습니다(0.0.9). 실험적인 패키지로 보이는데, 안정성이 검증되었는지 확인해주세요.
MSW 패키지는 적절한 버전으로 추가되어 API 모킹에 잘 활용될 것으로 보입니다.
frontend/public/mockServiceWorker.js (1)
1-308: 라이브러리 자동 생성 파일 – 리뷰 생략
이 파일은 MSW가 자동 생성한 코드이므로 수정하지 않는 것이 권장됩니다.🧰 Tools
🪛 Biome (1.9.4)
[error] 276-276: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
- setupWorker에 전달되는 handlers 배열의 타입을 명시적으로 RequestHandler[]로 지정. - TypeScript에서 발생한 타입 불일치 오류 해결. - MSW의 엄격한 타입 검사 규칙에 맞도록 코드 수정.
…ttps://github.com/Moadong/moadong into feature/#383-club-application-type-mock-api-FE-107
- 하드코딩된 API URL을 API_BASE 상수로 대체 - URL 관리 중앙화로 유지보수성 향상
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
frontend/src/mocks/utils/createApiUrl.ts (1)
1-12: 전반적으로 잘 구현되었습니다!
createApiUrl함수는 목적이 명확하고 구현이 간단합니다. 다만 몇 가지 개선 사항을 제안합니다:
clubId의 추가적인 엣지 케이스 처리가 필요합니다:
- 현재는 빈 문자열과 0만 처리하고 있지만,
undefined,null,NaN등의 경우도 고려해야 합니다.- 문자열 "0"은 현재 조건에서 캐치되지 않습니다.
함수에 JSDoc 문서를 추가하면 사용자가 더 쉽게 이해할 수 있습니다.
+/** + * 클럽 ID와 액션에 기반하여 API URL을 생성합니다. + * @param clubId - 클럽의 고유 식별자 + * @param action - API 액션 (기본값: 'apply') + * @returns 포맷팅된 API URL 문자열 + */ export const createApiUrl = ( clubId: string | number, action: string = 'apply', ) => { // 유효하지 않은 clubId 케이스 - if (clubId === '' || clubId === 0) { + if (!clubId || clubId === '' || clubId === 0 || clubId === '0') { return `${API_BASE}/${action}`; } return `${API_BASE}/${clubId}/${action}`; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
frontend/src/mocks/api/clubHandlers.test.ts(1 hunks)frontend/src/mocks/utils/createApiUrl.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/mocks/api/clubHandlers.test.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/mocks/utils/createApiUrl.ts (1)
frontend/src/mocks/constants/api.ts (1)
API_BASE(1-1)
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
frontend/src/utils/initSDK.ts (2)
18-25: ChannelService 초기화 최적화 필요현재 구현에서는 환경 변수 유무와 관계없이 항상
loadScript()를 호출합니다. 플러그인 키가 없을 때는 스크립트 로드가 불필요할 수 있으므로, 조건부로 로드하는 것이 좋습니다. 또한 초기화 과정에서의 예외 처리도 추가해주세요.export function initializeChannelService() { - ChannelService.loadScript(); - if (process.env.CHANNEL_PLUGIN_KEY) { + if (process.env.CHANNEL_PLUGIN_KEY && process.env.CHANNEL_PLUGIN_KEY.trim() !== '') { + try { + ChannelService.loadScript(); ChannelService.boot({ pluginKey: process.env.CHANNEL_PLUGIN_KEY, }); + } catch (error) { + console.error('ChannelService 초기화 실패:', error); + } } }
1-4: 함수 설명 주석 추가 권장각 SDK 초기화 함수에 대한 설명 주석이 없어 코드의 의도를 파악하기 어렵습니다. 각 함수 위에 JSDoc 스타일 주석을 추가하여 함수의 목적, 사용하는 환경 변수, 그리고 동작 방식에 대한 설명을 추가하는 것이 좋습니다.
import mixpanel from 'mixpanel-browser'; import * as ChannelService from '@channel.io/channel-web-sdk-loader'; import * as Sentry from '@sentry/react'; +/** + * Mixpanel 분석 도구를 초기화합니다. + * 환경 변수 REACT_APP_MIXPANEL_TOKEN이 필요합니다. + * localhost에서는 Mixpanel이 비활성화됩니다. + */ export function initializeMixpanel() { // ... 기존 코드 ... } +/** + * Channel.io 고객 메시징 서비스를 초기화합니다. + * 환경 변수 CHANNEL_PLUGIN_KEY가 필요합니다. + */ export function initializeChannelService() { // ... 기존 코드 ... } +/** + * Sentry 에러 추적 서비스를 초기화합니다. + * 환경 변수 SENTRY_DSN이 필요하며, SENTRY_RELEASE도 사용됩니다. + * 개인 식별 정보(PII)는 전송되지 않습니다. + */ export function initializeSentry() { // ... 기존 코드 ... }Also applies to: 5-34
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
frontend/src/index.tsx(1 hunks)frontend/src/utils/initSDK.ts(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- frontend/src/index.tsx
- 개발 환경에서만 MSW 워커 로드 - 동적 임포트로 초기 로딩 시간 개선 - 불필요한 프로덕션 번들 크기 감소
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
frontend/src/mocks/�mswDevSetup.ts (2)
5-5: 타입 단언 방식을 개선하는 것이 좋습니다.이중 타입 단언(
as unknown as RequestHandler[])은 TypeScript의 타입 체크를 우회하는 방법으로, 타입 안전성을 저하시킬 수 있습니다. 타입 안전성을 높이려면 아래 방법 중 하나를 고려하세요.방법 1: 타입 가드 함수 사용
// API 파일에서 타입 정의 조정 export const handlers: RequestHandler[] = [...];방법 2: 더 안전한 타입 단언
-export const worker = setupWorker(...(handlers as unknown as RequestHandler[])); +export const worker = setupWorker(...handlers.map(handler => handler as RequestHandler));
1-5: 파일 목적과 사용 방법에 대한 문서화를 추가하세요.이 파일은 Mock Service Worker 설정의 중요한 부분이지만, 목적과 사용 방법에 대한 설명이 없습니다. 코드의 가독성과 유지보수성을 위해 파일 상단에 주석을 추가하는 것이 좋습니다.
+/** + * MSW(Mock Service Worker) 브라우저 환경 설정 파일 + * + * 이 파일은 개발 환경에서 API 요청을 가로채고 모의 응답을 반환하는 서비스 워커를 설정합니다. + * 애플리케이션 시작 시 이 워커를 초기화하여 API 모킹을 활성화할 수 있습니다. + * + * @example + * // 개발 환경에서만 MSW 초기화 + * if (process.env.NODE_ENV === 'development') { + * import('./mocks/mswDevSetup').then(({ worker }) => { + * worker.start(); + * }); + * } + */
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/index.tsx(1 hunks)frontend/src/mocks/�mswDevSetup.ts(1 hunks)frontend/src/mocks/api/clubHandlers.test.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/index.tsx
- frontend/src/mocks/api/clubHandlers.test.ts
- 객관식 및 주관식 질문 테스트 추가
- PUT /:clubId/apply 엔드포인트 추가 - 클럽 ID 유효성 검증 로직 구현 - 성공/실패 응답 메시지 정의
- 지원서 수정 성공 케이스 테스트 - 잘못된 클럽 ID로 수정 요청 시 400 에러 테스트
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (1)
frontend/src/mocks/api/apply.ts (1)
7-12: 🛠️ Refactor suggestion에러 메시지 상수화가 필요합니다.
에러 메시지 '유효하지 않은 클럽 ID입니다.'가 코드 내에서 여러 번 반복되고 있습니다. 유지보수성 향상을 위해 이를 상수로 추출하는 것을 권장합니다.
import { mockData } from '../data/mockData'; import { API_BASE } from '../constants/clubApi'; import { validateClubId } from '../utils/validateClubId'; +// 상수 정의 +const ERROR_INVALID_CLUB_ID = '유효하지 않은 클럽 ID입니다.'; + export const clubHandlers = [ http.get(`${API_BASE}/apply`, () => { return HttpResponse.json( - { message: '유효하지 않은 클럽 ID입니다.' }, + { message: ERROR_INVALID_CLUB_ID }, { status: 400 }, ); }),
🧹 Nitpick comments (3)
frontend/src/mocks/api/apply.ts (1)
14-32: GET 요청 핸들러 구현이 적절합니다.클럽 ID를 검증하고 적절한 응답을 반환하는 로직이 잘 구현되어 있습니다. 추가로 개선할 점:
- 동일한 에러 메시지가 반복되므로 앞서 제안한 상수를 사용하세요.
- 응답 형식이 API 명세와 일치하는지 확인하세요.
http.get(`${API_BASE}/:clubId/apply`, ({ params }) => { const clubId = String(params.clubId); if (!validateClubId(clubId)) { return HttpResponse.json( - { message: '유효하지 않은 클럽 ID입니다.' }, + { message: ERROR_INVALID_CLUB_ID }, { status: 400 }, ); }frontend/src/mocks/api/clubHandlers.test.ts (2)
46-72: 타입 캐스팅 대신 타입 가드 사용을 권장합니다.현재 여러 곳에서 타입 캐스팅(as)을 사용하고 있습니다. 타입 안전성을 높이기 위해 타입 가드 함수를 사용하는 것이 좋습니다.
+ // 타입 가드 함수 추가 + function isClubApplyResponse(data: any): data is ClubApplyResponse { + return data && typeof data.form_title === 'string' && Array.isArray(data.questions); + } it('클럽 지원서를 정상적으로 불러온다.', () => { expect(response.status).toBe(200); - expect((data as ClubApplyResponse).form_title).toBeDefined(); - expect((data as ClubApplyResponse).questions.length).toBeGreaterThan(0); + if (!isClubApplyResponse(data)) { + fail('응답이 예상 형식과 일치하지 않습니다'); + } + expect(data.form_title).toBeDefined(); + expect(data.questions.length).toBeGreaterThan(0); });
84-143: POST 테스트 케이스에서 중복 코드 개선이 필요합니다.여러 POST 테스트 케이스에서 비슷한 검증 로직이 반복되고 있습니다. 테스트 헬퍼 함수나 공통 검증 로직을 추출하여 코드 중복을 줄이는 것이 좋습니다.
+ // 공통 테스트 검증 함수 + const verifySuccessfulSubmission = async (answers: Record<string | number, string[]>) => { + const response = await submitApplication(CLUB_ID, answers); + const data: SubmissionResponse = await response.json(); + + expect(response.status).toBe(200); + expect(data.message).toBe('지원서가 성공적으로 제작되었습니다.'); + return { response, data }; + }; describe('클럽 지원서 POST 테스트', () => { it('지원서 제작 성공', async () => { const answers = { '1': ['답변1', '답변2'], '2': ['답변3'], }; - - const response = await submitApplication(CLUB_ID, answers); - const data: SubmissionResponse = await response.json(); - - expect(response.status).toBe(200); - expect(data.message).toBe('지원서가 성공적으로 제작되었습니다.'); + await verifySuccessfulSubmission(answers); });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
frontend/src/mocks/api/apply.ts(1 hunks)frontend/src/mocks/api/clubHandlers.test.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
frontend/src/mocks/api/clubHandlers.test.ts (1)
Learnt from: seongwon030
PR: Moadong/moadong#418
File: frontend/src/mocks/api/clubHandlers.test.ts:0-0
Timestamp: 2025-05-16T06:25:11.156Z
Learning: 이 프로젝트에서는 API URL 생성과 같은 유틸리티 함수는 별도 파일로 분리하여 관리하는 것이 좋습니다. 특히 테스트 파일 내에 정의된 유틸리티 함수는 재사용성과 일관성을 위해 분리가 권장됩니다.
🧬 Code Graph Analysis (1)
frontend/src/mocks/api/apply.ts (3)
frontend/src/mocks/constants/clubApi.ts (1)
API_BASE(1-1)frontend/src/mocks/utils/validateClubId.ts (1)
validateClubId(1-4)frontend/src/mocks/data/mockData.ts (1)
mockData(28-125)
🔇 Additional comments (4)
frontend/src/mocks/api/apply.ts (1)
1-5: API 및 유틸리티 임포트 구성이 잘 되었습니다.필요한 모듈과 유틸리티 함수, 상수들이 명확하게 임포트되어 있어 코드의 가독성과 구조가 좋습니다.
validateClubId유틸리티를 분리하여 재사용한 것이 좋은 패턴입니다.frontend/src/mocks/api/clubHandlers.test.ts (3)
1-6: 임포트가 명확하게 정의되어 있습니다.필요한 모듈과 타입 그리고 유틸리티 함수들이 잘 임포트되어 있습니다. MSW와 관련된 설정도 적절히 구성되어 있습니다.
22-26: MSW 서버 설정이 적절합니다.테스트 환경 설정을 위한 MSW 서버가 잘 구성되어 있으며, Jest의 라이프사이클 훅을 사용하여 테스트 전후 처리가 적절히 구현되어 있습니다.
74-82: 테스트 코드가 잘 구성되어 있습니다.에러 케이스 테스트가 명확하게 구현되어 있으며, 예상 응답과 상태 코드를 적절히 검증하고 있습니다.
- API 요청 관련 유틸리티 함수들을 별도 파일로 분리 - sendApiRequest, submitApplication, updateApplication 함수를 utils/request.ts로 이동 - 테스트 코드에서 유틸리티 함수 import하여 사용하도록 수정 - DELETE 메서드 지원 추가
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
frontend/src/mocks/api/utils/request.ts (4)
3-16: 에러 처리 로직 추가 고려현재
sendApiRequest함수는 네트워크 오류나 서버 오류에 대한 명시적인 에러 처리를 포함하지 않습니다. 비록 이것이 목(mock) API용이더라도, 실제 구현과 일관성을 유지하기 위해 기본적인 에러 처리를 추가하는 것이 좋을 수 있습니다.다음과 같은 에러 처리 로직 추가를 고려해보세요:
export const sendApiRequest = async ( clubId: string, answers: Record<number, string[]>, method: 'POST' | 'PUT' | 'DELETE', ) => { - const response = await fetch(createApiUrl(clubId), { - method, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(answers), - }); - return response; + try { + const response = await fetch(createApiUrl(clubId), { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(answers), + }); + return response; + } catch (error) { + console.error('API 요청 중 오류 발생:', error); + throw error; + } };
1-32: JSDoc 문서화 추가 권장함수의 목적과 사용법을 설명하는 JSDoc 주석이 없습니다. 다른 개발자들이 이 유틸리티 함수들을 이해하고 사용하는 데 도움이 되도록 문서화를 추가하는 것이 좋습니다.
다음과 같은 JSDoc 문서화 추가를 고려해보세요:
import { createApiUrl } from '@/mocks/utils/createApiUrl'; +/** + * 클럽 ID와 답변을 사용하여 API 요청을 보내는 함수 + * @param clubId - 요청을 보낼 클럽 ID + * @param answers - 질문 번호/ID를 키로 하고 답변 배열을 값으로 하는 객체 + * @param method - HTTP 메서드 ('POST', 'PUT', 'DELETE') + * @returns fetch API의 응답 객체 + */ export const sendApiRequest = async ( clubId: string, answers: Record<number, string[]>, method: 'POST' | 'PUT' | 'DELETE', ) => { // ... 함수 내용 ... }; +/** + * 지원서를 제출하는 함수 (POST 요청) + * @param clubId - 지원서를 제출할 클럽 ID + * @param answers - 질문 번호/ID를 키로 하고 답변 배열을 값으로 하는 객체 + * @returns fetch API의 응답 객체 + */ export const submitApplication = ( // ... 함수 내용 ... ); // 나머지 함수들에 대한 JSDoc 추가
8-14: 응답 데이터 처리 로직 고려현재
sendApiRequest함수는 원시 응답 객체를 그대로 반환합니다. 사용자가 응답 데이터를 사용하려면 추가적으로.json()과 같은 메서드를 호출해야 합니다. 일반적인 패턴은 응답 데이터를 자동으로 파싱하여 반환하는 것입니다.응답 데이터를 자동으로 파싱하는 로직을 추가하는 것을 고려해보세요:
export const sendApiRequest = async ( clubId: string, answers: Record<number, string[]>, method: 'POST' | 'PUT' | 'DELETE', ) => { const response = await fetch(createApiUrl(clubId), { method, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(answers), }); - return response; + if (!response.ok) { + throw new Error(`API 요청 실패: ${response.status} ${response.statusText}`); + } + return response.json(); };아니면 최소한 응답 상태를 확인하는 로직을 추가하세요.
3-7: HTTP 메서드 타입 확장 고려현재
sendApiRequest함수는 'POST', 'PUT', 'DELETE' 메서드만 지원합니다. 나중에 확장성을 고려하여 'GET'과 'PATCH' 같은 다른 HTTP 메서드도 지원하도록 타입을 확장하는 것이 좋을 수 있습니다.다음과 같이 메서드 타입을 확장하는 것을 고려해보세요:
export const sendApiRequest = async ( clubId: string, answers: Record<number, string[]>, - method: 'POST' | 'PUT' | 'DELETE', + method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', ) => {그리고 GET 메서드일 경우 body를 전송하지 않도록 로직을 추가하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
frontend/src/mocks/api/clubHandlers.test.ts(1 hunks)frontend/src/mocks/api/utils/request.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/mocks/api/clubHandlers.test.ts
🔇 Additional comments (1)
frontend/src/mocks/api/utils/request.ts (1)
28-31: deleteApplication 함수의 answers 매개변수 필요성 검토
deleteApplication함수는 삭제 작업을 수행하는데answers매개변수가 필요한지 의문입니다. 일반적으로 DELETE 요청은 리소스 식별자(여기서는clubId)만으로 충분합니다. 만약 특별한 이유가 없다면, 이 매개변수를 제거하는 것이 API 디자인 측면에서 더 일관성 있어 보입니다.DELETE 요청에 실제로 요청 본문(body)이 필요한지 확인해주세요. 필요하지 않다면 다음과 같이 수정을 고려해보세요:
export const deleteApplication = ( clubId: string, - answers: Record<number | string, string[]>, ) => sendApiRequest(clubId, {}, 'DELETE');
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment Thanks for integrating Codecov - We've got you covered ☂️ |
This reverts commit e37eaa8.
oesnuj
left a comment
There was a problem hiding this comment.
MSW로 mock API 서버 구성해주신 거 정말 수고 많으셨습니다 👍
실제 API가 아직 없는 상황에서도 빠르게 개발해볼 수 있을 것 같아요!
- 한 가지 궁금한 점이 있어요
추후 백엔드 API가 실제로 만들어졌을 때, 관련 테스트를 백엔드에서 받은 실제 API로 검증하는 게 좋을까요?
아니면 msw로 구성한 mock API를 기반으로 테스트하는 것이 괜찮을까요?
mock 서버가 단순히 더미 데이터를 주는 것 외에도 테스트 용도로 어떻게 사용되는 게 맞는지 궁금해서요!
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
frontend/src/mocks/api/apply.ts (2)
35-52: 🛠️ Refactor suggestionPOST 핸들러에 요청 본문 검증이 누락되었습니다
이전 리뷰에서 지적된 요청 본문 검증이 여전히 구현되지 않았습니다. 실제 지원서 제출 시에는 답변 데이터의 유효성을 검증해야 합니다.
- http.post(`${API_BASE}/:clubId/apply`, ({ params }) => { + http.post(`${API_BASE}/:clubId/apply`, async ({ params, request }) => { const clubId = String(params.clubId); if (!validateClubId(clubId)) { return HttpResponse.json( { message: ERROR_MESSAGE.INVALID_CLUB_ID }, { status: 400 }, ); } + // 요청 본문 검증 + try { + const requestData = await request.json(); + if (!requestData || typeof requestData !== 'object') { + return HttpResponse.json( + { message: '올바른 답변 데이터를 제공해주세요.' }, + { status: 400 }, + ); + } + } catch (error) { + return HttpResponse.json( + { message: '잘못된 요청 형식입니다.' }, + { status: 400 }, + ); + } return HttpResponse.json(
54-71: 🛠️ Refactor suggestionPUT 핸들러의 async 사용이 불일치합니다
PUT 핸들러만
async를 사용하고 있지만 실제로는await를 사용하지 않습니다. POST 핸들러와 일관성을 위해 제거하거나, 요청 본문 검증을 추가하여await를 실제로 사용하세요.- http.put(`${API_BASE}/:clubId/apply`, async ({ params }) => { + http.put(`${API_BASE}/:clubId/apply`, async ({ params, request }) => { const clubId = String(params.clubId); if (!validateClubId(clubId)) { return HttpResponse.json( { message: ERROR_MESSAGE.INVALID_CLUB_ID }, { status: 400 }, ); } + // 요청 본문 검증 (POST와 동일한 로직) + try { + const requestData = await request.json(); + if (!requestData || typeof requestData !== 'object') { + return HttpResponse.json( + { message: '올바른 답변 데이터를 제공해주세요.' }, + { status: 400 }, + ); + } + } catch (error) { + return HttpResponse.json( + { message: '잘못된 요청 형식입니다.' }, + { status: 400 }, + ); + } return HttpResponse.json(
🧹 Nitpick comments (5)
frontend/src/mocks/api/applyHandlers.test.ts (3)
35-38: beforeEach에서 중복 호출 최적화 검토매 테스트마다 동일한 API 호출과 JSON 파싱을 수행하고 있습니다. 응답 데이터가 변경되지 않는다면, 테스트별로 필요한 부분만 호출하는 것이 더 효율적일 수 있습니다.
- beforeEach(async () => { - response = await fetch(createApiUrl(CLUB_ID)); - data = await response.json(); - }); + beforeEach(async () => { + response = await fetch(createApiUrl(CLUB_ID)); + data = await response.json(); + });현재 구조도 충분히 괜찮지만, 테스트 성능을 고려한다면 각 테스트에서 필요한 데이터만 가져오는 방식도 고려해볼 수 있습니다.
115-130: 에러 케이스 테스트에서 일관성 확인POST 에러 테스트에서
submitApplication함수 대신 직접fetch를 사용하고 있습니다. 다른 성공 케이스들과 일관성을 위해submitApplication함수를 사용하는 것을 고려해보세요.- it('잘못된 클럽 ID로 요청 시 400 에러', async () => { - const response = await fetch(`${API_BASE}/invalid-id/apply`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - '1': ['답변1'], - '2': ['답변2'], - }), - }); + it('잘못된 클럽 ID로 요청 시 400 에러', async () => { + const response = await submitApplication('invalid-id', { + '1': ['답변1'], + '2': ['답변2'], + });이렇게 하면 유틸리티 함수의 에러 처리도 함께 테스트할 수 있습니다.
147-162: PUT 에러 테스트도 동일한 일관성 이슈가 있습니다POST 에러 테스트와 마찬가지로
updateApplication함수를 사용하는 것이 더 일관적입니다.- it('잘못된 클럽 ID로 수정 요청 시 400 에러', async () => { - const response = await fetch(`${API_BASE}/invalid-id/apply`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - '1': ['수정된 답변1'], - '2': ['수정된 답변2'], - }), - }); + it('잘못된 클럽 ID로 수정 요청 시 400 에러', async () => { + const response = await updateApplication('invalid-id', { + '1': ['수정된 답변1'], + '2': ['수정된 답변2'], + });frontend/src/mocks/api/apply.ts (2)
8-13: 기본 GET 엔드포인트의 목적이 명확하지 않습니다
/apply경로로의 GET 요청이 항상 400 에러를 반환하는 이유가 불분명합니다. 이 엔드포인트가 실제로 필요한지, 아니면 문서화나 명확한 에러 메시지가 필요한지 검토해보세요.실제 API에서 클럽 ID 없이 지원서에 접근하는 것이 잘못된 요청이라면, 더 구체적인 에러 메시지를 고려해볼 수 있습니다:
http.get(`${API_BASE}/apply`, () => { return HttpResponse.json( - { message: ERROR_MESSAGE.INVALID_CLUB_ID }, + { message: '클럽 ID가 필요합니다. /:clubId/apply 형식으로 요청해주세요.' }, { status: 400 }, ); }),🧰 Tools
🪛 GitHub Check: codecov/patch
[warning] 9-9: frontend/src/mocks/api/apply.ts#L9
Added line #L9 was not covered by tests
9-9: 테스트 커버리지 누락 해결정적 분석 도구에서 9번째 라인이 테스트로 커버되지 않는다고 표시됩니다. 이는 기본 GET
/apply엔드포인트에 대한 테스트가 없기 때문입니다.테스트 파일에 다음 테스트를 추가하는 것을 고려해보세요:
describe('기본 apply 엔드포인트 테스트', () => { it('클럽 ID 없이 요청 시 400 에러를 반환한다', async () => { const response = await fetch(`${API_BASE}/apply`); const data: ApiErrorResponse = await response.json(); expect(response.status).toBe(400); expect(data.message).toBeDefined(); }); });테스트 커버리지를 개선하기 위한 추가 테스트 코드를 생성해드릴까요?
🧰 Tools
🪛 GitHub Check: codecov/patch
[warning] 9-9: frontend/src/mocks/api/apply.ts#L9
Added line #L9 was not covered by tests
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/mocks/api/apply.ts(1 hunks)frontend/src/mocks/api/applyHandlers.test.ts(1 hunks)frontend/src/mocks/api/index.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/mocks/api/index.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
frontend/src/mocks/api/applyHandlers.test.ts (6)
frontend/src/mocks/data/mockData.ts (1)
Question(14-21)frontend/src/mocks/api/apply.ts (1)
applyHandlers(7-72)frontend/src/mocks/utils/createApiUrl.ts (1)
createApiUrl(4-10)frontend/src/mocks/constants/clubApi.ts (2)
CLUB_ID(3-3)API_BASE(1-1)frontend/src/mocks/constants/error.ts (1)
ERROR_MESSAGE(1-5)frontend/src/mocks/api/utils/request.ts (2)
submitApplication(18-21)updateApplication(23-26)
frontend/src/mocks/api/apply.ts (4)
frontend/src/mocks/constants/clubApi.ts (1)
API_BASE(1-1)frontend/src/mocks/constants/error.ts (1)
ERROR_MESSAGE(1-5)frontend/src/mocks/utils/validateClubId.ts (1)
validateClubId(1-4)frontend/src/mocks/data/mockData.ts (1)
mockData(28-125)
🪛 GitHub Check: codecov/patch
frontend/src/mocks/api/apply.ts
[warning] 9-9: frontend/src/mocks/api/apply.ts#L9
Added line #L9 was not covered by tests
🔇 Additional comments (8)
frontend/src/mocks/api/applyHandlers.test.ts (6)
1-7: 임포트 구조가 잘 정리되어 있습니다필요한 모듈들을 적절히 임포트하고 있으며, MSW 테스트에 필요한 모든 의존성이 포함되어 있습니다.
9-23: 타입 정의가 명확하고 구조적입니다API 응답에 대한 인터페이스들이 잘 정의되어 있어 타입 안전성을 보장합니다. 특히
ClubApplyResponse,ApiErrorResponse,SubmissionResponse로 명확히 구분한 점이 좋습니다.
24-28: MSW 서버 설정이 적절합니다테스트 라이프사이클 관리가 올바르게 구현되어 있어 테스트 간 상태 오염을 방지합니다.
52-59: 필수 질문 검증 로직이 견고합니다옵셔널 체이닝을 사용하여 안전하게 필수 질문의 항목을 검증하고 있습니다. 실제 데이터 구조와 잘 맞는 테스트입니다.
74-84: 다양한 답변 형태에 대한 테스트가 포괄적입니다문자열과 숫자 키를 모두 포함한 답변 데이터로 테스트하여 실제 사용 시나리오를 잘 반영했습니다.
99-113: 주관식 질문 테스트 데이터가 현실적입니다다양한 주관식 질문 타입(단답형, 서술형, 이메일, 전화번호, 이름)에 대한 실제적인 답변 예시로 테스트하고 있어 매우 유용합니다.
frontend/src/mocks/api/apply.ts (2)
1-5: 임포트 구조가 체계적으로 개선되었습니다이전 리뷰에서 지적된 중복 검증 로직과 상수화 이슈가 잘 해결되었습니다.
validateClubId유틸리티와ERROR_MESSAGE상수를 적절히 사용하고 있습니다.
15-33: GET 핸들러가 잘 구현되었습니다클럽 ID 검증과 적절한 응답 구조를 가지고 있습니다. 에러 처리와 성공 응답 모두 일관적입니다.

#️⃣연관된 이슈
📝작업 내용
1️⃣ MSW 기반 API Mocking 및 테스트 환경 구축
msw란?
왜 사용하는가?
msw 구성에 msw 공식문서 내용을 간단하게 정리해두었습니다.
mswDevSetup
setUpWorker는 네트워크 요청을 가로채어 모의 응답을 반환하는 역할을 합니다.2️⃣ 테스트 작성
단위 테스트
API 테스트
3️⃣ Jsdom 문제
TextEncoder,TextDecoder인데 JSDOM에 해당 기능이 없습니다.jest-fixed-jsdom
jest-fixed-jsdom은 기존 JSDOM 환경을 패치하여 Service Worker API와 같은 브라우저 API를 폴리필한다고 하네요. 폴리필jest.config.js에서 environment를jest-fixed-jsdom로 하면 에러가 나지 않습니다.그 외 작업
initSDK에 sdk 실행 함수를 분리하였습니다. 96979f4중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
put, post에서 request body 검증 로직이 필요할지 의문입니다. 한 번 생각해보죵
🫡 참고사항
Summary by CodeRabbit
신규 기능
테스트
문서화
환경 설정 및 초기화
의존성 추가