๊ฐœ์ธ๊ณต๋ถ€/TDD

React Testing: Components testing

soon327 2021. 11. 25. 21:32


๐Ÿ˜Ž React Compontets testing

๋ฆฌ์•กํŠธ ๊ณต์‹ํŽ˜์ด์ง€๋ฅผ ๋ณด๋ฉด components testing์— ์•„๋ž˜์™€ ๊ฐ™์ด ๋‘๊ฐ€์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”์ฒœํ•˜๊ณ ์žˆ๋‹ค.

Jest is a JavaScript test runner that lets you access the DOM via jsdom. While jsdom is only an approximation of how the browser works, it is often good enough for testing React components. Jest provides a great iteration speed combined with powerful features like mocking modules and timers so you can have more control over how the code executes.

React Testing Library is a set of helpers that let you test React components without relying on their implementation details. This approach makes refactoring a breeze and also nudges you towards best practices for accessibility. Although it doesnโ€™t provide a way to โ€œshallowlyโ€ render a component without its children, a test runner like Jest lets you do this by mocking.

Jest์™€ React Testing Library๋ฅผ ์กฐํ•ฉํ•˜๋ฉด ๋‹ค์–‘ํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์‰ฝ๊ฒŒ ํ…Œ์ŠคํŒ…ํ•  ์ˆ˜ ์žˆ๋‹ค. ํŠนํžˆ ์ปดํฌ๋„ŒํŠธ ์•ˆ์˜ ๋‹ค์–‘ํ•œ ํ•จ์ˆ˜๋“ค์„ ํ…Œ์ŠคํŒ…ํ•  ๋•Œ, mocking์„ ํ†ตํ•ด์„œ ํ•จ์ˆ˜์˜ ์ž‘๋™์„ ์‰ฝ๊ฒŒ ํ…Œ์ŠคํŒ…ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

// TodoFilters.test.ts

import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import prepareReduxWrapper from '@/lib/prepareReduxWrapper';
import TodoFilters from '@/components/TodoFilters';

describe('TodoFilters', () => {
  const setup = () => {
    const [Wrapper, store] = prepareReduxWrapper();
    render(
      <Wrapper>
        <TodoFilters />
      </Wrapper>
    );
    return { store };
  };

  it('submit new todo', async () => {
    setup();
    const allButton = await screen.findByText('์ „์ฒด');
    expect(allButton).toBeDisabled();

    const doneButton = await screen.findByText('์™„๋ฃŒ');
    fireEvent.click(doneButton);
    expect(doneButton).toBeDisabled();

    const undoneButton = await screen.findByText('๋ฏธ์™„๋ฃŒ');
    fireEvent.click(undoneButton);
    expect(undoneButton).toBeDisabled();
  });
});

์œ„์˜ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋Š” TodoFilters ์ปดํฌ๋„ŒํŠธ์—์„œ์˜ 3๊ฐ€์ง€ ๋ฒ„ํŠผ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค. ์‚ดํŽด๋ณด๋ฉด ์ „์ฒด, ์™„๋ฃŒ, ๋ฏธ์™„๋ฃŒ ๋ฒ„ํŠผ 3๊ฐ€์ง€๊ฐ€ ์žˆ๊ณ  ํด๋ฆญ๋œ ๋ฒ„ํŠผ์ด disabled๋˜๋Š”์ง€ ํ…Œ์ŠคํŒ…ํ•˜์˜€๋‹ค.
์ด์ฒ˜๋Ÿผ React-testing-library๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ •์ ํ…Œ์ŠคํŒ…๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋™์ ํ…Œ์ŠคํŒ…๊นŒ์ง€ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

React-testing-library ์ฃผ์š” API

render

render๋Š” DOM์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋žœ๋”๋ง ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜์ด๋‹ค. ์ธ์ž๋กœ ๋žœ๋”๋งํ•  React ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ›์œผ๋ฉฐ, React Testing Library๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ชจ๋“  ์ฟผ๋ฆฌํ•จ์ˆ˜์™€ ๊ธฐํƒ€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ๋‹ด๊ณ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์•„๋ž˜์™€๊ฐ™์ด ์‚ฌ์šฉํ•˜์—ฌ ์›ํ•˜๋Š” ์ฟผ๋ฆฌํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import { render } from "@testing-library/react";

 ...

it("text testing", () => {
    const wrarpper = render(<TodoFilters/>)
    expect(wrapper.getByText('abcd')).toBeInTheDocument()
    );
})

it("image testing", () => {
  const { getByAltText } = render(<NotFound path="/abc" />);

  // ์ด๋ฏธ์ง€๋Š” alt ์†์„ฑ๊ฐ’์„ ์ด์šฉํ•˜๋Š” getAltText ์ฟผ๋ฆฌํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  const image = getByAltText("404");
  expect(image).toHaveAttribute(
    "src",
    "https://media.giphy.com/media/14uQ3cOFteDaU/giphy.gif"
  );
});

ํ˜น์€ ์•„๋ž˜์ฒ˜๋Ÿผ ๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹น์œผ๋กœ ์‚ฌ์šฉํ•  ์ฟผ๋ฆฌํ•จ์ˆ˜๋งŒ์„ ์–ป์–ด์˜ฌ ์ˆ˜๋„ ์žˆ๋‹ค.

const { getByText, getByLabelText, getByPlaceholderText } = render(<YourComponent />)

์ฟผ๋ฆฌํ•จ์ˆ˜๋Š” getByXxx(), queryByXxx(), findByXxx() ๋“ฑ ๋‹ค์–‘ํ•˜๋ฉฐ ์ด๊ณณ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค.

fireEvent

fireEvent ๊ฐ์ฒด๋Š” ํŠน์ • ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•œ ์ด๋ฒคํŠธ ํ•จ์ˆ˜๋“ค์„ ๋‹ด๊ณ  ์žˆ๋‹ค. ๋ณดํ†ต ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋‹ค์–‘ํ•œ ์ฟผ๋ฆฌํ•จ์ˆ˜๋กœ ์ด๋ฒคํŠธ ๋Œ€์ƒ์„ ์„ค์ •ํ•˜๊ณ  change, click ๋“ฑ์˜ ์ด๋ฒคํŠธ๋ฅผ ์ž‘๋™์‹œ์ผœ ํ…Œ์ŠคํŒ…ํ•œ๋‹ค.

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

 ...

  const button = getByText("๋กœ๊ทธ์ธ");
  const email = getByLabelText("์ด๋ฉ”์ผ");
  const password = getByLabelText("๋น„๋ฐ€๋ฒˆํ˜ธ");

  fireEvent.change(email, { target: { value: "user@test.com" } });
  fireEvent.change(password, { target: { value: "Test1234" } });
  fireEvent.click(button);

 ...

๐Ÿ™ ์ฐธ๊ณ