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

Posted

技术标签:

【中文标题】如何对 React-Redux 连接组件进行单元测试?【英文标题】:How to Unit Test React-Redux Connected Components? 【发布时间】:2016-05-09 22:48:19 【问题描述】:

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

我点击此链接为 React-Redux 代码配置我的测试环境。

How to implement testing + code coverage on React with Karma, Babel, and Webpack

我可以成功测试我的 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 间谍切换到 chai 间谍。

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

【问题讨论】:

如何导出组件?您是使用命名导出还是仅使用导出默认值? import PhoneVerification from '../PhoneVerification'; 是您的违规行,当您这样做时,如果您没有进行命名导出,则会得到未定义。 我正在使用命名导出。 我也有类似的设置并且收到类似的错误消息。这方面有进展吗?谢谢。 能不能在这段代码sn-p中加js让代码高亮? 【参考方案1】:

一个更漂亮的方法是导出你的普通组件和包裹在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 装饰器做到这一点?【参考方案2】:

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

这是一个更简洁的解决方案,除了连接的组件之外不需要导出任何东西:

如果你使用的是 jest,你可以 mock connect 方法返回三个东西:

    mapStateToProps mapDispatchToProps 反应组件

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

1.使用内联模拟

在测试的 describe 函数之前添加以下 sn-p。

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

2。使用文件模拟

    在根目录(package.json 所在的位置)创建文件__mocks__/react-redux.js 在文件中添加以下sn-p。

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

模拟之后,您将能够使用Container.mapStateToPropsContainer.mapDispatchToPropsContainer.ReactComponent 访问上述所有三个。

容器可以通过简单的方式导入

import Container from '&lt;path&gt;/&lt;fileName&gt;.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. @ShailyAggarwal 你需要使用浅渲染器来避免太深reactjs.org/docs/shallow-renderer.html【参考方案3】:

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

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

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-aware expect 选项。在本例中不需要它。

【讨论】:

对不起@Tarjei Huse,但我回滚了你的编辑。 是 Redux 提供的用于挂钩到 React 的东西。您的编辑硬编码了它目前的工作方式。但是,如果 更改其实现,我不希望我的代码暴露。【参考方案4】:

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

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

创建假存储并挂载组件

你可以参考这篇文章Testing redux store connected React Components using Jest and Enzyme | TDD | REACT | REACT NATIVE

【讨论】:

【参考方案5】:

尝试创建 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 并为其提供必要的模拟道具来测试您的“哑”组件。没有测试点已经测试过(连接装饰器、mapStateToProps、mapDispatchToProps 等...)

【讨论】:

感谢您的解决方案。 嘿@george.cz,您将mapStateToProps 描述为“已经过测试”。你如何测试mapStateToPropsmapDispatchToProps?您是否将它们从容器中导出并为导出的函数创建测试? @skypecakes 我相信他的意思是connect itself。 @falsarella 哦,这很有道理。谢谢。

以上是关于如何对 React-Redux 连接组件进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

如何对第一个Vue.js组件进行单元测试 (上)

我可以等待我的组件连接到 react-redux 吗?

表示 react-redux 连接组件的 UML 图

使用 react-redux 连接功能时渲染组件上的打字稿错误

使用玩笑的反应单元测试失败

React-Redux实现组件间数据共享+项目打包