ํ ์คํ ๋๊ตฌ
Importing
import ReactTestUtils from 'react-dom/test-utils'; // ES6
var ReactTestUtils = require('react-dom/test-utils'); // npm๊ณผ ES5
๊ฐ์
ReactTestUtils
๋ ์ฌ๋ฌ๋ถ์ด ์ ํํ ํ
์คํ
ํ๋ ์์ํฌ์์ ํ
์คํธ๋ฅผ ์ฝ๊ฒ ์งํํ ์ ์๋๋ก ํด ์ค๋๋ค. Facebook์์๋ Jest๋ฅผ ์ด์ฉํด ๋์ฑ ์ฝ๊ฒ JavaScript ํ
์คํธ๋ฅผ ํ๊ณ ์์ต๋๋ค. Jest ์น์ฌ์ดํธ์ React ์์ต์ ๋ฌธ์๋ฅผ ํตํด Jest๋ฅผ ์์ํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณด์ธ์.
์ฃผ์
Facebook์์๋ React Testing Library ์ฌ์ฉ์ ๊ถ์ฅํฉ๋๋ค. ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฌ์ฉ์๊ฐ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ฒ๋ผ ํ ์คํธ๋ฅผ ์์ฑํ ์ ์๋๋ก ์ค๊ณ๋์์ต๋๋ค.
React v16 ์ดํ์์๋ Enzyme์ ํตํด React ์ปดํฌ๋ํธ์ ์ถ๋ ฅ์ ์ฝ๊ฒ ๊ฒ์ฆํ๊ณ ์กฐ์ํ๊ณ ํ์ํ ์ ์์ต๋๋ค.
act()
mockComponent()
isElement()
isElementOfType()
isDOMComponent()
isCompositeComponent()
isCompositeComponentWithType()
findAllInRenderedTree()
scryRenderedDOMComponentsWithClass()
findRenderedDOMComponentWithClass()
scryRenderedDOMComponentsWithTag()
findRenderedDOMComponentWithTag()
scryRenderedComponentsWithType()
findRenderedComponentWithType()
renderIntoDocument()
Simulate
์ฐธ์กฐ์ฌํญ
act()
์ปดํฌ๋ํธ์ ์ง๋จ์ ์ค๋นํ๊ธฐ ์ํด์๋ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ณ ๊ฐฑ์ ํด์ฃผ๋ ์ฝ๋๋ฅผ act()
๋ฅผ ํธ์ถํ ๊ฒ์ ์์ ๋ฃ์ด์ค์ผ ํฉ๋๋ค. ์ด๋ฅผ ํตํด React๋ฅผ ๋ธ๋ผ์ฐ์ ๋ด์์ ๋์ํ๋ ๊ฒ๊ณผ ๋น์ทํ ํ๊ฒฝ์์ ํ
์คํธํ ์ ์์ต๋๋ค.
์ฃผ์
react-test-renderer
๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ๋๊ฐ์ด ์๋ํ๋act
export๊ฐ ์ ๊ณต๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ Counter
์ปดํฌ๋ํธ๊ฐ ์๋ค๊ณ ํด๋ด
์๋ค.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
handleClick() {
this.setState(state => ({
count: state.count + 1,
}));
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.handleClick}>
Click me
</button>
</div>
);
}
}
์ด๋ฐ ๋ฐฉ์์ผ๋ก ํ ์คํธ ํ ์ ์์ต๋๋ค.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';import Counter from './Counter';
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('can render and update a counter', () => {
// ์ฒซ render์ componentDidMount๋ฅผ ํ
์คํธ
act(() => { ReactDOM.createRoot(container).render(<Counter />); }); const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
// ๋ ๋ฒ์งธ render์ componentDidUpdate๋ฅผ ํ
์คํธ
act(() => { button.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(label.textContent).toBe('You clicked 1 times');
expect(document.title).toBe('You clicked 1 times');
});
- DOM ์ด๋ฒคํธ ๋ฐํ์ DOM ์ปจํ
์ด๋๊ฐ
document
๊ฐ์ฒด์ ์ถ๊ฐ๋์์ ๋๋ง ์๋ํ๋ค๋ ์ ์ ์์ง๋ง์ธ์. ๋ถํ์ํ๊ฒ ๋ฐ๋ณต ๋๋ ์ฝ๋๋ฅผ ์ค์ด๊ธฐ ์ํด์react-testing-library
์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. - ํ
์คํธ ๋ฐฉ๋ฒ ๋ฌธ์์
act()
์ ๋์ ๋ฐฉ์์ ๋ํ ์์ธํ ๋ด์ฉ์ด ์์์ ์ฌ์ฉ๋ฒ๊ณผ ํจ๊ป ํฌํจ๋์ด ์์ต๋๋ค.
mockComponent()
mockComponent(
componentClass,
[mockTagName]
)
๋ชจ์ ์ปดํฌ๋ํธ ๋ชจ๋์ ์ด ๋ฉ์๋์ ๋๊ฒจ ์ ์ฉํ ๋ฉ์๋๋ค์ ๋ถ์ฌ ์ฆ๊ฐํด ๋๋ฏธ React ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ณดํต์ ๊ฒฝ์ฐ์ฒ๋ผ ๋ ๋๋ง ํ์ง ์๊ณ ๊ทธ ๋์ ์ปดํฌ๋ํธ๋ ๊ฐ๋จํ๊ฒ <div>
ํ๊ทธ๊ฐ ๋ฉ๋๋ค. mockTagName
๊ฐ์ ๋๊ฒจ์ค๋ค๋ฉด <div>
๋์ ๋ค๋ฅธ ํ๊ทธ๋ก ๋ง๋ค์ด ์ค ์ ์์ต๋๋ค.
์ฃผ์
mockComponent()
๋ ๋ ์ด์ ์ฐ์ด์ง ์๋ API์ ๋๋ค.jest.mock()
์ฌ์ฉ์ ์ถ์ฒํฉ๋๋ค.
isElement()
isElement(element)
element
๊ฐ React์ element๋ผ๋ฉด true
๋ฅผ ๋ฐํํฉ๋๋ค.
isElementOfType()
isElementOfType(
element,
componentClass
)
element
๊ฐ componentClass
ํ์
์ React element๋ผ๋ฉด true
๋ฅผ ๋ฐํํฉ๋๋ค.
isDOMComponent()
isDOMComponent(instance)
instance
๊ฐ <div>
๋ <span>
๊ฐ์ DOM ์ปดํฌ๋ํธ๋ผ๋ฉด true
๋ฅผ ๋ฐํํฉ๋๋ค.
isCompositeComponent()
isCompositeComponent(instance)
instance
๊ฐ ํด๋์ค๋ ํจ์ ๊ฐ์ด ์ฌ์ฉ์๊ฐ ์ ์ํ ์ปดํฌ๋ํธ๋ผ๋ฉด true
๋ฅผ ๋ฐํํฉ๋๋ค.
isCompositeComponentWithType()
isCompositeComponentWithType(
instance,
componentClass
)
instance
๊ฐ componentClass
ํ์
์ ๊ฐ์ง ์ปดํฌ๋ํธ๋ผ๋ฉด true
๋ฅผ ๋ฐํํฉ๋๋ค.
findAllInRenderedTree()
findAllInRenderedTree(
tree,
test
)
tree
์ ๋ชจ๋ ์ปดํฌ๋ํธ๋ฅผ ํ์ํ์ฌ test(component)
๊ฐ true
์ผ ๋ ๋ชจ๋ ์ปดํฌ๋ํธ๋ฅผ ์ถ์ ํฉ๋๋ค. ์ด ํจ์๋ ๊ทธ ์์ฒด๋ง์ผ๋ก๋ ์ ์ฉํ์ง ์์ง๋ง, ๋ค๋ฅธ ํ
์คํธ ๋๊ตฌ์ ๊ธฐ๋ฐ์ด ๋ฉ๋๋ค.
scryRenderedDOMComponentsWithClass()
scryRenderedDOMComponentsWithClass(
tree,
className
)
๋ ๋๋ง ๋ ํธ๋ฆฌ์์ ์กฐ๊ฑด className
์ ๋ง์กฑํ๋ class๋ช
์ ๊ฐ์ง๊ณ ์๋ DOM ์ปดํฌ๋ํธ์ DOM ์๋ฆฌ๋จผํธ๋ฅผ ๋ชจ๋ ๊ฒ์ํฉ๋๋ค.
findRenderedDOMComponentWithClass()
findRenderedDOMComponentWithClass(
tree,
className
)
scryRenderedDOMComponentsWithClass()
์ ๊ธฐ๋ฅ์ด ์ ์ฌํ๋ ๊ฒฐ๊ณผ๊ฐ์ด ํ๋๋ผ๊ณ ๊ฐ์ ํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๊ฐ๋ง์ ๋ฐํํฉ๋๋ค. ๋ ๊ฐ ์ด์์ ๊ฒฐ๊ณผ๊ฐ์ด ์๋ค๋ฉด ์์ธ๋ฅผ ๋ฐํํฉ๋๋ค.
scryRenderedDOMComponentsWithTag()
scryRenderedDOMComponentsWithTag(
tree,
tagName
)
๋ ๋๋ง ๋ ํธ๋ฆฌ ๋ด์์ ์กฐ๊ฑด tagName
์ ๋ง์กฑํ๋ tag๋ช
์ ๊ฐ์ง DOM ์ปดํฌ๋ํธ์ DOM ์๋ฆฌ๋จผํธ๋ฅผ ๋ชจ๋ ๊ฒ์ํฉ๋๋ค.
findRenderedDOMComponentWithTag()
findRenderedDOMComponentWithTag(
tree,
tagName
)
scryRenderedDOMComponentsWithTag()
์ ๊ธฐ๋ฅ์ด ์ ์ฌํ๋ ๊ฒฐ๊ณผ๊ฐ์ด ํ๋๋ผ๊ณ ๊ฐ์ ํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๊ฐ๋ง์ ๋ฐํํฉ๋๋ค. ๋ ๊ฐ ์ด์์ ๊ฒฐ๊ณผ๊ฐ์ด ์๋ค๋ฉด ์์ธ๋ฅผ ๋ฑ์ต๋๋ค.
scryRenderedComponentsWithType()
scryRenderedComponentsWithType(
tree,
componentClass
)
componentClass
ํ์
์ ๊ฐ์ง ๋ชจ๋ ์ธ์คํด์ค๋ฅผ ๊ฒ์ํฉ๋๋ค.
findRenderedComponentWithType()
findRenderedComponentWithType(
tree,
componentClass
)
scryRenderedComponentsWithType()
์ ๊ธฐ๋ฅ์ด ์ ์ฌํ๋ ๊ฒฐ๊ณผ๊ฐ์ด ํ๋๋ผ๊ณ ๊ฐ์ ํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๊ฐ๋ง์ ๋ฐํํฉ๋๋ค. ๋ ๊ฐ ์ด์์ ๊ฒฐ๊ณผ๊ฐ์ด ์๋ค๋ฉด ์์ธ๋ฅผ ๋ฑ์ต๋๋ค.
renderIntoDocument()
renderIntoDocument(element)
React ์๋ฆฌ๋จผํธ๋ฅผ document๋ด์ ๋จ์ด์ ธ ์๋ DOM ๋ ธ๋์ ๋ ๋๋งํฉ๋๋ค. ์ด ํจ์๋ฅผ ์ฐ๋ ค๋ฉด DOM์ด ํ์ํฉ๋๋ค. ์ด ํจ์๋ ๋ค์ ์ฝ๋์ ๊ฐ์ ๊ธฐ๋ฅ์ ํฉ๋๋ค.
const domContainer = document.createElement('div');
ReactDOM.createRoot(domContainer).render(element);
์ฃผ์
window
,window.document
์window.document.createElement
๋React
๋ฅผ ๊ฐ์ ธ์์ ์ฌ์ฉํ๊ธฐ ์ ์๋ ์ ์ญ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ทธ๋ ์ง ์๋ค๋ฉด React๋ DOM์ ์ ๊ทผํ ์ ์๋ค๊ณ ๊ฐ์ฃผํ ๊ฒ์ด๋ฉฐsetState
์ ๊ฐ์ ๋ฉ์๋๋ค์ด ์๋ํ์ง ์์ ๊ฒ ์ ๋๋ค.
๋ค๋ฅธ ํ ์คํ ๋๊ตฌ๋ค
Simulate
Simulate.{eventName}(
element,
[eventData]
)
์ด๋ฒคํธ ๋ฐ์ดํฐ์ธ eventData
๋ฅผ ์ต์
์ผ๋ก ์ค DOM ๋
ธ๋์ ๋ถ์ด๋ ์ด๋ฒคํธ๋ฅผ ์๋ฎฌ๋ ์ดํ
ํฉ๋๋ค.
Simulate
๋ React๊ฐ ์ดํดํ๋ ๋ชจ๋ ์ด๋ฒคํธ๋ฅผ ์ํ ๋ฉ์๋๋ฅผ ๊ฐ์ง๋๋ค.
์๋ฆฌ๋จผํธ ํด๋ฆญ
// <button ref={(node) => this.button = node}>...</button>
const node = this.button;
ReactTestUtils.Simulate.click(node);
์ ๋ ฅ ํ๋์ ๊ฐ์ ๋ฐ๊พผ ๋ค ENTERํค ๋๋ฅด๊ธฐ
// <input ref={(node) => this.textInput = node} />
const node = this.textInput;
node.value = 'giraffe';
ReactTestUtils.Simulate.change(node);
ReactTestUtils.Simulate.keyDown(node, {key: "Enter", keyCode: 13, which: 13});
์ฃผ์
์ปดํฌ๋ํธ ๋ด์์ ์ฌ์ฉํ๊ณ ์๋ keyCode, which์ ๊ฐ์ ์ด๋ฒคํธ ํ๋กํผํฐ๋ ๋ณ๋๋ก ์ ๊ณตํด์ฃผ์ด์ผ ํฉ๋๋ค. React์์๋ ์ด๋ฌํ ์ด๋ฒคํธ ํ๋กํผํฐ๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด ์ฃผ์ง ์์ต๋๋ค.