2. React Testing Library

Keyword

  1. React Testing Library

  2. given - when - then ํŒจํ„ด

  3. Mocking

  4. Test fixture


1. React Testing Library

Behavior Driven Test(ํ–‰์œ„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ) ๋ฐฉ๋ฒ•๋ก ์ด ๋– ์˜ค๋ฅด๋ฉด์„œ ์ฃผ๋ชฉ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•œ ํ…Œ์ŠคํŒ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ์กด์˜ ๊ด€ํ–‰์ด๋˜ Implementation Driven Test(๊ตฌํ˜„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ)์˜ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ๋Œ€๋‘๋ผ์—ˆ๋‹ค.

Implementation Driven Test(๊ตฌํ˜„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ)

๊ตฌํ˜„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€์— ์ดˆ์ ์„ ๋‘”๋‹ค. ํŠน์ • ์—˜๋ฆฌ๋จผํŠธ์˜ ํƒœ๊ทธ๊ฐ€ ๋ฌด์—‡์ด๋ฉฐ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋กœ ๋ฌด์—‡์ด ์“ฐ์˜€๋Š”์ง€, ์–ด๋– ํ•œ ๊ฐ’์ด ์“ฐ์˜€๋Š”์ง€์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธํ•œ๋‹ค.

Behavior Driven Test(ํ–‰์œ„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ)

์‚ฌ์šฉ์ž์˜ ๊ด€์ ์—์„œ UI์™€ ์ธํ„ฐ๋ ‰์…˜ํ–ˆํ•˜์—ฌ ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ์— ๋”ฐ๋ผ ํ™”๋ฉด์— ๋ณ€ํ™”๊ฐ€ ์ผ์–ด๋‚˜๋Š”์ง€์— ์ดˆ์ ์„ ๋‘”๋‹ค.

Enzyme vs React Testing Library

Enzyme

๊ธฐ์กด์—” React์•ฑ์€ Enzyme์„ ์ด์šฉํ•ด IDT ๋ฐฉ๋ฒ•๋ก ์— ๋”ฐ๋ผ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์œผ๋ฉฐ, ์‹ค์ œ DOM์ด ์•„๋‹Œ React Virtual DOM์„ ๊ธฐ์ค€์œผ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋Š” State, ์ „๋‹ฌ๋˜๋Š” Props๊ฐ€ ๋ฌด์—‡์ธ์ง€์— ๋Œ€ํ•ด ๊ฒ€์ฆ์ด ์šฉ์ดํ•˜๋‹ค.

React-Testing-Library

React Testing Library๋Š” ์‹ค์ œ ๋ธŒ๋ผ์šฐ์ € DOM์„ ๊ธฐ์ค€์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ฒŒ ๋œ๋‹ค. React์˜ ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„๊ณผ ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋Œ€ํ•ด ๊ด€์‹ฌ์ด ์—†์œผ๋ฉฐ, ์‚ฌ์šฉ์ž ๋ธŒ๋ผ์šฐ์ €์— ๋ Œ๋”๋˜๋Š” UI์˜ ์‹ค์ œ ๋™์ž‘์„ ํ…Œ์ŠคํŠธํ•˜๋Š”๋ฐ ์ดˆ์ ์„ ๋งž์ถ˜๋‹ค.

render, screen, fireEvent

  • render

render() ํ•จ์ˆ˜๋Š” React-Testing-Library์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ชจ๋“  ์ฟผ๋ฆฌ ํ•จ์ˆ˜์™€ ๊ธฐํƒ€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

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


const { container, getByText, getByRole } = render(<Component />);
  • screen

screen ๊ฐ์ฒด๋Š” ๋žœ๋”๋ง๋œ ์ปดํฌ๋„ŒํŠธ์˜ DOM ์š”์†Œ๋ฅผ ์„ ํƒํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

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

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

screen.getByLabelText('Name');
  • fireEvent

fireEvent ๊ฐ์ฒด๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฒคํŠธ๋ฅผ ๊ฐ€์ƒ์œผ๋กœ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.

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

render(<Component />);
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);

2. given - when - then ํŒจํ„ด

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์— ์‚ฌ์šฉ๋˜๋Š” ํŒจํ„ด์ค‘ ํ•˜๋‚˜๋กœ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

Given(์ฃผ์–ด์ง„ ์ƒํ™ฉ): ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ธฐ์œ„ํ•ด ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ธํŒ…ํ•˜๋Š” ์ดˆ๊ธฐ ์กฐ๊ฑด, ์ดˆ๊ธฐ๊ฐ’

When(๋™์ž‘): ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ธฐ์œ„ํ•œ ๋™์ž‘์„ ์„ค์ •

then(๊ฒฐ๊ณผ): ๋™์ž‘์˜ ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™”๋Š”์ง€ ๊ฒ€์ฆ

function add(a: number, b: number) {
    return a + b;
}

function subtract(a: number, b: number) {
    return a - b;
}
const context = describe;

describe('๊ณ„์‚ฐ๊ธฐ', () => {
    context('add ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด', () => {
        it('๋‘ ์ˆ˜๋ฅผ ๋”ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค', () => {
            // Given - ์ดˆ๊ธฐ ์ƒํƒœ, ๊ฐ’
            const num1 = 5;
            const num2 = 3;

            // When - ๋™์ž‘ ์„ค์ •
            const result = add(num1, num2);

            // Then - ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ ๊ฒ€์ฆ
            expect(result).toBe(8);
        });
    });

    context('subtract ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด', () => {
        it('๋‘ ์ˆ˜๋ฅผ ๋บ€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค', () => {
            // Given - ์ดˆ๊ธฐ ์ƒํƒœ, ๊ฐ’
            const num1 = 10;
            const num2 = 4;

            // When - ๋™์ž‘ ์„ค์ •
            const result = subtract(num1, num2);

            // Then - ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ ๊ฒ€์ฆ
            expect(result).toBe(6);
        });
    });
});

3. Mocking

Mocking์€ ์™ธ๋ถ€ ์˜์กด์„ฑ(API ํ˜ธ์ถœ, DB ์ ‘๊ทผ)์„ ๊ฐ€์งœ๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์šฉํ•˜๋Š” ์ด์œ ?

  1. ์˜์กด์„ฑ ๊ฒฉ๋ฆฌ - ๋„คํŠธ์›Œํฌ ํ†ต์‹ , DB ์ ‘๊ทผ์œผ๋กœ ๋ถ€ํ„ฐ ๊ฒฉ๋ฆฌํ•˜์—ฌ ์˜์กด์„ฑ ๋ณ€ํ™”์˜ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์•„ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค.

  2. ํ…Œ์ŠคํŠธ ์ผ๊ด€์„ฑ ์œ ์ง€ - ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค๋Š” ๋ณ€ํ™”ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ชจํ‚น์„ ํ†ตํ•ด ํ…Œ์ŠคํŠธ์˜ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

  3. ํ…Œ์ŠคํŠธ ์†๋„ ํ–ฅ์ƒ - API ํ˜ธ์ถœ, DB ์ƒํ˜ธ์ž‘์šฉ์œผ๋กœ ์ธํ•œ ์‹œ๊ฐ„์„ ์†Œ๋ชจํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ๋น ๋ฅธ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

jest.mock()

jest.mock() ํ•จ์ˆ˜๋Š” ๋ชจ๋“ˆ์„ ๋ชจํ‚นํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์ด ์˜์กดํ•˜๋Š” ๋ชจ๋“ˆ์„ Mockingํ•˜์—ฌ ์›ํ•˜๋Š” ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๊ฑฐ๋‚˜ ๊ฐ€์งœ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

// math.js
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

// ์ฒซ๋ฒˆ์งธ ์ธ์ž๋Š” ๋ชจํ‚นํ•  ๋ชจ๋“ˆ, ๋‘๋ฒˆ์งธ ์ธ์ž๋Š” ๋ชจ๋“ˆ์˜ ํ•จ์ˆ˜๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋ชจํ‚นํ•˜๊ณ  ๋™์ž‘์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Œ.
jest.mock('./math', () => ({
  ...math,
  subtract: jest.fn(),
}));

jest.fn()

jest.fn() ํ•จ์ˆ˜๋Š” ๊ฐ€์งœ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ํ•จ์ˆ˜ ํ˜ธ์ถœ ์—ฌ๋ถ€, ๋ฐ˜ํ™˜ ๊ฐ’, ํ˜ธ์ถœ ํšŸ์ˆ˜๋“ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

// ๋นˆ ๊ฐ€์งœ ํ•จ์ˆ˜
const mockFn = jest.fn();
mockFn(); // ํ•จ์ˆ˜ ํ˜ธ์ถœ์‹œ undefined๋ฅผ ๋ฐ˜ํ™˜

// ์ธ์ž๋กœ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด, ํŠน์ • ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฐ€์งœ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
const mockFn = jest.fn((a, b) => a + b);

4. Test fixture

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ดˆ๊ธฐ๊ฐ’, ์ƒํƒœ, ๊ฐ์ฒด๋“ฑ์„ ์ œ๊ณตํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ๋ฐ˜๋ณต์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ

fixture

// fixture/products.ts
const products = [
  {
    category: 'Fruits', price: '$1', stocked: true, name: 'Apple',
  },
];

export default products;

// fixture/index.ts
import products from "./products";

export default {
  products,
};

app.test.tsx

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

import App from './App';

import fixtures from '../fixture';

jest.mock('./hooks/useFetchProducts', () => () => fixtures.products);

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

  screen.getByText('Apple');
});

__mocks__

Mockingํ•œ custom hooks๋ฅผ ๋ชจ์•„๋†“์€ ํด๋”

// __mocks__/useFetchProducts.ts
const useFetchProducts = jest.fn(); // Mocking custom hooks 

export default useFetchProducts;

Last updated