54
votes

I am using Mocha, Chai, Karma, Sinon, Webpack for Unit tests.

I followed this link to configure my testing environment for React-Redux Code.

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

I can successfully test my action and reducers javascript code, but when it comes to testing my components it always throw some error.

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': '[email protected]',
        '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();
    })
});

This following error comes up with above code.

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.

Tried switching from sinon spies to chai-spies.

How should I unit test my React-Redux Connected Components(Smart Components)?

5
How are you exporting your component? Are you using named exports or just the export default? import { PhoneVerification } from '../PhoneVerification'; is your offending line, when you do this you get undefined if you're not doing a named export.Henrik Andersson
I am using Named Export.Ayushya
I too have a similar setup and am getting a similar error message. Any headway on this? Thanks.FujiRoyale
Can you add js to this code snippet to get the code highlighting?cameronroe

5 Answers

54
votes

A prettier way to do this, is to export both your plain component, and the component wrapped in connect. The named export would be the component, the default is the wrapped component:

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

In this way you can import normally in your app, but when it comes to testing you can import your named export using import { Sample } from 'component'.

31
votes

The problem with the accepted answer is that we are exporting something unnecessarily just to be able to test it. And exporting a class just to test it is not a good idea in my opinion.

Here is a neater solution without the need of exporting anything but the connected component:

If you are using jest, you can mock connect method to return three things:

  1. mapStateToProps
  2. mapDispatchToProps
  3. ReactComponent

Doing so is pretty simple. There are 2 ways: Inline mocks or global mocks.

1. Using inline mock

Add the following snippet before the test's describe function.

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

2. Using file mock

  1. Create a file __mocks__/react-redux.js in the root (where package.json is located)
  2. Add the following snippet in the file.

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

After mocking, you would be able to access all the above three using Container.mapStateToProps,Container.mapDispatchToProps and Container.ReactComponent.

Container can be imported by simply doing

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

Hope it helps.

Note that if you use file mock. The mocked file will be used globally for all the test cases(unless you do jest.unmock('react-redux')) before the test case.

Edit: I have written a detailed blog explaining the above in detail:

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

16
votes

You can test your connected component and I think you should do so. You may want to test the unconnected component first, but I suggest that you will not have complete test coverage without also testing the connected component.

Below is an untested extract of what I do with Redux and Enzyme. The central idea is to use Provider to connect the state in test to the connected component in 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)

You may want to create a store with pure Redux. redux-mock-store is just a light-weight version of it meant for testing.

You may want to use react-addons-test-utils instead of airbnb's Enzyme.

I use airbnb's chai-enzyme to have React-aware expect options. It was not needed in this example.

3
votes

redux-mock-store is an awesome tool to test redux connected components in react

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

Create fake store and mount the component

You may refer to this article Testing redux store connected React Components using Jest and Enzyme | TDD | REACT | REACT NATIVE

enter image description here

1
votes

Try creating 2 files, one with component itself, being not aware of any store or anything (PhoneVerification-component.js). Then second one (PhoneVerification.js), which you will use in your application and which only returns the first component subscribed to store via connect function, something like

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

Then you can test your "dumb" component by requiring PhoneVerification-component.js in your test and providing it with necessary mocked props. There is no point of testing already tested (connect decorator, mapStateToProps, mapDispatchToProps etc...)