利用 Jest 为 React 组件编写单元测试
前情回顾
react-test-renderer
react-dom/test-utils
Enzyme
Q&A
今天节目的主要内容有
前情回顾
谁还记得两周前我们说过啥?

什么是单元测试?
单元测试的基本构成
Jest 的基本使用
React 组件的单元测试有何不同呢?
没有什么不同
套路都是一样的
我们只需要根据 React 组件自身的特点测试相应功能即可
React 组件 render 的结果是一个组件数,最终会被 React 解析成纯粹由 HTML 构成的 DOM 树
React 组件可以有 state,且 state 的变化会影响 render 的结果
对应 React 组件来说,props 就是它的入参
React 组件可以拥有生命周期函数,并会在特定的时间点执行这些函数
如何以一种有利于测试的方式 render 组件?
什么是 renderer?
shallow render
vs
full render
shallow render
import ShallowRenderer from 'react-test-renderer/shallow';
const renderer = new ShallowRenderer();
- render() 用于 render 一个组件。你可以把 ShallowRenderer 的实例想象成一个容纳被 render 组件的“空间”。
- getRenderOutput() 在 render 之后,可以通过此命令获取 render 的结果。
Demo
const Link = ({to, children}) => (
<a className="my-link" href={to} target="_blank" rel="noopener noreferrer">{children}</a>
);
const Header = () => (
<div>
<span className="brand">Hello world</span>
<Link to="https://jd.com">JD</Link>
<Link to="http://butler.jd.com">Butler</Link>
<Link to="http://lrc.jd.com">lrc</Link>
</div>
);
describe('Header', () => {
it('should render a top level div', () => {
const renderer = new ShallowRenderer();
renderer.render(<Header />);
const result = renderer.getRenderOutput();
expect(result.type).toBe('div');
});
it('should render 3 Link', () => {
const renderer = new ShallowRenderer();
renderer.render(<Header />);
const result = renderer.getRenderOutput();
const childrenLink = result.props.children.filter(c => c.type === Link);
expect(childrenLink.length).toBe(3);
});
});
full render
import TestRenderer from 'react-test-renderer';
TestRender.create(...)
TestRenderer 实例上的方法与属性
- .toJSON()
- .toTree()
- .update(element)
- .umount()
- .getInstance()
- .root
test instance 上的属性与方法
.find() & findAll()
.findByType() & .findAllByType()
.findByProps() & .findAllByProps()
.instance
describe('Header', () => {
it('should render 3 a tag with className "my-link"', () => {
const testRenderer = TestRenderer.create(<Header />);
const testInstance = testRenderer.root;
expect(
testInstance.findAll(
node => node.type === 'a' && node.props.className === 'my-link'
)
).toHaveLength(3);
});
});
import ReactTestUtils from 'react-dom/test-utils';
- .Simulate.{evnentName}()
- .renderIntoDocument()
- .scryRenderedDOMComponentsWithClass()
- .findRenderedDOMComponentWithClass()
- .scryRenderedDOMComponentsWithTag()
- .findRenderedDOMComponentWithTag()
ReactTestUtils 上常用的方法
class Button extends React.Component {
constructor() {
super();
this.state = { disabled: false };
this.handClick = this.handClick.bind(this);
}
handClick() {
if (this.state.disabled) { return }
if (this.props.onClick) { this.props.onClick() }
this.setState({ disabled: true });
setTimeout(() => {this.setState({ disabled: false })}, 200);
}
render() {
return (
<button className="my-button" onClick={this.handClick}>{this.props.children}</button>
);
}
};
it('should call onClick callback if provided', () => {
const onClickMock = jest.fn();
const testInstance = ReactTestUtils.renderIntoDocument(
<Button onClick={onClickMock}>hello</Button>
);
const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
ReactTestUtils.Simulate.click(buttonDom);
expect(onClickMock).toHaveBeenCalled();
});
it('should be throttled to 200ms', () => {
const testInstance = ReactTestUtils.renderIntoDocument(<Button>hello</Button>);
const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
ReactTestUtils.Simulate.click(buttonDom);
expect(testInstance.state.disabled).toBeTruthy();
jest.advanceTimersByTime(199);
expect(testInstance.state.disabled).toBeTruthy();
jest.advanceTimersByTime(1);
expect(testInstance.state.disabled).toBeFalsy();
});
- Shallow Rendering 对应 react-test-renderer/shallow
- Full DOM Rendering 对应 react-dom/test-utils
- Static Rendering 对应 react-test-renderer
describe('Button', () => {
it('should be throttled to 200ms', () => {
const wrapper = mount(<Button>hello</Button>);
wrapper.find('.my-button').simulate('click');
expect(wrapper.state('disabled')).toBeTruthy();
jest.advanceTimersByTime(199);
expect(wrapper.state('disabled')).toBeTruthy();
jest.advanceTimersByTime(1);
expect(wrapper.state('disabled')).toBeFalsy();
});
it('should call onClick callback if provided', () => {
const onClickMock = jest.fn();
const wrapper = mount(<Button onClick={onClickMock}>hello</Button>);
wrapper.find('.my-button').simulate('click');
expect(onClickMock).toHaveBeenCalled();
});
});
Q & A
利用 Jest 为 React 组件编写单元测试
By loveky
利用 Jest 为 React 组件编写单元测试
- 1,424