1. 현재 프로젝트 구성
Next.js App router 사용
Typescript
Eslint
2. 테스트 툴 선택
Jest 와 Testing Library 를 사용하기로 결정
3. 세팅
1) Testing Libray React 패키지 설치
(참고 : Installation - With Typescript)
npm install --save-dev @testing-library/react @testing-library/dom @types/react @types/react-dom
2) Jest (React) 패키지 설치
(참고: Testing React Apps - Setup With Create React App)
npm install --save-dev jest react-test-renderer
(참고: Next.js JEST- Manual Setup)
npm install -D jest-environment-jsdom @testing-library/jest-dom
3) Typescript 용 추가 패키지 설치
npm i --save-dev @types/jest ts-jest
4) Eslint 용 추가 패키지 설치
npm i -D eslint-plugin-jest
5) 테스트 스크립트 추가
✅ 설치 후 package.json
"scripts": {
"test": "jest"
},
"dependencies": {
"react": "^18",
"react-dom": "^18",
},
"devDependencies": {
"@testing-library/dom": "^10.1.0",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.12",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"react-test-renderer": "^18.3.1",
"eslint-plugin-jest": "^28.6.0",
"ts-jest": "^29.1.5",
}
6) tsconfig.json 에 types 추가
"compilerOptions": {
"types": ["node", "jest", "@testing-library/jest-dom"],
7) jest init 으로 기본 config 세팅
npm init jest@latest
✅ jset.config.ts 파일 확인
Next.js Jest config 사용하여 수정 (참고)
/**
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
import type { Config } from "jest";
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: "./",
});
const config: Config = {
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
// An array of file extensions your modules use
moduleFileExtensions: ["js", "mjs", "cjs", "jsx", "ts", "tsx", "json", "node"],
// A preset that is used as a base for Jest's configuration
preset: "ts-jest",
// The test environment that will be used for testing
testEnvironment: "jsdom",
};
export default createJestConfig(config);
8) .eslintrc.json에 설정 추가 (기존설정에 jest 관련 설정 추가)
"plugins": ["jsx-a11y", "@typescript-eslint", "prettier", "jest"],
"env": {
// 전역객체를 eslint가 인식하는 구간
"browser": true, // document나 window 인식되게 함
"node": true,
"es6": true,
"jest/globals": true
},
"ignorePatterns": [
"node_modules/",
".eslintrc.js",
"next.config.js",
"postcss.config.js",
"run_server.js",
"jest.config.ts"
], // eslint 미적용될 폴더나 파일 명시
4. 예제 코드 작성
Jest 페이지에서 제공하는 CheckboxWithLabel.js 컴포넌트를 만들고, 테스트 코드를 작성
typescript 버전으로 작성
1) CheckboxWithLabel.tsx
import { useState } from "react";
export default function CheckboxWithLabel({ labelOn, labelOff }: { labelOn: string; labelOff: string }) {
const [isChecked, setIsChecked] = useState(false);
const onChange = () => {
setIsChecked(!isChecked);
};
return (
<label>
<input type="checkbox" checked={isChecked} onChange={onChange} />
{isChecked ? labelOn : labelOff}
</label>
);
}
2) 프로젝트 루트에 __tests__/CheckboxWithLabel.test.tsx
import { cleanup, fireEvent, render } from "@testing-library/react";
import CheckboxWithLabel from "@/components/ui/CheckboxWithLabel";
// Note: running cleanup afterEach is done automatically for you in @testing-library/react@9.0.0 or higher
// unmount and cleanup DOM after the test is finished.
afterEach(cleanup);
it("CheckboxWithLabel changes the text after click", () => {
const { queryByLabelText, getByLabelText } = render(<CheckboxWithLabel labelOn="On" labelOff="Off" />);
expect(queryByLabelText(/off/i)).toBeTruthy();
fireEvent.click(getByLabelText(/off/i));
expect(queryByLabelText(/on/i)).toBeTruthy();
});
3) 테스트 실행
npm run test
5. 실제 내가 원하는 테스트 내용
- url 로 다국가 처리중 : /{country}/{language} 👉 /kr/ko, /us/en, /ch/zh, /tw/zh, /jp/ja...
- 국가별로 지원하는 서비스가 다름
- kr : 주소로 배달, 매장 픽업
- us : 주소로 배달, 매장 픽업
- tw : 주소로 배달, 매장 픽업, 편의점 픽업
- jp : 주소로 배달
- next-intl 을 사용하여 다국어 처리를 하고 있음
- /locales/ko/common.json 파일 안에 key-value 값으로 관리
- <ShippingOptions> 라는 컴포넌트는 국가에 맞게, 필요한 버튼을 노출시켜주는 컴포넌트
TODO 👉 테스트 내용 : url 로 국가가 변경될 때마다 적절한 버튼들이 랜더링 되는가
1) ShippingOptions 컴포넌트 코드
import { useParams } from "next/navigation";
import { useEffect } from "react";
import { useCheckoutStore } from "@/shared/libs/stores/checkout";
import ConveniencePickup from "./_function/ConveniencePickup";
import ShipToAddress from "./_function/ShipToAddress";
import StorePickup from "./_function/StorePickup";
export default function ShippingOptions() {
const { country } = useParams();
const { updateShippingOption } = useCheckoutStore();
useEffect(() => {
if (!["kr", "us", "tw"].includes(country as string)) {
updateShippingOption("shipToAddress");
}
}, [country]);
if (["kr", "us"].includes(country as string))
// 스토어 픽업 지원 국가
return (
<div className="flex gap-[8px] mb-[24px]">
<ShipToAddress />
<StorePickup />
</div>
);
if (country === "tw")
// 스토어 픽업 & 편의점 픽업 지원 국가
return (
<div className="flex gap-[8px] flex-col">
<ShipToAddress />
<StorePickup />
<ConveniencePickup />
</div>
);
return (
<div className="mb-[24px]">
<ShipToAddress />
</div>
);
}
2) /__tests__/ShippingOptions.test 테스트 코드
/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
import "@testing-library/jest-dom";
import { render } from "@testing-library/react";
import { useParams } from "next/navigation";
import { NextIntlProvider } from "next-intl";
import ShippingOptions from "@/components/pages/Checkout/ShippingStep/ShippingOptions";
// Mock useParams from next/navigation
jest.mock("next/navigation", () => ({
useParams: jest.fn(),
}));
describe("ShippingOptions", () => {
const useRouter = jest.spyOn(require("next/router"), "useRouter");
const locale = "ko";
const messages = require(`../locales/${locale}/common.json`);
useRouter.mockImplementationOnce(() => ({
query: { locale },
}));
it("kr 은 2가지 shipping option 을 리턴한다 (배송지 주소 / 매장에서 픽업)", () => {
(useParams as jest.Mock).mockReturnValue({ country: "kr" });
const { debug } = render(
<NextIntlProvider messages={messages} locale={locale}>
<ShippingOptions />
</NextIntlProvider>,
);
// expect(getByText("배송지 주소")).toBeInTheDocument();
// expect(getByText("매장에서 픽업")).toBeInTheDocument();
debug();
});
it("jp 은 1가지 shipping option 을 리턴한다 (배송지 주소)", () => {
(useParams as jest.Mock).mockReturnValue({ country: "jp" });
const { getByText } = render(
<NextIntlProvider messages={messages} locale={locale}>
<ShippingOptions />
</NextIntlProvider>,
);
expect(getByText("배송지 주소")).toBeInTheDocument();
});
it("tw 은 3가지 shipping option 을 리턴한다 (배송지 주소 / 매장에서 픽업 / 편의점에서 픽업)", () => {
(useParams as jest.Mock).mockReturnValue({ country: "tw" });
const { getByText } = render(
<NextIntlProvider messages={messages} locale={locale}>
<ShippingOptions />
</NextIntlProvider>,
);
expect(getByText("배송지 주소")).toBeInTheDocument();
expect(getByText("매장에서 픽업")).toBeInTheDocument();
expect(getByText("편의점에서 픽업")).toBeInTheDocument();
});
});
3) 테스트 실행
npm run test
4) 테스트 결과
debug() 를 사용하면, 실제 생성되는 html 을 확인 할 수 있다
'programming > React' 카테고리의 다른 글
[Next.js] Google Map Distance Matrix API 프론트단에서 사용하기 (0) | 2024.08.21 |
---|---|
Next.js App Router SSR 용 fetch 코드 (헤더 쿠키, Authorization 세팅) (0) | 2024.08.13 |
Next.js 에서 Client Side Fetch 를 React-Query + 기본 Fetch 조합으로 사용하기 (0) | 2024.05.29 |
Next.js 의 Fast Refresh 는 하나의 브라우저 세션과만 동작한다 (0) | 2024.05.24 |
Next App Router 환경 + MSW 설정 (0) | 2024.04.09 |