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

충남대 FE_박건민 5주차 과제 #74

Open
wants to merge 6 commits into
base: geonbur
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
# 카카오 테크 캠퍼스 - 프론트엔드 카카오 선물하기 편

# 1단계: Form 부분 테스트 코드 작성

[x] Jest와 React Testing Libaray를 사용하여 테스트 기반 환경 구축
[x] MSW를 사용하여 Mock API가 동작하도록 구현. (상세 API / 옵션 API)
[x] 단위 테스트로 작성하면 좋을 테스트가 있다면 단위테스트 코드 작성.
[x] 상품 상세 페이지와 관련된 통합 테스트 코드 작성.
[x] 결제하기 페이지의 Form과 관련된 통합 테스트 코드 작성.
현금영수증 Checkbox가 false인 경우 현금영수증 종류, 현금영수증 번호 field가 비활성화 되어있는지 확인하는 테스트 코드 작성. (만약 true인 경우 현금영수증 종류, 번호 field에 값이 입력 되어야 함)
form의 validation 로직이 정상 동작하는지 확인하는 테스트 코드 작성.

# 2단계: 로그인, 관심 상품 등록 / 삭제, 관심 목록 구현

[ ] 로그인 기능 구현.
[ ] 회원가입 화면을 만들고, 회원가입 기능이 동작되게 구현. (회원가입을 하면 로그인이 되도록)
회원가입 버튼은 로그인 화면 하단에 배치하고, 로그인 화면은 그대로 사용
[ ] 상품 상세 페이지에서 관심 등록 버튼 생성
[ ] 상품 상세 페이지에서 관심 버튼을 클릭 시 관심 추가
관심 등록 성공 시 Alert로 "관심 등록 완료" 메시지를 노출
[ ] 나의 계정 페이지에서 관심 목록 리스트 생성
관심 목록 리스트는 chakra UI를 사용하여 구현
관심 목록 API는 카카오테크 선물하기 API 노션의 response 데이터 사용
[ ] 관심 목록 리스트에서 관심 삭제가 가능하도록 구현
관심 삭제 시 목록에서 사라지도록 구현

- 질문 1. Test code를 작성해보면서 좋았던 점과 아쉬웠던 점에 대해 말해주세요.
직접 실행해서 하나씩 확인 하는 것보다 체계적으로 코드의 작동을 확인 할 수 있음. 하지만 Fail을 할 경우 테스트 코드의 어느 부분에서 오류가 났는지를 알 수가 없어서 수정에 어려움이 있었음.

- 질문 2. 스스로 생각했을 때 좋은 컴포넌트란 무엇인지 본인만의 기준을 세우고 설명해 주세요.
재사용성, 독립성을 가진 컴포턴트가 좋은 컴포넌트라고 생각. 또한 외부와의 인터페이스는 props로 명확하게 정의해야 하고, 불필요한 리렌더링을 최소화하면 좋음.

- 질문 3. 스스로 생각했을 때 공통 컴포넌트를 만들 때 가장 중요한 요소 2개를 선택하고 이유와 함께 설명해주세요.
재사용성: 동일한 컴포넌트를 사용함으로써 코드 중복을 줄이고 유지보수를 용이하게 함.
일관성: 전반적인 스타일과 동작을 통일성 있게 유지하면 UX에 좋은 영향을 줄 수 있다고 생각함.
7 changes: 7 additions & 0 deletions craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,11 @@ module.exports = {
'@': path.resolve(__dirname, 'src'),
},
},
jest: {
configure: {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
},
},
};
93 changes: 88 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@storybook/react": "^7.6.17",
"@storybook/react-webpack5": "^7.6.17",
"@storybook/test": "^7.6.17",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
Expand Down Expand Up @@ -83,5 +84,10 @@
},
"overrides": {
"react-refresh": "0.11.0"
},
"jest": {
"moduleNameMapper": {
"^axios$": "axios/dist/node/axios.cjs"
}
}
}
20 changes: 20 additions & 0 deletions src/components/common/Button/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { fireEvent, render, screen } from '@testing-library/react';

// import React from 'react';
import { Button } from './index';

test('버튼 렌더링과 클릭 테스트', () => {
const mockClickHandler = jest.fn();
const buttonText = 'Click';

render(
<Button onClick={mockClickHandler} data-testid="custom-button">
{buttonText}
</Button>,
);
const buttonElement = screen.getByTestId('custom-button');
fireEvent.click(buttonElement);

expect(buttonElement).toBeInTheDocument();
expect(mockClickHandler).toHaveBeenCalledTimes(1);
});
87 changes: 87 additions & 0 deletions src/components/features/Order/OrderForm/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// src\components\features\Order\OrderForm\index.test.tsx

import '@testing-library/jest-dom';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';

import { AuthProvider, useAuth } from '@/provider/Auth';

import { OrderForm } from './index'; // 결제하기 폼 컴포넌트 경로

const setupQueryClient = new QueryClient();

const customRender = (ui: React.ReactNode, { route = '/' } = {}) => {
window.history.pushState({}, 'Test page', route);
return render(
<QueryClientProvider client={setupQueryClient}>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/" element={ui} />
</Routes>
</AuthProvider>
</BrowserRouter>
</QueryClientProvider>,
);
};

// Mocking AuthProvider and useAuth
jest.mock('@/provider/Auth', () => ({
...jest.requireActual('@/provider/Auth'),
useAuth: jest.fn(),
}));

describe('OrderForm Component Tests', () => {
beforeEach(() => {
(useAuth as jest.Mock).mockReturnValue({ id: 'testUser', name: 'testUser', token: 'testUser' });
});

test('현금영수증 Checkbox가 false인 경우 현금영수증 종류, 현금영수증 번호 field가 비활성화 되어있는지 확인', async () => {
customRender(<OrderForm orderHistory={{ id: 1, count: 1 }} />);

const cashReceiptCheckbox = screen.getByLabelText('현금영수증 신청');
const cashReceiptType = screen.getByLabelText('현금영수증 종류');
const cashReceiptNumber = screen.getByPlaceholderText('(-없이) 숫자만 입력해주세요.');

expect(cashReceiptCheckbox).toBeInTheDocument();
expect(cashReceiptType).toBeDisabled();
expect(cashReceiptNumber).toBeDisabled();

fireEvent.click(cashReceiptCheckbox);

await waitFor(() => {
expect(cashReceiptType).toBeEnabled();
expect(cashReceiptNumber).toBeEnabled();
});

fireEvent.change(cashReceiptNumber, { target: { value: '010' } });

await waitFor(() => {
expect(cashReceiptNumber).toHaveValue('010');
});
});

test('form의 validation 로직이 정상 동작하는지 확인하는 테스트', async () => {
customRender(<OrderForm orderHistory={{ id: 1, count: 1 }} />);

const submitBtn = screen.getByRole('button', { name: /결제하기/i });

fireEvent.click(submitBtn);

await waitFor(() => {
expect(screen.getByText('메시지를 입력해주세요.')).toBeInTheDocument();
});

const messageField = screen.getByPlaceholderText('선물과 함께 보낼 메시지를 적어보세요');

fireEvent.change(messageField, { target: { value: '축하해요' } });

fireEvent.click(submitBtn);

await waitFor(() => {
expect(screen.queryByText('메시지를 입력해주세요.')).not.toBeInTheDocument();
});
});
});
6 changes: 6 additions & 0 deletions src/mocks/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { setupServer } from 'msw/node';

import { categoriesMockHandler } from '@/api/hooks/categories.mock';
import { productsMockHandler } from '@/api/hooks/products.mock';

export const server = setupServer(...categoriesMockHandler, ...productsMockHandler);
Loading