From 8209bf83cb8ebbb6eaee714d8ad7b1b7384a28cb Mon Sep 17 00:00:00 2001 From: Ebisu <127163361+aabyess@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:45:23 +0900 Subject: [PATCH] =?UTF-8?q?test(connect):=20fetchpostsclient=20msw=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetchPostsClient, toggleConnectLike, deleteConnectLike API 함수에 대한 MSW 기반 단위 테스트를 작성 - 정상 응답 및 500/404 에러 케이스 검증 - server.use()로 에러 핸들러 오버라이드 후 rejects.toThrow 검증 --- .../connect/apis/fetchPostsClient.test.ts | 93 +++++++++++++++++++ features/connect/comment/mappers.test.ts | 43 +++++++++ features/connect/post/mappers.test.ts | 15 --- 3 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 features/connect/apis/fetchPostsClient.test.ts diff --git a/features/connect/apis/fetchPostsClient.test.ts b/features/connect/apis/fetchPostsClient.test.ts new file mode 100644 index 00000000..0096d344 --- /dev/null +++ b/features/connect/apis/fetchPostsClient.test.ts @@ -0,0 +1,93 @@ +import { server } from "@/mocks/server"; +import { http, HttpResponse } from "msw"; +import { fetchPostsClient, toggleConnectLike, deleteConnectLike } from "./fetchPostsClient"; +import { BASE_URL } from "@/mocks/constants"; + +// 각 테스트 전후 MSW 서버 생명주기 관리 +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); // 테스트마다 핸들러 초기화 +afterAll(() => server.close()); + +describe("fetchPostsClient", () => { + // 정상 흐름 + it("정상 응답 시 게시글 목록을 반환한다", async () => { + const result = await fetchPostsClient({ type: "all", limit: 5 }); + expect(result.data).toBeDefined(); + }); + + // 반환 타입 검증 + it("반환 데이터가 배열이다", async () => { + const result = await fetchPostsClient({ type: "all", limit: 5 }); + expect(Array.isArray(result.data)).toBe(true); + }); + + // type 파라미터 동작 검증 + it("type=best로 HOT 게시글을 조회할 수 있다", async () => { + const result = await fetchPostsClient({ type: "best", limit: 20 }); + expect(result.data).toBeDefined(); + }); + + // keyword 파라미터 동작 검증 + it("keyword 파라미터를 포함해 요청할 수 있다", async () => { + const result = await fetchPostsClient({ type: "all", keyword: "달램핏" }); + expect(result.data).toBeDefined(); + }); + + // 500 에러 → throw 검증 + it("서버 에러 시 에러를 throw한다", async () => { + server.use(http.get(`${BASE_URL}/posts`, () => HttpResponse.json({}, { status: 500 }))); + await expect(fetchPostsClient({ type: "all" })).rejects.toThrow("게시글 조회 실패"); + }); + + // 404 에러 → throw 검증 + it("404 응답 시 에러를 throw한다", async () => { + server.use(http.get(`${BASE_URL}/posts`, () => HttpResponse.json({}, { status: 404 }))); + await expect(fetchPostsClient({ type: "all" })).rejects.toThrow("게시글 조회 실패"); + }); +}); + +describe("toggleConnectLike", () => { + // 정상 흐름 + it("정상 응답 시 에러 없이 완료된다", async () => { + await expect(toggleConnectLike(1)).resolves.not.toThrow(); + }); + + // 500 에러 → throw 검증 + it("서버 에러 시 에러를 throw한다", async () => { + server.use( + http.post(`${BASE_URL}/posts/:postId/like`, () => HttpResponse.json({}, { status: 500 })), + ); + await expect(toggleConnectLike(1)).rejects.toThrow("좋아요 요청 실패"); + }); + + // 존재하지 않는 게시글 → throw 검증 + it("존재하지 않는 게시글에 좋아요 시 에러를 throw한다", async () => { + server.use( + http.post(`${BASE_URL}/posts/:postId/like`, () => HttpResponse.json({}, { status: 404 })), + ); + await expect(toggleConnectLike(9999)).rejects.toThrow("좋아요 요청 실패"); + }); +}); + +describe("deleteConnectLike", () => { + // 정상 흐름 + it("정상 응답 시 에러 없이 완료된다", async () => { + await expect(deleteConnectLike(1)).resolves.not.toThrow(); + }); + + // 500 에러 → throw 검증 + it("서버 에러 시 에러를 throw한다", async () => { + server.use( + http.delete(`${BASE_URL}/posts/:postId/like`, () => HttpResponse.json({}, { status: 500 })), + ); + await expect(deleteConnectLike(1)).rejects.toThrow("좋아요 취소 실패"); + }); + + // 존재하지 않는 게시글 → throw 검증 + it("존재하지 않는 게시글에 좋아요 취소 시 에러를 throw한다", async () => { + server.use( + http.delete(`${BASE_URL}/posts/:postId/like`, () => HttpResponse.json({}, { status: 404 })), + ); + await expect(deleteConnectLike(9999)).rejects.toThrow("좋아요 취소 실패"); + }); +}); diff --git a/features/connect/comment/mappers.test.ts b/features/connect/comment/mappers.test.ts index 8670ac41..cee7eecc 100644 --- a/features/connect/comment/mappers.test.ts +++ b/features/connect/comment/mappers.test.ts @@ -5,6 +5,8 @@ const baseComment: PostComment = { id: 10, content: "좋은 글이에요!", createdAt: "2024-02-01T12:00:00.000Z", + likeCount: 0, + isLiked: false, author: { id: 99, name: "김댓글", @@ -26,6 +28,20 @@ describe("mapCommentToCard", () => { expect(result.authorName).toBe("김댓글"); }); + + it("반환 객체가 CommentCardItem에 맞는 구조를 가진다", () => { + const result = mapCommentToCard(baseComment); + + expect(result).toEqual({ + id: 10, + content: "좋은 글이에요!", + authorName: "김댓글", + authorImage: "https://example.com/avatar.jpg", + date: new Date("2024-02-01T12:00:00.000Z").getTime(), + likeCount: 0, + isLiked: false, + }); + }); }); describe("authorImage 매핑", () => { @@ -59,5 +75,32 @@ describe("mapCommentToCard", () => { expect(result.date).not.toBe("2024-02-01T12:00:00.000Z"); }); + + it("서로 다른 createdAt은 서로 다른 date를 반환한다", () => { + const comment1 = { ...baseComment, createdAt: "2024-01-01T00:00:00.000Z" }; + const comment2 = { ...baseComment, createdAt: "2024-06-01T00:00:00.000Z" }; + + expect(mapCommentToCard(comment1).date).not.toBe(mapCommentToCard(comment2).date); + }); + }); + + describe("엣지 케이스", () => { + it("content가 빈 문자열이어도 그대로 매핑된다", () => { + const result = mapCommentToCard({ ...baseComment, content: "" }); + + expect(result.content).toBe(""); + }); + + it("content에 특수문자가 있어도 그대로 매핑된다", () => { + const result = mapCommentToCard({ ...baseComment, content: "굵게 & '따옴표'" }); + + expect(result.content).toBe("굵게 & '따옴표'"); + }); + + it("id가 0이어도 올바르게 매핑된다", () => { + const result = mapCommentToCard({ ...baseComment, id: 0 }); + + expect(result.id).toBe(0); + }); }); }); diff --git a/features/connect/post/mappers.test.ts b/features/connect/post/mappers.test.ts index 33c7c3c2..9def92d5 100644 --- a/features/connect/post/mappers.test.ts +++ b/features/connect/post/mappers.test.ts @@ -84,19 +84,4 @@ describe("mapPostToCard", () => { expect(result.imageUrl).toBeNull(); }); }); - - describe("createdAt → date 변환", () => { - it("createdAt 문자열이 timestamp(number)로 변환된다", () => { - const result = mapPostToCard(basePost); - - expect(typeof result.date).toBe("number"); - expect(result.date).toBe(new Date("2024-01-15T09:00:00.000Z").getTime()); - }); - - it("date가 문자열이 아닌 숫자 타입이다", () => { - const result = mapPostToCard(basePost); - - expect(result.date).not.toBe("2024-01-15T09:00:00.000Z"); - }); - }); });