Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#6 샘플 상품 목록 페이지 Jest 단위 테스트 코드를 작성한다 #7

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions __tests__/pages/sample/product/list.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import {fireEvent, render, screen, waitFor} from "@testing-library/react"
import "@testing-library/jest-dom"
import ProductListPage from "@/pages/sample/product/list";
import mockRouter from "next-router-mock";
import {when} from "jest-when";
import {useProducts} from "@/client/sample/product";
import dayjs from "dayjs";

jest.mock("@/client/sample/product");

describe("테이블 랜더링", () => {
it("URL 파라메터가 없으면 1페이지에 해당하는 아이템을 5개씩 테이블에 보여준다", async () => {
// given
when(useProducts as jest.Mock).calledWith({
page: 1
}).mockReturnValue({
data: {
data: {
page: {
currentPage: 1,
pageSize: 5,
totalPage: 1,
totalCount: 6
},
items: [
{
id: 1,
code: "A0001",
brand: "apple",
name: "iPhone 14 Pro",
price: 1550000,
status: "SALE",
createdAt: "2023-02-02T10:00:00+09:00",
updatedAt: "2023-02-02T10:00:00+09:00",
},
{
id: 2,
code: "A0002",
brand: "파타고니아",
name: "클래식 레트로-X 후리스 플리스 자켓",
price: 230000,
status: "SALE",
createdAt: "2023-02-02T11:00:00+09:00",
updatedAt: "2023-02-02T11:00:00+09:00",
},
{
id: 3,
code: "A0003",
brand: "다이슨",
name: "dyson v15 detect complete",
price: 1290000,
status: "SOLDOUT",
createdAt: "2023-02-02T12:00:00+09:00",
updatedAt: "2023-02-02T12:00:00+09:00",
},
{
id: 4,
code: "A0004",
brand: "Aēsop",
name: "레저렉션 아로마틱 핸드 워시",
price: 47000,
status: "NOTSALE",
createdAt: "2023-02-02T13:00:00+09:00",
updatedAt: "2023-02-02T13:00:00+09:00",
},
{
id: 5,
code: "A0005",
brand: "LUSH",
name: "더티 보디 스프레이",
price: 60000,
status: "SALE",
createdAt: "2023-02-02T14:00:00+09:00",
updatedAt: "2023-02-02T14:00:00+09:00",
},
]
}
}
});

// when
await mockRouter.push("/sample/product/list");
render(<ProductListPage/>)
await waitFor(() => screen.getByTestId("product-table"));

// then
const productTable = screen.getByTestId("product-table");
expect(productTable).toBeInTheDocument();

// 테이블 헤더 확인
const tableHeader = productTable.querySelector("thead > tr");
expect(tableHeader).not.toBeNull();
const headerColumns = (tableHeader as Element).querySelectorAll("th");
expect(headerColumns).toHaveLength(7);
expect(headerColumns[0]).toHaveTextContent("");
expect(headerColumns[1]).toHaveTextContent("상품코드");
expect(headerColumns[2]).toHaveTextContent("상품명");
expect(headerColumns[3]).toHaveTextContent("금액");
expect(headerColumns[4]).toHaveTextContent("판매상태");
expect(headerColumns[5]).toHaveTextContent("생성일시");
expect(headerColumns[6]).toHaveTextContent("수정일시");

// 테이블 바디 확인
const tableBodyRows = productTable.querySelectorAll("tbody > tr");
expect(tableBodyRows).toHaveLength(6); // ant table은 컨텐츠이 담기지 않는 TR이 하나 존재하기 때문에 컨텐츠 + 1를 확인한다.

const bodyRow1Columns = tableBodyRows[1].querySelectorAll("td");
expect(bodyRow1Columns).toHaveLength(8);
expect(bodyRow1Columns[2]).toHaveTextContent("A0001");
expect(bodyRow1Columns[3]).toHaveTextContent("appleiPhone 14 Pro");
expect(bodyRow1Columns[4]).toHaveTextContent("1,550,000원");
expect(bodyRow1Columns[5]).toHaveTextContent("SALE");
expect(bodyRow1Columns[6]).toHaveTextContent("2023/02/0210:00");
expect(bodyRow1Columns[7]).toHaveTextContent("2023/02/0210:00");

const bodyRow2Columns = tableBodyRows[2].querySelectorAll("td");
expect(bodyRow2Columns).toHaveLength(8);
expect(bodyRow2Columns[2]).toHaveTextContent("A0002");
expect(bodyRow2Columns[3]).toHaveTextContent("파타고니아클래식 레트로-X 후리스 플리스 자켓");
expect(bodyRow2Columns[4]).toHaveTextContent("230,000원");
expect(bodyRow2Columns[5]).toHaveTextContent("SALE");
expect(bodyRow2Columns[6]).toHaveTextContent("2023/02/0211:00");
expect(bodyRow2Columns[7]).toHaveTextContent("2023/02/0211:00");

const bodyRow3Columns = tableBodyRows[3].querySelectorAll("td");
expect(bodyRow3Columns).toHaveLength(8);
expect(bodyRow3Columns[2]).toHaveTextContent("A0003");
expect(bodyRow3Columns[3]).toHaveTextContent("다이슨dyson v15 detect complete");
expect(bodyRow3Columns[4]).toHaveTextContent("1,290,000원");
expect(bodyRow3Columns[5]).toHaveTextContent("SOLDOUT");
expect(bodyRow3Columns[6]).toHaveTextContent("2023/02/0212:00");
expect(bodyRow3Columns[7]).toHaveTextContent("2023/02/0212:00");

const bodyRow4Columns = tableBodyRows[4].querySelectorAll("td");
expect(bodyRow4Columns).toHaveLength(8);
expect(bodyRow4Columns[2]).toHaveTextContent("A0004");
expect(bodyRow4Columns[3]).toHaveTextContent("Aēsop레저렉션 아로마틱 핸드 워시");
expect(bodyRow4Columns[4]).toHaveTextContent("47,000원");
expect(bodyRow4Columns[5]).toHaveTextContent("NOTSALE");
expect(bodyRow4Columns[6]).toHaveTextContent("2023/02/0201:00");
expect(bodyRow4Columns[7]).toHaveTextContent("2023/02/0201:00");

const bodyRow5Columns = tableBodyRows[5].querySelectorAll("td");
expect(bodyRow5Columns).toHaveLength(8);
expect(bodyRow5Columns[2]).toHaveTextContent("A0005");
expect(bodyRow5Columns[3]).toHaveTextContent("LUSH더티 보디 스프레이");
expect(bodyRow5Columns[4]).toHaveTextContent("60,000원");
expect(bodyRow5Columns[5]).toHaveTextContent("SALE");
expect(bodyRow5Columns[6]).toHaveTextContent("2023/02/0202:00");
expect(bodyRow5Columns[7]).toHaveTextContent("2023/02/0202:00");
});

it("URL 파라메터 page=2 이면 2페이지에 해당하는 아이템을 최대 5개를 테이블에 보여준다", async () => {
// given
when(useProducts as jest.Mock).calledWith({
page: 2
}).mockReturnValue({
data: {
data: {
page: {
currentPage: 1,
pageSize: 5,
totalPage: 1,
totalCount: 6
},
items: [
{
id: 6,
code: "A0006",
brand: "블루보틀",
name: "쓰리 아프리카스",
price: 25000,
status: "SALE",
createdAt: dayjs("2023-02-02T15:00:00+09:00"),
updatedAt: dayjs("2023-02-02T15:00:00+09:00"),
},
]
}
}
});

// when
await mockRouter.push("/sample/product/list?page=2");
render(<ProductListPage/>)
await waitFor(() => screen.getByTestId("product-table"));

// then
const productTable = screen.getByTestId("product-table");
expect(productTable).toBeInTheDocument();

// 테이블 바디 확인
const tableBodyRows = productTable.querySelectorAll("tbody > tr");
expect(tableBodyRows).toHaveLength(2); // ant table은 컨텐츠이 담기지 않는 TR이 하나 존재하기 때문에 컨텐츠 + 1를 확인한다.

const bodyRow1Columns = tableBodyRows[1].querySelectorAll("td");
expect(bodyRow1Columns).toHaveLength(8);
expect(bodyRow1Columns[2]).toHaveTextContent("A0006");
expect(bodyRow1Columns[3]).toHaveTextContent("블루보틀쓰리 아프리카스");
expect(bodyRow1Columns[4]).toHaveTextContent("25,000원");
expect(bodyRow1Columns[5]).toHaveTextContent("SALE");
expect(bodyRow1Columns[6]).toHaveTextContent("2023/02/0203:00");
expect(bodyRow1Columns[7]).toHaveTextContent("2023/02/0203:00");
});
});

describe("버튼 이벤트", () => {
it("상품 등록 버튼을 클릭하면 상품 등록 페이지로 이동한다", async () => {
// given
when(useProducts as jest.Mock).calledWith({
page: 1
}).mockReturnValue({});

await mockRouter.push("/sample/product/list");
render(<ProductListPage/>)
await waitFor(() => screen.getByTestId("create-product-btn"));

// when
const button = screen.getByTestId("create-product-btn");
fireEvent.click(button);

// then
expect(mockRouter).toMatchObject({
pathname: "/sample/product/new",
query: {},
});
});
});
27 changes: 27 additions & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})

// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const config = {
rootDir: ".",
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
moduleNameMapper: {
"^@/lib/(.*)$": "<rootDir>/src/lib/$1",
"^@/pages/(.*)$": "<rootDir>/src/pages/$1",
"^@/components/(.*)$": "<rootDir>/src/components/$1",
"^@/client/(.*)$": "<rootDir>/src/client/$1",
},
moduleDirectories: ["node_modules", "<rootDir>/"],
testEnvironment: 'jest-environment-jsdom',
}

export default async () => ({
...(await createJestConfig(config)()),
// ESM 모듈을 Jest에서 사용하기 위한 설정 추가
transformIgnorePatterns: ["node_modules/(?!(ky-universal|ky)/)"],
});
9 changes: 9 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Object.defineProperty(window, 'matchMedia', {
value: () => ({
matches: false,
addListener: () => {},
removeListener: () => {},
}),
});

jest.mock('next/router', () => require('next-router-mock'));
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build": "next build",
"export": "next export",
"start": "next start",
"test": "jest --watch",
"lint": "next lint"
},
"dependencies": {
Expand All @@ -29,6 +30,10 @@
"swr": "^2.2.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.5",
"@types/jest-when": "^3.5.3",
"@types/node": "18.11.18",
"@types/numeral": "^2.0.2",
"@types/qs": "^6.9.7",
Expand All @@ -38,6 +43,10 @@
"eslint": "8.46.0",
"eslint-config-next": "13.4.12",
"eslint-config-prettier": "^8.9.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-when": "^3.6.0",
"next-router-mock": "^0.9.10",
"postcss": "^8.4.27",
"prettier": "^3.0.0",
"tailwindcss": "^3.3.3",
Expand Down
3 changes: 2 additions & 1 deletion src/components/page/sample/product/product-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,14 @@ const ProductList = () => {
<Button className="btn-with-icon" icon={<Download />}>
엑셀 다운로드
</Button>
<Button type="primary" onClick={() => router.push("/sample/product/new")}>
<Button data-testid="create-product-btn" type="primary" onClick={() => router.push("/sample/product/new")}>
상품등록
</Button>
</div>
</DefaultTableBtn>

<DefaultTable<IProduct>
data-testid="product-table"
rowSelection={rowSelection}
columns={columns}
dataSource={data?.data.items || []}
Expand Down
4 changes: 2 additions & 2 deletions src/components/page/sample/product/product-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const ProductSearch = () => {
<FormSearch>
<FieldInline>
<Form.Item label="기간" name="searchDateType" initialValue="created">
<Select dropdownMatchSelectWidth={false}>
<Select popupMatchSelectWidth={false}>
<Select.Option value="created">등록일자</Select.Option>
<Select.Option value="updated">수정일자</Select.Option>
</Select>
Expand All @@ -52,7 +52,7 @@ const ProductSearch = () => {
<div>
<FieldInline>
<Form.Item label="검색조건" name="searchType" initialValue="productName">
<Select dropdownMatchSelectWidth={false}>
<Select popupMatchSelectWidth={false}>
<Select.Option value="productName">상품명</Select.Option>
<Select.Option value="brandName">브랜드명</Select.Option>
</Select>
Expand Down