React/Redux 用酶进行测试

React/Redux Testing w/ Enzyme

我正在学习如何使用酶测试 React/Redux 成分。该组件将应用程序级状态作为道具。当我 运行 测试时,我得到错误:

Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components).

TypeError: Cannot read property 'contextTypes' of undefined

我的 console.log of wrapper 在下面的测试文件中记录为 undefined.

我知道我的设置有问题,并花了几个小时试图解决这个问题。任何人都可以看到我正在导入和尝试使用该组件的方式有什么明显的吗?我想不通为什么是 undefined。在此先感谢您的帮助或见解!

BackendDisplay.js

import React from 'react';
import { connect } from 'react-redux';
import moment from 'moment';

var BackendDisplay = React.createClass({

  render() {

    const { username, node_version, app_path, timestamp } = this.props.loginState;
    const dateTime = moment(timestamp).format('MMMM Do YYYY, h:mm:ss a');

    return (
      <div>
        <h1>Welcome, {username}!</h1>
        <p><span className="bold">Node Version:</span> {node_version}</p>
        <p><span className="bold">Application Path:</span> {app_path}</p>
        <p><span className="bold">Date/Time:</span> {dateTime}</p>
      </div>
    );
  }
});

const mapStateToProps = function(store) {
  return store;
}

module.exports = connect(mapStateToProps)(BackendDisplay);

BackendDisplay.test.js

'use strict';

import React from 'react';
import {shallow} from 'enzyme';
import { connect } from 'react-redux';
import { BackendDisplay } from '../components/BackendDisplay';

describe('<BackendDisplay />', () => {

  it('Correctly displays username, node_version, app_path, and timestamp', () => {

    const wrapper = shallow(<BackendDisplay />);
    console.log(wrapper);

  });

});

修改后编辑: BackendDisplay.js

import React from 'react';
import { connect } from 'react-redux';
import moment from 'moment';

var BackendDisplay = React.createClass({

  render() {

    const { username, node_version, app_path, timestamp } = this.props.loginState;
    const dateTime = moment(timestamp).format('MMMM Do YYYY, h:mm:ss a');

    return (
      <div>
        <h1>Welcome, {username}!</h1>
        <p><span className="bold">Node Version:</span> {node_version}</p>
        <p><span className="bold">Application Path:</span> {app_path}</p>
        <p><span className="bold">Date/Time:</span> {dateTime}</p>
      </div>
    );
  }
});

const mapStateToProps = function(store) {
  return store;
}

// module.exports = connect(mapStateToProps)(BackendDisplay);
export default connect(mapStateToProps)(BackendDisplay);

BackendDisplay.test.js

'use strict';

import React from 'react';
import {shallow} from 'enzyme';
import { connect } from 'react-redux';
import store from '../store';
import { Provider } from 'react-redux';
import ConnectedBackendDisplay, {BackendDisplay} from '../components/BackendDisplay';

describe('<BackendDisplay />', () => {

  it('Correctly displays username, node_version, app_path, and timestamp', () => {

    const wrapper = shallow(
      <Provider store={store}>
        <BackendDisplay />
      </Provider>
    );

    console.log(wrapper.find(BackendDisplay));
    expect(wrapper.find(BackendDisplay).length).to.equal(1);

  });

});

错误信息: TypeError: Enzyme::Selector expects a string, object, or Component Constructor

您的 BackendDisplay 是一个容器组件,它通过使用连接 api.

连接到 Redux 存储

您应该导出未修饰的组件以进行测试。因为它是未修饰的,这个导出的组件将不会被 react-redux 的 Connect 组件包裹。

var BackendDisplay = React.createClass({

  render() {

    const { username, node_version, app_path, timestamp } = this.props.loginState;
    const dateTime = moment(timestamp).format('MMMM Do YYYY, h:mm:ss a');

    return (
      <div>
        <h1>Welcome, {username}!</h1>
        <p><span className="bold">Node Version:</span> {node_version}</p>
        <p><span className="bold">Application Path:</span> {app_path}</p>
        <p><span className="bold">Date/Time:</span> {dateTime}</p>
      </div>
    );
  }
});

然后你可以按如下方式导入,使测试生效

import {BackendDisplay} from 'BackendDisplay'

作为奖励,您还可以通过更改以下行导出装饰后的 BackendDisplay 组件

module.exports = connect(mapStateToProps)(BackendDisplay);

 export default connect(mapStateToProps)(BackendDisplay);

这是导入修饰和未修饰组件的方法

import ConnectedBackendDisplay, {BackendDisplay} from 'BackendDisplay'  

ConnectedBackendDisplay 指的是被装饰的组件 通过未命名导出导出(导出默认 BackendDisplay)。

我们只是给它起这个名字,这样就很清楚它被包裹在一个连接组件中。

我更新了以下组件以使用导出默认值,这提供了一个未命名的导出。

后端显示

import React from 'react';
import { connect } from 'react-redux';
import moment from 'moment';

export const BackendDisplay = React.createClass({

  render() {

    const { username, node_version, app_path, timestamp } = this.props;
    // removed reference to this.props.loginState

    const dateTime = moment(timestamp).format('MMMM Do YYYY, h:mm:ss a');

    return (
      <div>
        <h1>Welcome, {username}!</h1>
        <p><span className="bold">Node Version:</span> {node_version}</p>
        <p><span className="bold">Application Path:</span> {app_path}</p>
        <p><span className="bold">Date/Time:</span> {dateTime}</p>
      </div>
    );
  }
});

const mapStateToProps = function(store) {
  return store;
}

export default connect(mapStateToProps)(BackendDisplay);

这里是测试套件,用于演示使用酶将上述组件作为装饰组件和未装饰组件进行测试。

我正在使用 chai 库来简化测试断言。 jsdom 库也被用来创建一个 DOM 环境,这样我们就可以使用 Enzyme 的挂载功能来测试组件,它可以完全呈现​​组件。

测试

'use strict';
import React from 'react';
import jsdom from 'jsdom'
import { expect } from 'chai'
import { shallow , mount} from 'enzyme';
import { Provider } from 'react-redux';
import ConnectedBackendDisplay, // decorated component
   {BackendDisplay} from 'app/components/BackendDisplay';  // undecorated component
// for mocking a store to test the decorated component
import configureMockStore from 'redux-mock-store'; 

// create a fake DOM environment so that we can use Enzyme's mount to
// test decorated components
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')
global.document = doc
global.window = doc.defaultView


describe.only('<BackendDisplay />', () => {

    it('undecorated component correctly displays username', () => {
        // define the prop we want to pass in
        const username = 'Foo'
        // render the component with the prop
        const wrapper = mount(<BackendDisplay username={username} />);
        // test that the text of the first <p> equals username prop that we passed in       
        expect(wrapper.find('h1').first().text()).to.equal(username);
   });

    it('decorated component correctly displays username', () => {
        // define the prop we want to pass in
        const username = 'Foo'
        const initialState = { }
        // create our mock store with an empyty initial state
        const store = configureMockStore(initialState)

        // render the component with the mockStore
        const wrapper = shallow(<Provider store={store}>
                                <ConnectedBackendDisplay username={username}/>
                              </Provider>);

        // test that the text of the first <p> equals username prop that we passed in       
        expect(wrapper.find('h1').first().text()).to.equal(username);
   });
});