使用酶进行React / Redux测试

时间:2016-12-18 15:47:38

标签: javascript unit-testing reactjs redux enzyme

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

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

将以下测试文件中的wrapper我的console.log记录为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

1 个答案:

答案 0 :(得分:4)

您的BackendDisplay是一个容器组件,它通过使用connect 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)。

我们只是给它这个名字,以便它清楚地包含在一个连接组件中。

我更新了以下组件以使用export default,它提供了一个未命名的导出。

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的mount函数测试组件,该函数可以完全呈现​​组件。

测试

'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);
   });
});