2. React Testing Library
Keyword
React Testing Library
given - when - then ํจํด
Mocking
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 ์ ๊ทผ)์ ๊ฐ์ง๋ก ๊ตฌํํ์ฌ ์ธ๋ถ ๋ฆฌ์์ค์ ์์กดํ์ง ์๊ณ ํ ์คํธ๋ฅผ ์ํํ ์ ์๋ค.
์ฌ์ฉํ๋ ์ด์ ?
์์กด์ฑ ๊ฒฉ๋ฆฌ - ๋คํธ์ํฌ ํต์ , DB ์ ๊ทผ์ผ๋ก ๋ถํฐ ๊ฒฉ๋ฆฌํ์ฌ ์์กด์ฑ ๋ณํ์ ์ํฅ์ ๋ฐ์ง ์์ ๋ ๋ฆฝ์ ์ผ๋ก ์คํ๋ ์ ์๋ค.
ํ ์คํธ ์ผ๊ด์ฑ ์ ์ง - ์ธ๋ถ ๋ฆฌ์์ค๋ ๋ณํํ ์ ์์ผ๋ฏ๋ก ๋ชจํน์ ํตํด ํ ์คํธ์ ์ผ๊ด์ฑ์ ๋ณด์ฅ๋ฐ์ ์ ์๋ค.
ํ ์คํธ ์๋ ํฅ์ - 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