无法读取未定义的单元测试酶的 属性 'contextTypes'

Cannot read property 'contextTypes' of undefined Unit Testing enzyme

我正在尝试使用 redux 应用程序在我的反应中进行单元测试。所以我需要测试连接的组件不幸的是我得到了这个错误:

Cannot read property 'contextTypes' of undefined I use enzyme in unit testing Here is my component:

    import React from 'react';
    import TextFieldGroup from '../common/TextFieldGroup';
    import validateInput from '../../server/validations/login';
    import { connect } from 'react-redux';
    import { login } from '../../actions/authActions';

    class LoginForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          username: '',
          password: '',
          errors: {},
          isLoading: false
        };

        this.onSubmit = this.onSubmit.bind(this);
        this.onChange = this.onChange.bind(this);
      }

      isValid() {
        const { errors, isValid } = validateInput(this.state);

        if (!isValid) {
          this.setState({ errors });
        }

        return isValid;
      }

      onSubmit(e) {
        e.preventDefault();
        if (this.isValid()) {
          this.setState({ errors: {}, isLoading: true });
          this.props.login(this.state).then(
            (res) => this.context.router.push('/'),
            (err) => this.setState({ errors: err.response.data.errors, isLoading: false })
          );
        }
      }

      onChange(e) {
        this.setState({ [e.target.name]: e.target.value });
      }

      render() {
        const { errors, username, password, isLoading } = this.state;

        return (
          <form onSubmit={this.onSubmit}>
            <h1>Login</h1>

            { errors.form && <div className="alert alert-danger">{errors.form}</div> }

            <TextFieldGroup
              field="username"
              label="Username"
              value={username}
              error={errors.username}
              onChange={this.onChange}
            />

            <TextFieldGroup
              field="password"
              label="Password"
              value={password}
              error={errors.password}
              onChange={this.onChange}
              type="password"
            />

            <div className="form-group"><button className="btn btn-primary" disabled={isLoading}>Login</button></div>
          </form>
        );
      }
    }

    LoginForm.propTypes = {
      login: React.PropTypes.func.isRequired
    }

    LoginForm.contextTypes = {
      router: React.PropTypes.object.isRequired
    }

    export default connect(null, { login })(LoginForm);

这是我的测试:

    import React from 'react';
    import { mount, shallow } from 'enzyme';
    import {expect} from 'chai';
    import sinon from 'sinon';
    import { connect } from 'react-redux'

    import { Login } from '../../js/react/components/login/LoginForm';

    describe('<Login />', function () {
      it('should have an input for the username', function () {
        const wrapper = shallow(<Login />);
        expect(wrapper.find('input[name=username]')).to.have.length(1);
      });

      it('should have an input for the password', function () {
        const wrapper = shallow(<Login />);
        expect(wrapper.find('input[name=password]')).to.have.length(1);
      });

      it('should have a button', function () {
        const wrapper = shallow(<Login />);
        expect(wrapper.find('button')).to.have.length(1);
      });

      it('simulates click events', () => {
        const onButtonClick = sinon.spy();
        const wrapper = shallow(
          <Login onButtonClick={onButtonClick} />
        );
        wrapper.find('button').simulate('click');
        expect(onButtonClick).to.have.property('callCount', 1);
      });

    });

非常感谢建议和答案:)

测试装饰组件

要直接测试组件,您需要仅导出组件本身的功能,而无需将其传递到 Connect。

目前,在您的测试中,您正在导入由 connect() 返回的包装器组件,而不是 LoginForm 组件本身。如果您想测试 LoginForm 组件与 Redux 的交互,这很好。如果您不是单独测试组件,那么这种导出和导入组件的方法就可以了。但是,您需要记住使用专门为此单元测试创​​建的组件将组件包装在测试中。现在让我们看一下我们正在测试连接的组件的这种情况,并解释为什么我们将它包装在我们的单元测试中。

react-redux

中Provider和Connect组件的关系

react-redux 库为我们提供了一个 Provider 组件。 Provider 的目的是允许其任何子组件在包装在 Connect 组件中时访问 Redux 存储。 Provider 和 Connect 之间的这种共生关系允许包装在 Connect 组件中的任何组件通过 React 的 context 功能访问 Redux 存储。

正在测试连接组件

还记得连接组件是包装在连接组件中的组件,这种包装使我们的组件能够访问 Redux 存储吗?出于这个原因,我们需要在我们的测试文件中创建一个模拟商店,因为我们需要一个商店来测试组件如何与之交互。

在测试中使用 Provider 为我们的连接组件提供存储

但是 Connect 不知道如何神奇地访问商店。它需要嵌套(包装)在一个组件中。 Provider 组件通过 React 的上下文 api.

连接到 Provider 来为我们的组件提供对 Store 的访问权限。

正如我们所见,Provider 接受了一个由 Redux store 组成的 prop:

ReactDOM.render(
      <Provider store={store}>
        <MyRootComponent />
      </Provider>,
      rootEl
    )

因此,要测试连接的组件,您需要使用 Provider 对其进行包装。请记住,Provider 将 Redux 存储对象作为 prop。对于我们的测试,我们可以使用 redux-mock-store 库,它可以帮助我们设置一个模拟商店,并为我们提供商店中的一些方法,以便在我们需要时跟踪已分派的操作。

import { Provider } from 'react-redux'
import { mount } from 'enzyme'
import chai, {expect} from 'chai'
import chaiEnzyme from 'chai-enzyme'
import configureMockStore from 'redux-mock-store'

chai.use(chaiEnzyme())

import ConnectedLoginForm, from '../app.js'
const mockStore = configureMockStore(middlewares)

describe.only('<LoginForm Component />', () => {
it('LoginForm should pass a given prop to its child component' () => {
    const store = mockStore(initialState)
    const wrapper = mount(
      <Provider store={store}>
        <ConnectedLoginForm />
      </Provider>
    )
    expect(wrapper.type()).to.equal('div')
  })
})

但有时您只想测试组件的呈现,而没有 Redux 存储。让我们看一下这个案例,我们想要单独测试未连接的 LoginForm 组件的呈现。

正在测试未连接的组件

因此,目前您正在测试通过使用 Connect 包装原始 LoginForm 组件创建的新组件。

为了在没有连接的情况下测试原始组件本身,为 LoginForm 组件创建一个 单独的第二个导出声明

import { connect } from 'react-redux'

// Use named export for unconnected component (for tests)
export class App extends Component { /* ... */ }

// Use default export for the connected component (for app)
export default connect(mapStateToProps)(App)

您现在可以在没有 Redux 存储的情况下测试组件的呈现。

正在测试中导入组件

记得这样导出组件的时候-

  • 默认导出用于导入连接的组件
  • 命名导出为 导入未连接的组件

现在在您的测试文件中导入未修饰的 LoginForm 组件,如下所示:

// Note the curly braces: grab the named export instead of default export
import { LoginForm } from './App'

或者同时导入未文档化和修饰(连接)的组件:

import ConnectedLoginForm, { LoginForm } from './App'

如果您想测试 LoginForm 组件如何与您的 Redux 商店交互,您必须

您没有导出登录组件。含义:

class LoginForm extends React.Component { ... -> export class LoginForm extends React.Component { ...