Hello Kitty Eyes Shut
๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ’ป๊ณต๋ถ€ ๊ธฐ๋ก/๐Ÿ“Œ Frontend

[Frontend] ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ - Testing Library & Jest

๋ฐ˜์‘ํ˜•

 

 

 

 

๐Ÿ“˜ ์™œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ• ๊นŒ?

"ํ”„๋ก ํŠธ์—”๋“œ๋Š” ๋ˆˆ์œผ๋กœ ์ง์ ‘ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ

๊ตณ์ด ํ…Œ์ŠคํŠธ๊นŒ์ง€ ํ•ด์•ผ ํ• ๊นŒ?"

 

์ด๋ ‡๊ฒŒ ์ƒ๊ฐํ•˜๋Š” ๋ถ„๋“ค๋„ ๋งŽ์ง€๋งŒ,

์‚ฌ์‹ค์€ ์˜คํžˆ๋ ค ๊ทธ ๋ฐ˜๋Œ€์ด๋‹ค.

 

 

๐Ÿ’ก ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ค‘์š”ํ•œ ์ด์œ 

โœ” ๋ฆฌํŒฉํ† ๋ง ์‹œ ๊ธฐ๋Šฅ์ด ๊นจ์ง€์ง€ ์•Š์•˜๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ” ํ˜‘์—… ์ค‘ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ณ€๊ฒฝํ•œ ์ฝ”๋“œ๊ฐ€ ๋‚ด ๊ธฐ๋Šฅ์— ์˜ํ–ฅ์„ ์ฃผ๋Š”์ง€ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ” CI / CD ์ž๋™ํ™” ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์•ˆ์ •์ ์ธ ๋ฐฐํฌ๋ฅผ ๋„์™€์ค€๋‹ค.

โœ” ๋ฒ„๊ทธ๋ฅผ ์กฐ๊ธฐ์— ๋ฐœ๊ฒฌํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

 

 

์ฆ‰, ํ…Œ์ŠคํŠธ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์—์„œ๋„

์‹ ๋ขฐ์„ฑ ์žˆ๋Š” ์ฝ”๋“œ ์ž‘์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์œ„ํ•ด

๊ผญ ํ•„์š”ํ•œ ๊ณผ์ •์ด๋‹ค.

 

 


๐Ÿงช ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ์˜ ์ข…๋ฅ˜

ํ…Œ์ŠคํŠธ ์œ ํ˜• ์„ค๋ช… ์˜ˆ์‹œ
Unit Test ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ ํ˜น์€ ํ•จ์ˆ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ฝœ๋ฐฑ์ด ํ˜ธ์ถœ๋˜๋Š”์ง€
Integration Test ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์˜ ์ƒํ˜ธ์ž‘์šฉ ํ…Œ์ŠคํŠธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์—์„œ ์ž…๋ ฅ → ๋ฒ„ํŠผ ํด๋ฆญ → ์ด๋™
E2E Test ์‹ค์ œ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—์„œ ์œ ์ € ํ”Œ๋กœ์šฐ ํ…Œ์ŠคํŠธ Cypress, Playwright ์‚ฌ์šฉ

 

 


โš™๏ธ Jest + Testing Library ์„ค์ •

pnpm install --save-dev jest @types/jest ts-jest
pnpm install --save-dev @testing-library/react @testing-library/jest-dom

 

jest ๐Ÿ‘‰๐Ÿป ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ด์ฃผ๋Š” ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ ์—ญํ• ์„ ํ•œ๋‹ค.

@testing-library/react ๐Ÿ‘‰๐Ÿป DOM ์ค‘์‹ฌ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.

@testing-library/jest-dom ๐Ÿ‘‰๐Ÿป toBeInTheDocument() ๋“ฑ ์ถ”๊ฐ€์ ์ธ matcher๋“ค์„ ์ œ๊ณตํ•ด์ค€๋‹ค.

 

๐Ÿ’ก ์ด๋•Œ, jest.config.ts ํ˜น์€ jest.config.js ๋“ฑ์˜ ์„ค์ • ํŒŒ์ผ๋„ ํ•จ๊ป˜ ์ž‘์„ฑํ•ด์ค˜์•ผ ํ•œ๋‹ค.

 


๐Ÿงพ ๊ธฐ๋ณธ ํ…Œ์ŠคํŠธ ์˜ˆ์ œ (App ์ปดํฌ๋„ŒํŠธ ๋ผ์šฐํŒ… ํ…Œ์ŠคํŠธ)

import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from '../App';

jest.mock('@/pages/main/MainPage', () => {
  const MainPage = () => <div>Main Page</div>;
  return { __esModule: true, default: MainPage };
});

jest.mock('@/pages/login/LoginPage', () => {
  const LoginPage = () => <div>Login Page</div>;
  return { __esModule: true, default: LoginPage };
});

describe('App routing', () => {
  it('renders MainPage for default route "/"', () => {
    render(
      <MemoryRouter initialEntries={['/']}>
        <App />
      </MemoryRouter>,
    );
    expect(screen.getByText('Main Page')).toBeInTheDocument();
  });

  it('renders LoginPage for route "/login"', () => {
    render(
      <MemoryRouter initialEntries={['/login']}>
        <App />
      </MemoryRouter>,
    );
    expect(screen.getByText('Login Page')).toBeInTheDocument();
  });
});

 

๐Ÿ” ํฌ์ธํŠธ ์„ค๋ช…

MemoryRouter ๐Ÿ‘‰๐Ÿป ํ…Œ์ŠคํŠธ์šฉ ๋ผ์šฐํ„ฐ๋กœ ์‹ค์ œ ๋ธŒ๋ผ์šฐ์ € ์—†์ด ๋ผ์šฐํŒ…์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค.

jest.mock ๐Ÿ‘‰๐Ÿป ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ๋งŒ ๊ฐ€์งœ๋กœ ๋งŒ๋“ค์–ด์„œ ๋…๋ฆฝ์„ฑ์„ ํ™•๋ณดํ•ด์ค€๋‹ค.

screen.getByText() ๐Ÿ‘‰๐Ÿป ์‹ค์ œ UI์—์„œ ํŠน์ • ์š”์†Œ๊ฐ€ ๋ณด์ด๋Š”์ง€ ๊ฒ€์‚ฌํ•ด์ค€๋‹ค.

expect(...).toBeInTheDocument() ๐Ÿ‘‰๐Ÿป ํ•ด๋‹น ์š”์†Œ๊ฐ€ DOM์— ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•ด์ค€๋‹ค.

 


๐Ÿงฏ CI์—์„œ ํ…Œ์ŠคํŠธ๊นŒ์ง€ ๋Œ๋ฆฌ๊ณ  ์‹ถ๋‹ค๋ฉด

- name: Run Test with Coverage
  run: pnpm run test -- --coverage --ci

 

์œ„ ์ฝ”๋“œ๋ฅผ ci.yaml ์Šคํฌ๋ฆฝํŠธ์— ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

๐Ÿ’ก ํ˜น์‹œ ์•„์ง test ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด,

์•„๋ž˜์™€ ๊ฐ™์ด --passWithNoTests ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด์„œ ์‹คํŒจํ•˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

 

- name: Run Test with Coverage
  run: pnpm run test -- --coverage --ci --passWithNoTests

 

 

๋ฐ˜์‘ํ˜•