如何对 React-Redux 连接组件进行单元测试?

IT技术 unit-testing reactjs mocha.js sinon redux
2021-03-23 02:18:49

我正在使用 Mocha、Chai、Karma、Sinon、Webpack 进行单元测试。

我按照此链接为 React-Redux 代码配置了我的测试环境。

如何使用 Karma、Babel 和 Webpack 在 React 上实现测试 + 代码覆盖

我可以成功测试我的 action 和 reducers javascript 代码,但是在测试我的组件时,它总是会抛出一些错误。

import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils'; //I like using the Test Utils, but you can just use the DOM API instead.
import chai from 'chai';
// import sinon from 'sinon';
import spies from 'chai-spies';

chai.use(spies);

let should = chai.should()
  , expect = chai.expect;

import { PhoneVerification } from '../PhoneVerification';

let fakeStore = {
      'isFetching': false,
      'usernameSettings': {
        'errors': {},
        'username': 'sahil',
        'isEditable': false
      },
      'emailSettings': {
        'email': 'test@test.com',
        'isEmailVerified': false,
        'isEditable': false
      },
      'passwordSettings': {
        'errors': {},
        'password': 'showsomestarz',
        'isEditable': false
      },
      'phoneSettings': {
        'isEditable': false,
        'errors': {},
        'otp': null,
        'isOTPSent': false,
        'isOTPReSent': false,
        'isShowMissedCallNumber': false,
        'isShowMissedCallVerificationLink': false,
        'missedCallNumber': null,
        'timeLeftToVerify': null,
        '_verifiedNumber': null,
        'timers': [],
        'phone': '',
        'isPhoneVerified': false
      }
}

function setup () {
    console.log(PhoneVerification);
    // PhoneVerification.componentDidMount = chai.spy();
    let output = TestUtils.renderIntoDocument(<PhoneVerification {...fakeStore}/>);
    return {
        output
    }
}

describe('PhoneVerificationComponent', () => {
    it('should render properly', (done) => {
        const { output } = setup();
        expect(PhoneVerification.prototype.componentDidMount).to.have.been.called;
        done();
    })
});

上面的代码出现了以下错误。

FAILED TESTS:
  PhoneVerificationComponent
    ✖ should render properly
      Chrome 48.0.2564 (Mac OS X 10.11.3)
    Error: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.

尝试从 sinon spy 切换到 chai-spies。

我应该如何对 React-Redux 连接组件(智能组件)进行单元测试?

5个回答

一个更漂亮的方法是导出你的普通组件和包裹在 connect 中的组件。命名导出将是组件,默认是包装的组件:

export class Sample extends Component {

    render() {
        let { verification } = this.props;
        return (
            <h3>This is my awesome component.</h3>
        );
    }

}

const select = (state) => {
    return {
        verification: state.verification
    }
}

export default connect(select)(Sample);

通过这种方式,您可以在您的应用程序中正常导入,但是在测试时您可以使用import { Sample } from 'component'.

如何使用@connect 装饰器做到这一点?
2021-05-24 02:18:49

已接受答案的问题在于,我们不必要地导出某些东西,只是为了能够对其进行测试。在我看来,导出一个类只是为了测试它并不是一个好主意。

这是一个更简洁的解决方案,无需导出连接组件以外的任何内容:

如果你在使用 jest,你可以模拟connect方法来返回三件事:

  1. 映射状态到props
  2. mapDispatchToProps
  3. react组件

这样做非常简单。有两种方式:内联模拟或全局模拟。

1. 使用内联模拟

在测试的描述函数之前添加以下代码段。

jest.mock('react-redux', () => {
  return {
    connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({
      mapStateToProps,
      mapDispatchToProps,
      ReactComponent
    }),
    Provider: ({ children }) => children
  }
})

2. 使用文件模拟

  1. __mocks__/react-redux.js在根目录(package.json 所在的位置)创建一个文件
  2. 在文件中添加以下代码段。

module.exports = {
  connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({
    mapStateToProps,
    mapDispatchToProps,
    ReactComponent,
  }),
  Provider: ({children}) => children
};

模拟后,您将能够使用Container.mapStateToProps,Container.mapDispatchToProps访问上述所有三个Container.ReactComponent

容器可以通过简单地导入

import Container from '<path>/<fileName>.container.js'

希望能帮助到你。

请注意,如果您使用文件模拟。模拟文件将在所有测试用例中全局使用(除非您jest.unmock('react-redux'))在测试用例之前这样做

编辑:我写了一篇详细的博客,详细解释了上述内容:

http://rahulgaba.com/front-end/2018/10/19/unit-testing-redux-containers-the-better-way-using-jest.html

我试过你的解决方案,非常适合不进一步包含嵌套连接组件的连接组件。如果嵌套连接的组件,我会收到警告:React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
2021-06-09 02:18:49
@ShailyAggarwal 您需要使用浅层渲染器来避免深入reactjs.org/docs/shallow-renderer.html
2021-06-15 02:18:49

您可以测试连接的组件,我认为您应该这样做。您可能想先测试未连接的组件,但我建议您在不测试连接的组件的情况下不会有完整的测试覆盖率。

以下是我使用 Redux 和 Enzyme 所做的未经测试的摘录。中心思想是使用 Provider 将测试中的状态连接到测试中的连接组件。

import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import SongForm from '../SongForm'; // import the CONNECTED component

// Use the same middlewares you use with Redux's applyMiddleware
const mockStore = configureMockStore([ /* middlewares */ ]);
// Setup the entire state, not just the part Redux passes to the connected component.
const mockStoreInitialized = mockStore({ 
    songs: { 
        songsList: {
            songs: {
                songTags: { /* ... */ } 
            }
        }
    }
}); 

const nullFcn1 = () => null;
const nullFcn2 = () => null;
const nullFcn3 = () => null;

const wrapper = mount( // enzyme
        <Provider store={store}>
          <SongForm
            screen="add"
            disabled={false}
            handleFormSubmit={nullFcn1}
            handleModifySong={nullFcn2}
            handleDeleteSong={nullFcn3}
          />
        </Provider>
      );

const formPropsFromReduxForm = wrapper.find(SongForm).props(); // enzyme
expect(
        formPropsFromReduxForm
      ).to.be.deep.equal({
        screen: 'add',
        songTags: initialSongTags,
        disabled: false,
        handleFormSubmit: nullFcn1,
        handleModifySong: nullFcn2,
        handleDeleteSong: nullFcn3,
      });

===== ../SongForm.js

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

const SongForm = (/* object */ props) /* ReactNode */ => {
    /* ... */
    return (
        <form onSubmit={handleSubmit(handleFormSubmit)}>
            ....
        </form>

};

const mapStateToProps = (/* object */ state) /* object */ => ({
    songTags: state.songs.songTags
});
const mapDispatchToProps = () /* object..function */ => ({ /* ... */ });

export default connect(mapStateToProps, mapDispatchToProps)(SongForm)

你可能想用纯 Redux 创建一个商店。redux-mock-store 只是用于测试的轻量级版本。

您可能想使用 react-addons-test-utils 而不是 airbnb 的 Enzyme。

我使用 airbnb 的 chai-enzyme 来获得react感知的预期选项。在这个例子中不需要它。

抱歉@Tarjei Huse,但我回滚了您的编辑。<Provider> 是 Redux 提供的挂钩到 React 的东西。您的编辑硬编码了它目前的工作方式。但是,如果 <Provider> 更改其实现,我不希望我的代码暴露。
2021-06-17 02:18:49

redux-mock-store是一个很棒的工具,可以在 react 中测试 redux 连接的组件

const containerElement = shallow((<Provider store={store}><ContainerElement /></Provider>));

创建假商店并挂载组件

您可以参考这篇文章使用 Jest 和 Enzyme 测试 redux 存储连接的 React 组件 | TDD | react | react本机

在此处输入图片说明

尝试创建 2 个文件,一个带有组件本身,不知道任何商店或任何东西(PhoneVerification-component.js)。然后是第二个 (PhoneVerification.js),您将在您的应用程序中使用它,它只返回通过connect函数订阅存储的第一个组件,例如

import PhoneVerificationComponent from './PhoneVerification-component.js'
import {connect} from 'react-redux'
...
export default connect(mapStateToProps, mapDispatchToProps)(PhoneVerificationComponent)

然后,您可以通过PhoneVerification-component.js在测试中 require 并为其提供必要的模拟props来测试您的“哑巴”组件已经测试过没有任何测试点(连接装饰器、mapStateToProps、mapDispatchToProps 等...)

@skypecakes 我相信他的意思是他connect自己
2021-05-26 02:18:49
感谢您的解决方案。
2021-06-04 02:18:49
@falsarella Ohhhh 这是有道理的。谢谢。
2021-06-10 02:18:49
嘿@george.cz,你描述mapStateToProps为“已经测试过了”。你如何测试mapStateToPropsmapDispatchToProps您是否从容器中导出它们并为导出的函数创建测试?
2021-06-12 02:18:49