Jest / Enzyme - 生命周期方法中的模拟异步函数

Posted

技术标签:

【中文标题】Jest / Enzyme - 生命周期方法中的模拟异步函数【英文标题】:Jest / Enzyme - mock async function in lifecycle method 【发布时间】:2019-01-24 11:38:38 【问题描述】:

我正在尝试使用 Jest 对我的 React 组件进行单元测试。我的测试通过了,但由于 try/catch 中的 console.error() 而出现 TypeError。我认为我的模拟设置不正确,但我尝试在没有任何结果的情况下异步进行。我很感激任何提示。

console.error 组件/App.js:91 TypeError:无法读取未定义的属性“fetchPoints” 在 App.fetchPoints (D:\\components\App.js:87:48) 在 tryCatch (D:\\node_modules\regenerator-runtime\runtime.js:62:40) 在 Generator.invoke [as _invoke] (D:\\node_modules\regenerator-runtime\runtime.js:296:22) 在 Generator.prototype.(anonymous function) [as next] (D:\\node_modules\regenerator-runtime\runtime.js:114:21) 在步骤 (D:\\components\App.js:38:191) 在 D:\\components\App.js:38:437 在新的承诺 () 在应用程序。 (D:\\components\App.js:38:99) 在 App.componentDidMount (D:\\components\App.js:155:30) 在 D:\\node_modules\react-test-renderer\lib\ReactCompositeComponent.js:262:25 在 measureLifeCyclePerf (D:\\node_modules\react-test-renderer\lib\ReactCompositeComponent.js:73:12) 在 D:\\node_modules\react-test-renderer\lib\ReactCompositeComponent.js:261:11 在 CallbackQueue.notifyAll (D:\\node_modules\react-test-renderer\lib\CallbackQueue.js:74:22) 在 ReactTestReconcileTransaction.close (D:\\node_modules\react-test-renderer\lib\ReactTestReconcileTransaction.js:34:26) 在 ReactTestReconcileTransaction.closeAll (D:\\node_modules\react-test-renderer\lib\Transaction.js:207:25) 在 ReactTestReconcileTransaction.perform (D:\\node_modules\react-test-renderer\lib\Transaction.js:154:16) 在 batchedMountComponentIntoNode (D:\\node_modules\react-test-renderer\lib\ReactTestMount.js:67:27) 在 ReactDefaultBatchingStrategyTransaction.perform (D:\\node_modules\react-test-renderer\lib\Transaction.js:141:20) 在 Object.batchedUpdates (D:\\node_modules\react-test-renderer\lib\ReactDefaultBatchingStrategy.js:60:26) 在 Object.batchedUpdates (D:\\node_modules\react-test-renderer\lib\ReactUpdates.js:95:27) 在 Object.render (D:\\node_modules\react-test-renderer\lib\ReactTestMount.js:126:18) 在 Object.create (D:\\components__tests__\App.test.js:26:31) 在 Object.asyncJestTest (D:\\node_modules\jest-jasmine2\build\jasmine_async.js:108:37) 解决时 (D:\\node_modules\jest-jasmine2\build\queue_runner.js:56:12) 在新的承诺 () 在映射器 (D:\\node_modules\jest-jasmine2\build\queue_runner.js:43:19) 在 promise.then (D:\\node_modules\jest-jasmine2\build\queue_runner.js:87:41) 在 在 process._tickCallback (internal/process/next_tick.js:188:7)

App.test.js

我尝试使用 mount() 和 fetchPoints 来返回 Promise.resolve()。

import React from 'react';
    import renderer from 'react-test-renderer';
    import  shallow, configure, mount  from 'enzyme';
    import Adapter from 'enzyme-adapter-react-15';
    import  App  from '../App';
    
    configure( adapter: new Adapter() );
    
    describe('App', () => 
        let wrapper;
        const apiMockActions = 
            fetchPoints : jest.fn()
        ;
    
        beforeEach(() => 
            wrapper = shallow(<App actions=apiMockActions />);
        );
     
        it('should call fetchPoints in #componentDidMount', () => 
            return wrapper.instance().componentDidMount().then(() => 
                expect(apiMockActions.fetchPoints).toHaveBeenCalled();
            );
        );
    
    );

App.js

import React,  Component  from 'react';
    import  connect  from 'react-redux';
    import * as api from '../actions/api';
    import  bindActionCreators  from 'redux';
    
    export class App extends Component 
        constructor(props)
            super(props);
            this.state = 
                data: []
              
        
        async componentDidMount() 
            try 
                let res = await this.props.actions.fetchPoints();
                //this.setState( data: res );
                
             catch (error) 
                console.error(error);
            
        
    
        render() 
            return (
                <div className="col-md-12 app__div">      
                </div>
            );
        
    
    
    function mapStateToProps(state) 
        return 
            data: state.points
        ;
    
    
    function mapDispatchToProps(dispatch) 
        return 
            actions: bindActionCreators(Object.assign(, api), dispatch)
        ;
    
    
    export default connect(mapStateToProps, mapDispatchToProps)(App);

package.json

  "dependencies": 
        "ag-grid": "^14.2.0",
        "ag-grid-react": "^14.2.0",
        "axios": "^0.16.2",
        "babel-eslint": "^8.2.6",
        "babel-polyfill": "^6.26.0",
        "chart.js": "^2.7.1",
        "classnames": "^2.2.5",
        "eslint": "^5.2.0",
        "eslint-plugin-import": "^2.13.0",
        "eslint-plugin-react": "^7.10.0",
        "lodash": "^4.15.0",
        "moment": "^2.19.3",
        "prop-types": "^15.6.0",
        "rc-time-picker": "^2.4.1",
        "react": "^15.6.2",
        "react-addons-css-transition-group": "^15.6.2",
        "react-autosuggest": "^9.3.2",
        "react-bootstrap": "^0.31.5",
        "react-color": "^2.14.0",
        "react-datalist": "^4.0.0",
        "react-datepicker": "^0.29.0",
        "react-dom": "^15.6.2",
        "react-dom-factories": "^1.0.2",
        "react-dropzone": "^3.13.4",
        "react-grid-layout": "^0.15.3",
        "react-intl": "^2.4.0",
        "react-notification-system": "^0.2.16",
        "react-notify": "^2.0.1",
        "react-redux": "^5.0.6",
        "react-timepicker": "^1.3.1",
        "react-toggle-display": "^2.2.0",
        "redux": "^3.7.2",
        "redux-logger": "^3.0.6",
        "redux-promise-middleware": "^4.4.2",
        "redux-thunk": "^2.2.0",
        "section-iterator": "^2.0.0",
        "style-loader": "^0.13.2"
      ,
      "devDependencies": 
        "babel-core": "^6.26.0",
        "babel-jest": "^23.4.0",
        "babel-loader": "^7.1.2",
        "babel-plugin-lodash": "^3.3.2",
        "babel-plugin-syntax-object-rest-spread": "^6.13.0",
        "babel-plugin-transform-class-properties": "^6.24.1",
        "babel-plugin-transform-decorators-legacy": "^1.3.4",
        "babel-preset-env": "^1.6.1",
        "babel-preset-es2015": "^6.24.1",
        "babel-preset-react": "^6.24.1",
        "babel-preset-stage-0": "^6.24.1",
        "chalk": "^2.3.0",
        "cross-env": "^5.1.1",
        "css-loader": "^0.23.1",
        "enzyme": "^3.3.0",
        "enzyme-adapter-react-15": "^1.0.6",
        "jest": "^23.4.1",
        "jest-cli": "^23.4.1",
        "parallel-webpack": "^1.5.0",
        "progress-bar-webpack-plugin": "^1.11.0",
        "react-test-renderer": "^15.6.2",
        "redux-immutable-state-invariant": "^2.1.0",
        "style-loader": "^0.13.1",
        "webpack": "^4.16.1",
        "webpack-cli": "^3.0.8"
      ,

【问题讨论】:

您使用的是旧版本的react,可以分享一下您的package.json吗? 【参考方案1】:

应该为每个测试创建间谍和存根:

let apiMockActions;

beforeEach(() => 
  apiMockActions = 
    fetchPoints : jest.fn()
  ;
);

在 Enzyme 3 及更高版本中默认启用生命周期,因此手动调用 componentDidMount 会导致调用它两次。他们can be disabled 是为了链接componentDidMount 的承诺:

beforeEach(() => 
    wrapper = shallow(<App actions=apiMockActions />,  disableLifecycleMethods: true );
);

返回的承诺值可以用mockResolvedValue模拟:

it('should call fetchPoints in #componentDidMount', async () => 
    const points = [...];
    const instance = wrapper.instance();
    apiMockActions.fetchPoints.mockResolvedValue(points);
    await instance.componentDidMount();
    expect(apiMockActions.fetchPoints).toHaveBeenCalled();
    expect(instance.state.data).toEqual( data: points );
);

【讨论】:

仍然收到此控制台消息。所有测试都通过了。 console.log components/App.js:38 无法读取未定义的属性“fetchPoints” 我更新了答案。但是,如果您包含建议的 beforeEach,我不确定如何取消定义 actions。我建议调试您的代码并确保apiMockActions(&lt;App actions=apiMockActions /&gt; 中的一个对象。 一切都好。 console.log(typeof instance.props.actions) outputs: object fetchPoints 函数也在实例中定义。当我从 try/catch 中删除 console.error(e) 语句时,它不会输出“未定义”。我很好奇为什么会调用 catch 块,因为我定义了解析值。 当我定义 mockRejectedValue('403') 时,它会按预期在控制台中输出 '403'。 fetchPoints undefiend 没有出现。但是,当我没有定义 mockRejectedValue 时,我仍然不知道为什么在 catch 块中调用 fetchPoints() 函数。我认为它应该输出“错误”未定义。我会在 Jest 文档中做一些研究。感谢您的帮助! 它输出未定义,因为this.props.actions 未定义并导致错误。我认为这与 mockRejectedValue 无关,一个模拟在 OP 中设置不正确,但不会导致此错误。您提供的代码无法解释它。由于actions 明确提供为shallow(&lt;App actions=apiMockActions /&gt;, ...),这可能意味着App 在其他地方被实例化。还要仔细检查apiMockActions 是否是您调用shallow 的对象。

以上是关于Jest / Enzyme - 生命周期方法中的模拟异步函数的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Jest - Enzyme 测试 React 中 mapStateToProps 中的方法

Jest/Enzyme 使用 try/catch 中的 async/await 方法测试 React 组件

测试包含在 withRouter 中的反应组件(最好使用 jest/enzyme)

在使用 Jest/Enzyme 进行测试期间检测 React 中的合成点击

如何使用 Jest 和/或 Enzyme 获取嵌套在 React 组件中的元素的属性?

在 JEST + ENZYME 中无法达到 100% 的分支覆盖率