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

Posted

技术标签:

【中文标题】无法读取未定义单元测试酶的属性“contextTypes”【英文标题】:Cannot read property 'contextTypes' of undefined Unit Testing enzyme 【发布时间】:2017-06-16 12:38:06 【问题描述】:

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

无法读取未定义的属性“contextTypes” 我在单元测试中使用酶这是我的组件:

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

    );

非常感谢您的建议和答案:)

【问题讨论】:

已更改 >import Login from '../../js/react/components/login/LoginForm'; >从'../../js/react/components/login/LoginForm'导入登录;得到另一个错误>不变违规:在“Connect(LoginForm)”的上下文或道具中找不到“商店”。要么将根组件包装在 中,要么将“store”作为道具显式传递给“Connect(LoginForm)” 【参考方案1】:

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

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

【讨论】:

【参考方案2】:

测试装饰组件

要直接测试组件,您只需导出组件本身的函数而不将其传递给 Connect。

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

react-redux中Provider和Connect组件的关系

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

测试连接组件

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

在测试中为我们的 Connected 组件提供一个带有 Provider 的存储

但是,Connect 不知道如何神奇地访问商店。它需要嵌套(包装)在组件中。 Provider 组件通过 React 的 context api 挂钩到 Provider,从而使我们包装在 Connect 中的组件能够访问 Store。

正如我们所见,Provider 接受了一个由 Redux 存储组成的道具:

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

所以要测试连接的组件,您需要用 Provider 包装它。请记住 Provider 将 Redux 存储对象作为道具。对于我们的测试,我们可以使用 redux-mock-store 库,它可以帮助我们建立一个 mock store,并在 store 上为我们提供一些方法来跟踪哪些 action 已被调度(如果我们需要它们)。

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 存储交互,你必须

【讨论】:

以上是关于无法读取未定义单元测试酶的属性“contextTypes”的主要内容,如果未能解决你的问题,请参考以下文章

反应笑话单元测试用例TypeError:无法读取未定义的属性'then'

角度单元测试:TypeError:无法读取未定义的属性“根”

单元测试:TypeError:无法读取未定义的属性“管道”

无法读取未定义的属性“getters” - 带有笑话的 VueJS 单元测试

Karma 单元测试 EventEmitter 发射和订阅失败,无法读取未定义的属性“订阅”

使用业力和 webpack 4 运行单元测试时无法读取未定义的属性“externalModuleIndicator”