๐Ÿ“Œ React Testing Library

๋ฆฌ์•กํŠธ์—์„œ TDD ๋ฐฉ์‹์˜ ๊ฐœ๋ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ…Œ์ŠคํŒ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž.

๐ŸŽฏ ๋ชฉ์ 

1. ๋ฒ„๊ทธ ์บ์น˜

ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฒ„๊ทธ๋“ค์„ ์‚ฌ์ „์— ํ™•์ธํ•˜๊ธฐ ์šฉ์ดํ•˜๋‹ค.

2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹ ๋ขฐ๋„ ํ–ฅ์ƒ

์–ด๋– ํ•œ ๊ทผ๊ฑฐ๋กœ ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ๊ทผ๊ฑฐ๋ฅผ ๋’ท๋ฐ›์นจํ•˜๊ณ  ์ด ๊ทผ๊ฑฐ์— ๋Œ€ํ•œ ์‹ ๋ขฐ๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

3. ์งˆ๋ฌธ ๋ฐ ๋‹ต๋ณ€ ์‹œ๊ฐ„ ์ถ•์†Œ

๋ˆ„๊ตฐ๊ฐ€ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์งˆ๋ฌธํ–ˆ์„ ๋•Œ ๊ทธ์—๋Œ€ํ•œ ๋‹ต๋ณ€์œผ๋กœ ์ด ํ…Œ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด๋œ๋‹ค. ๊ทธ๋Ÿผ์œผ๋กœ ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

4. ๋ฌธ์„œ ์—ญํ• 

ํ…Œ์ŠคํŠธ๋ผ๋Š” ๋ฌธ์„œ๋ฅผ ์ œ๊ณตํ•จ์œผ๋กœ์„œ 2,3๋ฒˆ์˜ ์žฅ์ ์„ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•œ๋‹ค.

๐Ÿ’ผ ์‚ฌ์šฉ๋ฐฉ๋ฒ•

์šฐ์„  ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•ด์ค€๋‹ค.

1
2
3
4
5
npm install --save-dev @testing-library/react

npm install --save-dev @testing-library/dom

npm install --save-dev @testing-library/user-event

๋ฆฌ์•กํŠธ ํ…Œ์ŠคํŒ… ๋ผ์ด๋ธŒ๋ฆฌ๋Ÿฌ์™€ DOM ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ์‚ฌ์šฉ์ž ํ–‰๋™ ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๊ฐœ๋ฐœ์„ ํ•ด๋ณด์ž.

๐Ÿฆ– Component ํ…Œ์ŠคํŠธ

โœ๏ธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•

  1. ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง

  2. ์ปดํฌ๋„ŒํŠธ์˜ ์š”์†Œ ํƒ์ƒ‰

  3. ์š”์†Œ์™€์˜ ์ƒํ˜ธ์ž‘์šฉ

  4. ์–ด์„ค์…˜ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ์™€ ๊ธฐ๋Œ€ ๊ฐ’์ด ์ผ์น˜ํ•˜๋Š” ์ง€ ํ™•์ธ

์šฐ์„  ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์ž.

๐Ÿ“Œ Tip ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ

์ปดํฌ๋„ŒํŠธ๋ฅผ ์‰ฝ๊ฒŒ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด yamoo9๋‹˜์ด ์ œ๊ณตํ•ด์ค€ Tool์„ ์‚ฌ์šฉํ•ด๋ณด๋„๋กํ•˜์ž.

1
npx degit yamoo9/create-react-component create-react-component
1
2
3
4
5
6
7
8
// package.json
{
...
"scripts" :
...,
"rc": "node create-react-component create",
"rd": "node create-react-component delete"
}
1
2
npm run rc -- ์ปดํฌ๋„ŒํŠธ_์ด๋ฆ„ // ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ
npm run rd -- ์ปดํฌ๋„ŒํŠธ_์ด๋ฆ„ // ์ปดํฌ๋„ŒํŠธ ์ œ๊ฑฐ

ESLint ์—์„œ ํ…Œ์ŠคํŒ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ๋˜๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋„์›Œ์ฃผ๋Š”๋ฐ ์ด์— ๋Œ€ํ•œ Lint ๊ฒฝ๊ณ ๋ฅผ ๊บผ๋‘์ž.

1
2
3
4
5
// package.json
{
"eslintConfig":{...},
"testing-library/no-debugging-utils": "off"
}

์˜ˆ์ œ

1. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ํ™•์ธ

1
2
3
4
// ToggleButton.jsx
export function ToggleButton({ onText, offText, on }) {
return <div>{on ? onText : offText}</div>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ToggleButton.test.jsx
import { render, screen } from "@testing-library/react";
import { ToggleButton } from "./ToggleButton";

describe.only("ToggleButton Test Start!", () => {
test("์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ Œ๋”๋ง ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", () => {
render(<ToggleButton onText="1" offText="0" />);

const offTextElement = screen.getByText("0");
const onTextElement = screen.queryByText("1");

expect(offTextElement).toBeInTheDocument();
expect(onTextElement).not.toBeInTheDocument();
});
});
  • getByText() : ๊ฐ€์ƒ์œผ๋กœ ๊ทธ๋ ค์ง„ ๋ฌธ์„œ์— ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์•ฝ ์กด์žฌ ํ•˜์ง€ ์•Š๋Š” ๋‹ค๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
  • queryByText() : ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ณ  null ๊ฐ’์œผ๋กœ ๊ฐ€์ ธ์˜จ๋‹ค.

2. ํ™œ์„ฑํ™” ์ƒํƒœ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ํ…์ŠคํŠธ ํ‘œ์‹œ

1
2
3
4
// ToggleButton.jsx
export function ToggleButton({ onText, offText, on }) {
return <button type="button">{on ? onText : offText}</button>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ToggleButton.test.jsx
describe("ToggleButton ์ปดํฌ๋„ŒํŠธ", () => {
test("ํ™œ์„ฑ ์ƒํƒœ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ํ™œ์„ฑ(ON)/๋น„ํ™œ์„ฑ(OFF) ํ…์ŠคํŠธ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", () => {
let onText = "ON";
let offText = "OFF";

render(<ToggleButton onText={onText} offText={offText} />);

let elements = screen.queryAllByRole("button");
let firstElement = elements[0];
expect(firstElement).toHaveTextContent(offText);

screen.debug();

cleanup();

render(<ToggleButton onText={onText} offText={offText} on />);

elements = screen.queryAllByRole("button");
firstElement = elements[0];

screen.debug();

expect(firstElement).toHaveTextContent(onText);
});
});
  • ๋งŒ์•ฝ on ์ผ๋•Œ์˜ text์™€ off์ผ ๋•Œ์˜ text๋ฅผ ์ฐพ๊ณ  ์‹ถ๋‹ค๋ฉด queryAllText() ๋ฅผ ์‚ฌ์šฉํ•ด์ค€๋‹ค,
  • cleanup()์„ ํ•ด์ค˜์•ผ์ง€๋งŒ firstElement๋ฅผ ํ™•์ธํ•  ๋•Œ, ์•ž์— ๊ทธ๋ ค์ง„ ๊ฒƒ์„ ์ง€์šฐ๊ณ  ์ƒˆ๋กœ ๊ทธ๋ ค์ง„ ๊ฒƒ์„ ๋น„๊ตํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

3. onToggle ์†์„ฑ(prop)์— ์—ฐ๊ฒฐ๋œ ํ•จ์ˆ˜ ์‹คํ–‰ ํ™•์ธ & ํ™œ์„ฑ ์ƒํƒœ ์ปดํฌ๋„ŒํŠธ๋Š” ToggleButton--on ํด๋ž˜์Šค ์ด๋ฆ„ ํฌํ•จ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
// ToggleButton.js
export function ToggleButton({ onText, offText, on, onToggle }) {
return (
<button
type="button"
className={`ToggleButton ${on ? "ToggleButton--on" : ""}`.trim()}
onClick={onToggle}
>
{on ? onText : offText}
</button>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ToggleButton.test.js
describe("ToggleButton ์ปดํฌ๋„ŒํŠธ", () => {
test("`onToggle` ์†์„ฑ(prop)์— ์—ฐ๊ฒฐ๋œ ํ•จ์ˆ˜๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.", () => {
let expected = "triggering toggle event";
let received = "";

render(
<ToggleButton
onToggle={() => {
received = expected;
}}
/>
);

expect(received).not.toBe(expected);

const element = screen.queryByRole("button");

fireEvent.click(element); // click button element

expect(received).toBe(expected);
});

test(`ํ™œ์„ฑ ์ƒํƒœ์˜ ์ปดํฌ๋„ŒํŠธ๋Š” 'ToggleButton--on' ํด๋ž˜์Šค ์ด๋ฆ„์„ ํฌํ•จํ•œ๋‹ค.`, () => {
let expected = "ToggleButton--on";
render(<ToggleButton on />);

const element = screen.getByRole("button");
expect(element).toHaveClass(expected);
});
});
  • fireEvent() : ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์ฃผ๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.

  • queryByRole() : ํ•ด๋‹น ์š”์†Œ์˜ ์—ญํ• ์„ ํ™•์ธํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

    ex) button ํƒœ๊ทธ์— type์„ โ€œbuttonโ€์œผ๋กœ ๋ช…์‹œ์ ์œผ๋กœ ์ž‘์„ฑํ•˜์˜€๋Š”์ง€โ€ฆ

๐Ÿ“ ์†Œ๊ฐ

์˜ค๋Š˜ ์ˆ˜์—…์—์„œ ์ƒํƒœ๋ฅผ ๊ฐ€์ง€์ง€ ์•Š๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘ํ•œ ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์‹ค์Šต์„ ์ง„ํ–‰ํ•˜์˜€๋‹ค. ๋ฆฌ์•กํŠธ๋ฅผ TDD ๋ฐฉ์‹์œผ๋กœ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ์•ž์„œ ๋งํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‹ ๋ขฐ๋„ ํ–ฅ์ƒํ•  ์ˆ˜ ์žˆ๊ณ  ํ…Œ์ŠคํŠธ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

์•„์ง jest์— ์ต์ˆ™ํ•˜์ง€ ์•Š์•„ ๋‚ฏ์„ค๊ณ  ์–ด๋ ต์ง€๋งŒ, ์šฐํ…Œ์ฝ”์—์„œ๋„ jest๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ  ์•ž์œผ๋กœ ์ž์ฃผ ์‚ฌ์šฉํ•ด๋ณด๋ฉด์„œ ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ์— ๋Œ€ํ•ด ๋ชธ์„ ์ตํžˆ๋„๋ก ํ•ด์•ผ๊ฒ ๋‹ค.