开玩笑/酶-生命周期方法中的模拟异步功能

时间:2018-08-17 12:12:20

标签: reactjs unit-testing jestjs enzyme

我试图用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上[作为_invoke](D:\\ node_modules \ regenerator-runtime \ runtime.js:296:22)             在Generator.prototype。(匿名函数)[下一个](D:\\ node_modules \ regenerator-runtime \ runtime.js:114:21)             在步骤(D:\\ components \ App.js:38:191)             在D:\\ components \ App.js:38:437             在新的Promise()             在应用程序上。 (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)             在新的Promise()             在映射器上(D:\\ node_modules \ jest-jasmine2 \ build \ queue_runner.js:43:19)             然后承诺(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"
      },

1 个答案:

答案 0 :(得分:1)

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

let apiMockActions;

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

生命周期默认情况下在酶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 });
});