[React] Jest์ React testing library๋ฅผ ์ฌ์ฉํด์ ๊ฐ๋จํ React app ํ ์คํธ ํด๋ณด๊ธฐ (feat. TDD)
๐ ๋ค์ด๊ฐ๋ฉฐ
์ต๊ทผ 'ํด๋ฆฐ ์ฝ๋'์ฑ ๊ณผ ์ฌ๋ฌ ๊ฐ๋ฐ ๊ด๋ จ ์์์ด๋ ๋ฌธ์๋ฅผ ๋ณด๊ณ ํ ์คํธ ์ฝ๋์ ์ค์์ฑ์ ๊นจ๋ซ๊ฒ ๋์๋ค.
์์ผ๋ก ๊ฐ๋ฐ์ ํ๋ฉด์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ํด์ ํ ์คํธ ์ฝ๋๋ฅผ ๋จผ์ ์์ฑํ๊ณ ํ ์คํธ ์ฝ๋๋ฅผ ํต๊ณผํ๊ธฐ ์ํ ์ฝ๋๋ฅผ ์์ฑํ๋ TDD ๊ฐ๋ฐ ๋ฐฉ์์ผ๋ก React์์ Jest์ React testing library๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฒ์ ๊ณต๋ถํ๊ฒ ๋์๋ค. ๊ณต๋ถํ ๋ด์ฉ์ ๋ณต์ตํ๊ณ ์ ๋ฆฌํ๊ธฐ ์ํด ๊ธ์ ์์ฑํด ๋ณธ๋ค.
ํด๋น ๊ธ์์๋ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์ซ์๊ฐ ์ฆ๊ฐ๋๋ ๊ฐ๋จํ ์ฑ์ TDD ๊ฐ๋ฐ ๋ฐฉ์์ผ๋ก ํ ์คํธํ๊ณ ๋ง๋ค์ด ๋ณผ ๊ฒ์ด๋ค.
Jest์ React testing library
Jest ?
Jest๋ ๊ฐ๋ฐ์๊ฐ ์๋ฐ์คํฌ๋ฆฝํธ์ ํ์ ์คํฌ๋ฆฝํธ ์ฝ๋์ ๋ํ ํ ์คํธ๋ฅผ ์คํํ ์ ์๋๋ก ํ๋ ์๋ฐ์คํฌ๋ฆฝํธ ํ ์คํธ ํ๋ ์์ํฌ๋ค.
๋จ์ํจ์ ๊ฐ์ ์ผ๋ก ์ค๊ณ๋ ํ๋ ์์ํฌ๋ก, ๊ฐ๊ฐ์ ํ ์คํธ, ์ค๋ ์ท ๋น๊ต, mocking, coverage ๋ฑ์ ๊ตฌ์ถํ ์ ์๋ ๊ฐ๋ ฅํ API๋ฅผ ์ ๊ณตํ๋ค.
React testing library ?
React testing library๋ React Component๋ฅผ ํ ์คํธํ๊ธฐ ์ํด ํน๋ณํ ์ ์๋ ์๋ฐ์คํฌ๋ฆฝํธ ํ ์คํธ ์ ํธ๋ฆฌํฐ์ด๋ค. ๊ฐ๊ฐ์ ๊ตฌ์ฑ์์์ ๋ํ ์ฌ์ฉ์ ์ํธ์์ฉ์ ํ ์คํธํ๊ณ , UI๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ๋์ง ํ์ธํ ์ ์๋ค.
๊ทธ๋ผ ๋ ์ค์ ๋ญ ์ฐ๋ผ๊ณ ?๐ค
๋ต์ ๋ ๋ค ํ์ํ๋ค.
Jest๋ ํ ์คํธ๋ฅผ ์ฐพ์์ ์คํํ๊ณ , ํ ์คํธ๊ฐ ํต๊ณผํ๋์ง ๊ฒ์ฌํ๋ค. ๊ทธ๋ฆฌ๊ณ ํ ์คํธ suites, ํ ์คํธ ์ผ์ด์ค ๋ฑ์ ์์ฑํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
React testing library๋ ์๋ฅผ ๋ค์ด, ์ ์ ๊ฐ ๋ฒํผ์ ํด๋ฆญํ๋ฉด div๊ฐ ์กด์ฌํ๋์ง ๋ฑ React ์ฑ์ ํ ์คํธํ๊ธฐ ์ํ ๊ฐ์ DOM์ ์ ๊ณตํด์ค๋ค.
์ ํ๋ก์ ํธ ๋ง๋ค๊ธฐ
CRA๋ก ์งํํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ CRA๋ฅผ ์ค์นํด ์ค๋ค.
npx create-react-app ํด๋์ด๋ฆ
์ค์น๊ฐ ์๋ฃ๋์ผ๋ฉด ํฐ๋ฏธ๋ ์ฐฝ์ ์ด๊ณ npm test ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํด ์คํํ๋ค.
npm test
์คํํ๋ฉด ์ด๊ธฐ์ ์์ฑ๋์ด์๋ APP component๋ฅผ ํ ์คํธํ๋ ํ์ผ์์ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค.
CRA๋ก ํ๋ก์ ํธ๋ฅผ ๊ตฌ์ฑํ๋ฉด Jest์ React testing library๋ ๋ด์ฅ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์ค์นํ ํ์๊ฐ ์๋ค.
์๊ตฌ ์ฌํญ
๋ฒํผ์ ๋๋ฅด๋ฉด ์ซ์๊ฐ ์ฆ๊ฐํ๊ณ ๊ฐ์ํ๋ Counter App์ ๋ง๋ค๊ธฐ ์ํด ํ์ํ ์ปดํฌ๋ํธ๋ฅผ ์๊ฐํด๋ณด๋ฉด
- CountView ์ปดํฌ๋ํธ: ํ์ฌ ์ซ์(์นด์ดํธ ์ํ)๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ปดํฌ๋ํธ
- CountButtons ์ปดํฌ๋ํธ: '+' ๋ฒํผ๊ณผ '-'๋ฒํผ์ด ์๊ณ ๊ฐ๊ฐ์ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์ซ์๊ฐ 1์ฉ ์ฆ๊ฐํ๊ณ ๊ฐ์ํ๋ ํจ์๋ฅผ ์คํ์ํค๋ ์ปดํฌ๋ํธ
- APP ์ปดํฌ๋ํธ: ์ ์ฒด์ ์ธ ๋ก์ง์ ๋ด๋น (์นด์ดํธ ์ํ, ์นด์ดํธ์ ๊ดํ ํจ์)
์ด๋ ๊ฒ ํ์ํ๊ฒ ๋๋๋ฐ, ๋งค์ฐ ๊ฐ๋จํ ์ฑ์ด๋ค.
CountView ์ปดํฌ๋ํธ
ํ์ฌ ์นด์ดํธ ์ํ๋ฅผ ๋ณด์ฌ์ฃผ๋ CountView ์ปดํฌ๋ํธ๋ถํฐ ์์ฑํด๋ณด๊ฒ ๋ค.
๋จผ์ srcํด๋์ components ํด๋๋ฅผ ์์ฑํ๊ณ CountView ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด์ค๋ค.
src/components/CountView.js
function CountView() {
return <div>CountView</div>;
}
export default CountView;
props๋ก ๋ฐ์ ์ซ์ ๊ด๋ฆฌ
ํด๋น ์ปดํฌ๋ํธ์๋ props๋ก ๋ฐ์ ํ์ฌ count๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ญํ ์ ํ ๊ฒ์ด๋ค. ์ด์ ๋ง์ถฐ์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
src/components/CountView.test.js
import { render, screen } from '@testing-library/react';
import CountView from './CountView';
describe('<CountView />', () => {
it('shows the current count state.', () => {
let sampleCount = 0;
render(<CountView count={sampleCount} />);
const initialState = screen.getByText('ํ์ฌ ์ซ์: 0');
expect(initialState).toBeInTheDocument();
sampleCount = 13;
render(<CountView count={sampleCount} />);
const countState = screen.getByText('ํ์ฌ ์ซ์: 13');
expect(countState).toBeInTheDocument();
});
});
props๋ก count๋ฅผ ๋ฐ์์ค๋ ์ํฉ์ ๋ง๋ค๊ณ CountView ์ปดํฌ๋ํธ์ props๋ก sampleCount๋ฅผ ๋๊ฒจ์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ getByText๋ก ๋ณด์ฌ์ค ์์๋ฅผ ์ ํํ๋๋ก ํ๊ณ ํ๋ฉด์ ํด๋น ์์๊ฐ ์๋์ง toBeInTheDocument()๋ฅผ ํตํด์ ํ์ธํ๋ค.
์ด๋ ๊ฒ ์์ฑํ๊ณ npm test ๋ช ๋ น์ด๋ฅผ ์คํํด๋ณด๋ฉด ๋น์ฐํ ํ ์คํธ ์ผ์ด์ค๋ ์คํจํ๊ฒ ๋๋ค.
์ด์ ์ด ํ ์คํธ ์ผ์ด์ค๊ฐ ํต๊ณผ๋ ์ ์๋๋ก CountView ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด๋ณด์.
scr/components/CountView.js
function CountView({ count }) {
return <h1>ํ์ฌ ์ซ์: {count}</h1>;
}
export default CountView;
์ด๋ ๊ฒ ์์ฑํ๊ณ ๋ค์ npm test ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด ํ ์คํธ๊ฐ ํต๊ณผ๋๋ ๊ฒฐ๊ณผ๊ฐ ๋์ฌ ๊ฒ์ด๋ค.
CountButtons ์ปดํฌ๋ํธ
์ด๋ฒ์๋ '+' ๋ฒํผ๊ณผ '-'๋ฒํผ์ ๊ฐ์ง๊ณ ์๋ CountButtons ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด๋ณด์.
๋จผ์ ๋น ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด์ค๋ค.
src/components/CountButtons.js
function CountButtons() {
return (
<div>
CountButtons
</div>
);
}
export default CountButtons;
UI ๊ตฌ์ฑํ๊ธฐ
๊ฐ์ฅ ๋จผ์ ์์ฑํ ํ ์คํธ ์ฝ๋๋ UI๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒ์ด๋ค. ํด๋น ์ปดํฌ๋ํธ๊ฐ '+'์ ๋ฒํผ๊ณผ '-'์ ๋ฒํผ ์ด ๋ ๊ฐ์ง ๋ฒํผ์ ๊ฐ์ง๊ณ ์๋์ง ํ์ธํ๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ณด์.
import { render, screen } from '@testing-library/react';
import CountButtons from './CountButtons';
describe('<CountButtons />', () => {
it('has an increment button and a decrement button', () => {
render(<CountButtons />);
const incrementBtn = screen.getByTestId('incrementBtn');
const decrementBtn = screen.getByTestId('decrementBtn');
expect(incrementBtn).toBeInTheDocument();
expect(decrementBtn).toBeInTheDocument();
});
์ด๋ฒ์๋ getByTestId๋ฅผ ํตํด์ '+' ๋ฒํผ๊ณผ '-'๋ฒํผ์ ์ก์ ์ ์๋๋ก ์์ฑํ๋ค.
์ด์ ์ด ํ ์คํธ ์ผ์ด์ค๊ฐ ํต๊ณผํ ์ ์๋๋ก ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด๋ณด์.
src/components/CountButtons.js
function CountButtons() {
return (
<div>
<button data-testid="incrementBtn">
+
</button>
<button data-testid="decrementBtn">
-
</button>
</div>
);
}
export default CountButtons;
์ด๋ ๊ฒ data-testid๋ฅผ ๋ถ์ฌํ๊ฒ ๋๋ฉด ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ๋ getByTestId๋ฅผ ํตํด์ ํด๋น ์์๋ฅผ ์ฐพ์๋ผ ์ ์๋ค.
Click ์ด๋ฒคํธ ๊ด๋ฆฌ
์ด๋ฒ์๋ '+' ๋ฒํผ๊ณผ '-'๋ฒํผ์ ํด๋ฆญํ์ ๋ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ฅผ ๊ด๋ฆฌํด๋ณด์.
CountButtons ์ปดํฌ๋ํธ ์์๋ ๋ฒํผ์ ํด๋ฆญํ์ ๋ ์ซ์๊ฐ ์ฆ๊ฐํ๋ ํจ์์ ๊ฐ์ํ๋ ํจ์, ์ด ๋ ๊ฐ์ง ํจ์๋ฅผ props๋ก ๋ฐ์์์ ํธ์ถํ๋๋ก ํ ๊ฒ์ด๋ค.
src/components/CountButtons.test.js
import { fireEvent, render, screen } from '@testing-library/react';
import CountButtons from './CountButtons';
describe('<CountButtons />', () => {
it('has an increment button and a decrement button', () => {
render(<CountButtons />);
const incrementBtn = screen.getByTestId('incrementBtn');
const decrementBtn = screen.getByTestId('decrementBtn');
expect(incrementBtn).toBeInTheDocument();
expect(decrementBtn).toBeInTheDocument();
});
it('calls incrementFn and decrementFn', () => {
const incrementFn = jest.fn();
const decrementFn = jest.fn();
render(
<CountButtons incrementFn={incrementFn} decrementFn={decrementFn} />
);
const incrementBtn = screen.getByTestId('incrementBtn');
const decrementBtn = screen.getByTestId('decrementBtn');
fireEvent.click(incrementBtn);
fireEvent.click(decrementBtn);
expect(incrementFn).toBeCalled();
expect(decrementFn).toBeCalled();
});
});
incrementFn๊ณผ decrementFn ๋ผ๋ ํจ์๋ฅผ mocking ํด์ ํจ์๋ค์ด ๋ฒํผ์ด ํด๋ฆญ๋์ ๋ ํธ์ถ์ด ๋์๋์ง ๊ฒ์ฌ๋ฅผ ํ๋๋ก ํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ด ํ ์คํธ๋ค์ด ํต๊ณผ๋ ์ ์๋๋ก ํด๋น ์ปดํฌ๋ํธ ์์ฑ์ ๋ง๋ฌด๋ฆฌํด๋ณด์.
src/components/CountButtons.js
function CountButtons({ incrementFn, decrementFn }) {
return (
<div>
<button onClick={incrementFn} data-testid="incrementBtn">
+
</button>
<button onClick={decrementFn} data-testid="decrementBtn">
-
</button>
</div>
);
}
export default CountButtons;
App ์ปดํฌ๋ํธ
App ์ปดํฌ๋ํธ์์๋ counter์ ๊ดํ ๋ชจ๋ ์ํ๊ฐ ๊ด๋ฆฌ๋๋ค.
์ด ์ปดํฌ๋ํธ์์๋ ๊ธฐ์กด์ unit ํ ์คํธ๊ฐ ์ด๋ฃจ์ด์ง ์ปดํฌ๋ํธ๋ค์ ์ฌ์ฉํ์ฌ ๊ตฌํ๋๋ฏ๋ก ์ด๋ฒ์ ์์ฑํ๊ฒ ๋๋ ํ ์คํธ ์ฝ๋๋ ํตํฉ ํ ์คํธ์ด๋ค.
๋จผ์ ๊ธฐ์กด์ ์๋ App.js ํ์ผ์ ๋น ์ํ๋ก ๋ง๋ค์ด์ค๋ค.
src/App.js
function App() {
return (
<div>App</div>
);
}
export default App;
CountView ์ปดํฌ๋ํธ์ CountButtons ์ปดํฌ๋ํธ ๋ ๋๋ง ํ์ธ
ํด๋น App ์ปดํฌ๋ํธ์์ ์ฒซ ๋ฒ์งธ๋ก ์์ฑํ ํ ์คํธ ์ผ์ด์ค๋ CountView์ CountButtons๊ฐ ๋ ๋๋ง์ด ๋์๋์ง ํ์ธํ๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
๊ธฐ์กด์ App.test.js ํ์ผ์ ์๋ ๋ด์ฉ์ ์ง์ฐ๊ณ ์๋กญ๊ฒ ์์ฑํ๋ค.
src/App.test.js
import { render, screen } from '@testing-library/react';
import App from './App';
describe('<App />', () => {
it('renders ConterView and CountButtons', () => {
render(<App />);
const view = screen.getByText('ํ์ฌ ์ซ์: 0');
const buttons = screen.getAllByRole('button');
expect(view).toBeInTheDocument();
expect(buttons.length).toBe(2);
});
});
getAllByRole์ ํตํด์ ํ๋ฉด์ ๋ชจ๋ ๋ฒํผ์ ๋ด์ ์ ์๋๋ก ํ๋ค. ์ฐ๋ฆฌ๋ CountButtons ์ปดํฌ๋ํธ์์ ๋ ๊ฐ์ ๋ฒํผ์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ฐฐ์ด์ ๊ธธ์ด๊ฐ 2๊ฐ ๋ ๊ฒ์ด๋ค. (getAllByRole๋ก ์์๋ฅผ ๋ด๊ฒ ๋๋ฉด ๋ฐฐ์ด๋ก ๋ฐ๋๋ค.)
์ด๋ ๊ฒ ์์ฑํ ํ ์คํธ ์ผ์ด์ค๊ฐ ํต๊ณผ๋ ์ ์๋๋ก App ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด๋ณด์.
src/App.js
import { useState } from 'react';
import CountButtons from './components/CountButtons';
import CountView from './components/CountView';
function App() {
const [count, setCount] = useState(0);
return (
<>
<CountView count={count} />
<CountButtons />
</>
);
}
export default App;
์ฆ๊ฐํ๋ ๊ธฐ๋ฅ ๊ตฌํ
์ด์ '+'๋ฒํผ์ ํด๋ฆญํ๋ฉด 1์ฉ ์ฆ๊ฐ, '-'๋ฒํผ์ ํด๋ฆญํ๋ฉด 1์ฉ ๊ฐ์ํ๋ ๊ธฐ๋ฅ์ ์ํ ํ ์คํธ ์ผ์ด์ค๋ฅผ ๊ฐ๊ฐ ๋ง๋ค์ด ๋ณด์.
src/App.test.js
import { fireEvent, render, screen } from '@testing-library/react';
import App from './App';
describe('<App />', () => {
it('renders ConterView and CountButtons', () => {
render(<App />);
const view = screen.getByText('ํ์ฌ ์ซ์: 0');
const buttons = screen.getAllByRole('button');
expect(view).toBeInTheDocument();
expect(buttons.length).toBe(2);
});
it('increments by 1 each time incrementBtn is clicked', () => {
render(<App />);
const initialScreen = screen.getByText('ํ์ฌ ์ซ์: 0');
expect(initialScreen).toBeInTheDocument();
const incrementBtn = screen.getByTestId('incrementBtn');
fireEvent.click(incrementBtn);
fireEvent.click(incrementBtn);
fireEvent.click(incrementBtn);
const changedScreen = screen.getByText('ํ์ฌ ์ซ์: 3');
expect(changedScreen).toBeInTheDocument();
});
it('decrements by 1 each time decrementBtn is clicked', () => {
render(<App />);
const initialScreen = screen.getByText('ํ์ฌ ์ซ์: 0');
expect(initialScreen).toBeInTheDocument();
const decrementBtn = screen.getByTestId('decrementBtn');
fireEvent.click(decrementBtn);
fireEvent.click(decrementBtn);
const changedScreen = screen.getByText('ํ์ฌ ์ซ์: -2');
expect(changedScreen).toBeInTheDocument();
});
});
์ฆ๊ฐํ๋ ๊ธฐ๋ฅ์ ์ํ ํ ์คํธ ์ผ์ด์ค์์๋ fireEvent์ click ์ด๋ฒคํธ๋ฅผ ์ธ ๋ฒ ๋ฐ์์ํค๋ ์ํฉ์ ๋ง๋ค์ด์ ์ซ์์ ์ด๊ธฐ๊ฐ 0์์ + 3์ด ๋์ด์ ํ๋ฉด์ 3์ด ํ์๋์ด์ผ ํ๋ค.
๊ฐ์ํ๋ ๋ถ๋ถ์์๋ ๋ง์ฐฌ๊ฐ์ง๋ค. ๋ ๋ฒ ๋ฐ์์์ผ์ -2๊ฐ ํ์๋์ด์ผ ํ๋ค.
์ด์ ์ด ํ ์คํธ ์ผ์ด์ค๋ค์ ํต๊ณผ์์ผ๋ณด์.
src/App.js
import { useState } from 'react';
import CountButtons from './components/CountButtons';
import CountView from './components/CountView';
function App() {
const [count, setCount] = useState(0);
const incrementHandler = () => {
setCount((count) => count + 1);
}
const decrementtHandler = () => {
setCount((count) => count - 1);
}
return (
<>
<CountView count={count} />
<CountButtons
incrementFn={incrementHandler}
decrementFn={decrementtHandler}
/>
</>
);
}
export default App;
์ด์ npm test ๋ช ๋ น์ด๋ฅผ ์คํํด๋ณด๋ฉด ๋ชจ๋ ํ ์คํธ ์ผ์ด์ค๊ฐ ํต๊ณผ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
๊ทธ๋ผ ๋ง์ง๋ง์ผ๋ก npm start๋ฅผ ํตํด์ ์ง๊ธ๊น์ง ์์ฑํ Counter App์ ์คํํด์ ํ์ธํด ๋ณด์.
TDD ๊ฐ๋ฐ ํ๋ฆ์ผ๋ก ๊ฐ๋ฐํ๊ธฐ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ๋ฅผ ๋งค๋ฒ ํ์ธํ์ง ์์๋ ์๊ตฌ์ฌํญ์ ๋ง๊ฒ ๊ฐ๋ฐ์ ํ ์ ์์๋ค.
๋ง์ง๋ง์ผ๋ก Counter App์ ๋ํ ํ ์คํธ ์ฝ๋ coverage๋ฅผ ํ์ธํด๋ณด์.
ํฐ๋ฏธ๋์ ํด๋น ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ๊ณ ์คํํ๋ค.
npm test -- --coverage .
์ง๊ธ๊น์ง ์์ฑํ App.js, CountButtons.js, CountView.js ํ์ผ๋ค์ ๋ํ ํ ์คํธ coverage๊ฐ 100%์์ ํ์ธํ ์ ์๋ค.
์ฝ๊ฐ์ ๋ฆฌํฉํ ๋ง
๋ฒํผ์ ๋๋ฅผ ๋๋ง๋ค CountButtons ์ปดํฌ๋ํธ์ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๋ฌด๋ถ๋ณํ ๋ฆฌ๋ ๋๋ง์ ๋ง๊ธฐ ์ํด์๋ React.memo๋ก ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ์ฃผ์ด์ผ ํ๋ค.
src/components/CountButtons.js
import { memo } from 'react';
function CountButtons({ incrementFn, decrementFn }) {
return (
<div>
<button onClick={incrementFn} data-testid="incrementBtn">
+
</button>
<button onClick={decrementFn} data-testid="decrementBtn">
-
</button>
</div>
);
}
export default memo(CountButtons);
ํ์ง๋ง App ์ปดํฌ๋ํธ์์ count state๊ฐ ๋ฐ๋ ๋๋ง๋ค incrementHandler ํจ์์ decrementHandler ํจ์๊ฐ ์ฌ์ ์ธ ๋๋ฉด์ ๋ฒํผ์ ๋ํ๋ด๋ CountButtons ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋๊ณ ์๋ค.
๊ทธ๋ฌ๋ฏ๋ก App ์ปดํฌ๋ํธ์์ ํด๋น ํจ์๋ค์ useCallback์ผ๋ก ๊ฐ์ธ์ฃผ๋๋ก ํ์.
useCallback์์ ๋ ๋ฒ์งธ ์ธ์๋ก ๋น ๋ฐฐ์ด์ ๋ฃ์ด์ฃผ๋ฉด ์ฒซ ๋ ๋๋ง ์์๋ง ์ ์ธ๋๊ณ ๊ทธ ๋ค๋ก๋ ์ฒ์ ์ ์ธ๋ ํจ์๋ฅผ unmount ๋ ๋๊น์ง ๊ฐ์ ธ๊ฐ๋ค.
src/App.js
import { useCallback, useState } from 'react';
import CountButtons from './components/CountButtons';
import CountView from './components/CountView';
function App() {
const [count, setCount] = useState(0);
const incrementHandler = useCallback(() => {
setCount((count) => count + 1);
}, []);
const decrementtHandler = useCallback(() => {
setCount((count) => count - 1);
}, []);
return (
<>
<CountView count={count} />
<CountButtons
incrementFn={incrementHandler}
decrementFn={decrementtHandler}
/>
</>
);
}
export default App;
๐๋ง์น๋ฉฐ
์ด๋ ๊ฒ TDD ๊ฐ๋ฐ ํ๋ฆ์ผ๋ก Jest์ React testing library๋ฅผ ํ์ฉํด์ ๊ฐ๋จํ Counter ์ฑ์ ๋ง๋ค์ด๋ดค๋ค.
๊ณต๋ถํ๋ฉด์๋ ๋๋ ๊ฑฐ์ง๋ง ํ ์คํธ ์ฝ๋์ ์ค์์ฑ์ ์ ๋ง ํฌ๊ฒ ๋๊ปด์ง๋ค.
ํ ์คํธ ์ฝ๋๊ฐ ์์ผ๋ฉด ๋ฆฌํฉํ ๋ง์ ํ๊ณ ๋์ ํ๋ํ๋ ๋ธ๋ผ์ฐ์ ์์ ์ง์ ํ ์คํธํ์ง ์์๋ ๋๊ธฐ ๋๋ฌธ์ ์๊ฐ์ด ์ ์ฝ๋๋ค.
๊ฐ๋ฐํ ๋์๋ ํ ์คํธ ์ฝ๋ ์์ด ๊ฐ๋ฐํ๋ ํธ์ด ์๊ฐ์ด ๋จ์ถ๋์ง๋ง ๋์ค์ ์ฝ๋๊ฐ ๋ณต์กํด์ง๊ฑฐ๋ ๋ฆฌํฉํ ๋ง์ด ํ์ํ ๋๋ ํ ์คํธ ์ฝ๋๊ฐ ์๊ธฐ ๋๋ฌธ์ ์๊ฐ์ด ํจ์ฌ ์ ์ฝ๋๊ณ ๋ฆฌํฉํ ๋ง ํ๊ธฐ๋ ํธํด์ง๊ธฐ ๋๋ฌธ์,
์์ผ๋ก ๊ฐ๋ฐ์ ํ๋ฉด์ ํ ์คํธ ์ฝ๋๋ ๊ผญ ํ์ํ๋ค๊ณ ์๊ฐํ๋ค.