2. React Testing Library

  • React Testing Library

  • given - when - then ํŒจํ„ด

  • Mocking

  • Test fixture

๊ฐ•์˜ ์ •๋ฆฌ

React Testing Library

๋ฆฌ์•กํŠธ ํ…Œ์ŠคํŠธ๋ฅผ ์‰ฝ๊ฒŒํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“  ๊ฒƒ ์‚ฌ์šฉ์ž ์ž…์žฅ์— ๊ฐ€๊น๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

test๋Š” testํด๋”๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์ˆ˜๋„ ์žˆ๊ณ  ๊ฐ™์€ ๊ณ„์ธต์— [filename].test.tsx ์ด๋ ‡๊ฒŒ ํŒŒ์ผ์„ ๋งŒ๋“ค์ˆ˜๋„ ์žˆ๋‹ค.

import { render, screen } from '@testing-library/react';

import TextField from './TextField';

test('TextField', () => {
  const text = 'Tester';
  const setText = () => {
    // do nothing...
  };

  render(
    <TextField
      label="Name"
      placeholder="Input your name"
      text={text}
      setText={setText}
    />,
  );

  screen.getByLabelText('Name');
});

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ, ์ฆ‰ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ ๊ฒ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ์กด์—๋Š” label์ด ๋น ์ ธ์žˆ์—ˆ๊ณ , text ๊ฐ™์ด ๋ฒ”์šฉ์ ์ธ ํ‘œํ˜„์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์šฐ๋ฆฌ๊ฐ€ ํ…Œ์ŠคํŠธ๋ถ€ํ„ฐ ์ž‘์„ฑํ–ˆ๊ฑฐ๋‚˜ ๋น ๋ฅด๊ฒŒ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด ์ž‘์„ฑํ•˜๊ธฐ ์ „ ๋˜๋Š” ๋ฐ”๋กœ ์งํ›„์— ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•ด์„œ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์—ˆ์„ ๊ฒƒ. ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ํ•ด๋‹น ์ฝ”๋“œ์— ๋Œ€ํ•œ ์ง€์‹์ด ๊ฐ์†Œํ•˜๊ณ , ์ž์‹ ๊ฐ ๋˜ํ•œ ๊ฐ์†Œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฑด๋“œ๋ฆฌ๊ธฐ ํž˜๋“  ์ฝ”๋“œ๊ฐ€ ๋˜๊ธฐ ์‹ญ์ƒ์ด๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ๋„ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์™ธ๋ถ€ ์˜์กด์„ฑ์ด ๊ฐ•ํ•œ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค๋ฉด mocking์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. jest mock BDD ์Šคํƒ€์ผ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒŒ ์ฝ๊ธฐ ์‰ฝ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋„ ์ฝ”๋“œ๋‹ค. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ฐ™์€ ๋ถ€๋ถ„, ์˜ˆ๋ฅผ ๋“ค์–ด input์— ์ˆซ์ž๋งŒ ์ž…๋ ฅ๋ฐ›๊ณ  ์‹ถ๋‹ค๋ฉด ์ด ๋ถ€๋ถ„์€ ์™ธ๋ถ€์—์„œ ์ฃผ์ž…๋ฐ›๊ฒŒ ํ•œ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ์ฑ…์ž„์„ ๊ฐ–์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€๋งŒ ํ™•์ธํ•˜๋ฉด ๋œ๋‹ค.

import { render, screen, fireEvent } from '@testing-library/react';

import TextField from './TextField';

const context = describe;

describe('TextField', () => {
  const text = 'Tester';
  const setText = jest.fn();

  beforeEach(() => {
    setText.mockClear();
    // ๋˜๋Š” jest.clearAllMocks();
  });

  function renderTextField() {
    render(
      <TextField
        label="Name"
        placeholder="Input your name"
        text={text}
        setText={setText}
      />,
    );
  }

  it('renders an input control', () => {
    renderTextField();

    screen.getByLabelText('Name');
  });

  context('when user types text', () => {
    it('calls the change handler', () => {
      renderTextField();

      fireEvent.change(screen.getByLabelText('Name'), {
        target: {
          value: 'New Name',
        },
      });

      expect(setText).toBeCalledWith('New Name');
    });
  });
});

์™ธ๋ถ€ ์˜์กด์„ฑ์ด ํฐ ํ•จ์ˆ˜๋Š” ๋ชจํ‚น์„ ํ•œ๋‹ค.

import { render, screen } from '@testing-library/react';

import App from './App';

jest.mock('./hooks/useFetchProducts', () => () => [
 {
  category: 'Fruits', price: '$1', stocked: true, name: 'Apple',
 },
]);

test('App', () => {
 render(<App />);

 screen.getByText('Apple');

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ์“ฐ์ด๋Š” ๋”๋ฏธ ๋ฐ์ดํ„ฐ๋ฅผ fixture๋ผ๊ณ  ํ•˜๋Š”๋ฐ fixtures๋ผ๋Š” ํด๋”๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ด€๋ฆฌํ•œ๋‹ค. hook์„ mockingํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ์ฒ˜์Œ์—๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋‚ด๋ถ€์— ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด ๋ณต์žกํ•ด์ง„๋‹ค. hooks/__mocks__ ํด๋”์—์„œ ๋ชจํ‚น์„ ํ•ด๋†“๊ณ  ์™ธ๋ถ€๋กœ exportํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.

TDD๋Š” ๋งŽ์€ ์—ฐ์Šต์ด ํ•„์š”ํ•˜๋‹ค. ์ถฉ๋ถ„ํžˆ ์ˆ™๋ จ๋ ๋•Œ๊นŒ์ง€ ์ˆ˜๋ จ์ด ํ•„์š”ํ•จ.

Last updated