如何用Jest或Sinon测试React组件componentDidMount中调用的方法?
我近期需要测试React组件的componentDidMount方法中是否调用了自定义方法getDocuments。组件核心代码如下:
componentDidMount() { this.props.actions.getDocuments(); }
组件通过Redux的connect、bindActionCreators关联actions,完整实现代码:
export class ComponentList extends React.Component { constructor(props) { super(props) } componentDidMount() { this.props.actions.getDocuments(); } render() { return ( <div className="allowScroll"> .... </div> ) } } ComponentList.propTypes = { document: PropTypes.object, actions: PropTypes.object.isRequired }; const mapStateToProps = (state) => { return { document: state.document } }; const mapDispatchToProps = (dispatch) => { return { actions: bindActionCreators(componentActions, dispatch) }; } export default connect(mapStateToProps, mapDispatchToProps)(ComponentList)
我用Jest作为测试框架,用jest.fn()创建mock函数编写测试代码:
function setup(data) { const props = { session: {}, actions: { getDocuments: jest.fn() } }; const wrapper = mount(<ComponentList {...props} />, { context: { muiTheme: getMuiTheme() }, childContextTypes: { muiTheme: React.PropTypes.object.isRequired } } ); return { props, wrapper }; } describe('component:', () => { let component; describe('Given that the container is loaded', () => { beforeAll(() => { component = setup(); }); it('should call the getDocuments to get the data', () => { expect(component.props.actions.getDocuments).toHaveBeenCalled(); }); }); });
运行测试时报错:
TypeError: received.getMockName is not a function at Object.<anonymous> (src/containers/ComponentList/ComponentList.spec.js:61:158) at new Promise (<anonymous>) at <anonymous> at process._tickCallback (internal/process/next_tick.js:188:7)
改用Sinon替换Jest的mock函数后,仍报错:
expect(jest.fn())[.not].toHaveBeenCalled() jest.fn() value must be a mock function or spy. Received: function: [Function proxy] at Object.<anonymous> (src/containers/ComponentList/ComponentList.spec.js:61:158) at new Promise (<anonymous>) at <anonymous> at process._tickCallback (internal/process/next_tick.js:188:7)
请问是否可以使用Jest或Sinon实现该测试需求?如果可以,具体应该如何操作?
当然可以用Jest或者Sinon来完成这个测试,你遇到的报错主要是因为测试时没有正确处理Redux connect包裹后的组件,或者断言方式不匹配工具的要求。下面分别给出两种工具的正确实现步骤:
方法一:使用Jest完成测试
核心问题是你直接测试了被connect包裹后的默认导出组件,而不是原始的ComponentList类组件。connect会创建一个高阶组件,直接挂载它会导致你的mock props无法正确传递到原始组件中。正确的做法是测试未被connect包裹的原始组件:
- 在测试文件中导入原始的
ComponentList(注意用花括号,不是默认导出):
import { ComponentList } from './ComponentList';
- 修改
setup函数,确保mock函数正确传递,且挂载原始组件:
import React from 'react'; import { mount } from 'enzyme'; import { ComponentList } from './ComponentList'; import getMuiTheme from 'material-ui/styles/getMuiTheme'; function setup() { const mockGetDocuments = jest.fn(); const props = { session: {}, actions: { getDocuments: mockGetDocuments }, document: {} // 满足propTypes的要求,避免警告 }; const wrapper = mount(<ComponentList {...props} />, { context: { muiTheme: getMuiTheme() }, childContextTypes: { muiTheme: React.PropTypes.object.isRequired } }); return { props, wrapper, mockGetDocuments }; } describe('ComponentList', () => { describe('When the component mounts', () => { it('should call getDocuments', () => { const { mockGetDocuments } = setup(); expect(mockGetDocuments).toHaveBeenCalled(); }); }); });
这样修改后,你直接测试的是原始组件,mock函数会被正确调用,断言也能正常工作。之前的报错正是因为测试高阶组件导致mock没有到达目标方法。
方法二:使用Sinon完成测试
如果你更习惯用Sinon,只需要把Jest的jest.fn()替换成Sinon的sinon.spy(),同时注意用Sinon对应的断言方法(或适配Jest断言):
- 导入Sinon:
import sinon from 'sinon';
- 修改
setup函数和断言逻辑:
function setup() { const mockGetDocuments = sinon.spy(); const props = { session: {}, actions: { getDocuments: mockGetDocuments }, document: {} }; const wrapper = mount(<ComponentList {...props} />, { context: { muiTheme: getMuiTheme() }, childContextTypes: { muiTheme: React.PropTypes.object.isRequired } }); return { props, wrapper, mockGetDocuments }; } describe('ComponentList', () => { describe('When the component mounts', () => { it('should call getDocuments', () => { const { mockGetDocuments } = setup(); // 用Sinon自带的断言更稳妥 sinon.assert.calledOnce(mockGetDocuments); // 或者用Jest断言:expect(mockGetDocuments.calledOnce).toBe(true); }); }); });
这里用Sinon自带的断言方法可以避免Jest识别不了Sinon spy的问题,如果你坚持用Jest的toHaveBeenCalled(),需要确保测试环境兼容,但用Sinon原生断言是更稳妥的选择。
额外注意点
- 单元测试只关注组件本身的逻辑,所以一定要测试未被Redux connect包裹的原始组件,高阶组件的测试应该放在集成测试中。
- 确保传递给组件的props满足
propTypes的要求,避免控制台出现不必要的警告,也能让测试更严谨。
内容的提问来源于stack exchange,提问作者Sinha

