Redux: How to test a connected component?

18,881

Solution 1

This is an interesting question.

I usually do import both container and component to do the testing. For container testing I use, redux-mock-store. Component testing is for testing async functions. For instance in your case, login process is an async function using sinon stubs. Here is a snippet of the same,

import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { stub } from 'sinon';

const mockStore = configureMockStore([thunk]);

describe('Container Login', () => {
  let store;
  beforeEach(() => {
    store = mockStore({
      auth: {
        sport: 'BASKETBALL',
      },
    });
  });
  it('should render the container component', () => {
    const wrapper = mount(
      <Provider store={store}>
        <LoginContainer />
      </Provider>
    );

    expect(wrapper.find(LoginContainer).length).to.equal(1);
    const container = wrapper.find(LoginContainer);
    expect(container.find(Login).length).to.equal(1);
    expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
  });

  it('should perform login', () => {
    const loginStub = stub().withArgs({
      username: 'abcd',
      password: '1234',
    });
    const wrapper = mount(<Login
      loginUser={loginStub}
    />);
  wrapper.find('button').simulate('click');
  expect(loginStub.callCount).to.equal(1);
  });
});

Solution 2

As you pointed out, the way I usually do this is to export the un-connected component as well, and test that.

i.e.

export {Login};

Here's an example. Source of the component, and source of the tests.

For the wrapped component, I don't author tests for those because my mappings (mapStateToProps and mapDispatchToProps) are generally very simple. If I wanted to test a wrapped component, I'd really just be testing those maps. So those are what I would choose to explicitly test, rather than re-testing the entire component in a wrapped form.

There are two ways to test those functions. One way would be to export the functions within the module itself.

i.e.;

export {mapStateToProps, mapDispatchToProps}

I'm not a huge fan of this, because I wouldn't want other modules in the app to access them. In my tests, I sometimes use babel-plugin-rewire to access "in-scope" variables, so that's what I would do in this situation.

That might look something like:

import {
  Login, __Rewire__
}

const mapStateToProps = __Rewire__.__get__('mapStateToProps');

describe('mapStateToProps', () => { ... });
Share:
18,881

Related videos on Youtube

Umair Sarfraz
Author by

Umair Sarfraz

Updated on June 06, 2022

Comments

  • Umair Sarfraz
    Umair Sarfraz almost 2 years

    I am using Enzyme to unit test my React components. I understand that in order to test the raw unconnected component I'd have to just export it and test it (I've done that). I have managed to write a test for the connected component but I am really not sure if this's the right way and also what exactly would I want to test for the connected component.

    Container.jsx

    import {connect} from 'react-redux';
    import Login from './Login.jsx';
    import * as loginActions from './login.actions';
    
    const mapStateToProps = state => ({
      auth: state.auth
    });
    const mapDispatchToProps = dispatch => ({
      loginUser: credentials => dispatch(loginActions.loginUser(credentials))
    
    });
    export default connect(mapStateToProps, mapDispatchToProps)(Login);
    

    Container.test.js

    import React from 'react';
    import {Provider} from 'react-redux';
    import {mount, shallow} from 'enzyme';
    import {expect} from 'chai';
    import LoginContainer from '../../src/login/login.container';
    import Login from '../../src/login/Login';
    
    
    describe('Container Login', () => {
      it('should render the container component', () => {
        const storeFake = state => ({
          default: () => {
          },
          subscribe: () => {
          },
          dispatch: () => {
          },
          getState: () => ({ ...state })
        });
        const store = storeFake({
          auth: {
            sport: 'BASKETBALL'
          }
        });
    
        const wrapper = mount(
          <Provider store={store}>
            <LoginContainer />
          </Provider>
        );
    
        expect(wrapper.find(LoginContainer).length).to.equal(1);
        const container = wrapper.find(LoginContainer);
        expect(container.find(Login).length).to.equal(1);
        expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
      });
    });
    
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    I've done that. import Login from ../login is the un-connected component. The reason I haven't used {Login} is because they lie in separate files.
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    Also, what exactly do I need to test for the container component?
  • jamesplease
    jamesplease over 7 years
    Oh, I see what you mean. So far I haven't tested those tbqh. If I did test them, I would use something like babel-plugin-rewire and test mapStateToProps and mapDispatchToProps rather than the wrapped component itself.
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    mapStateToProps and mapDispatchToProps how? Can you provide an example please?
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    Also, have you done the integration testing? I've been able to perform that but I have some async actions that return functions. Any idea how I can test them? I can create a separate question for it.
  • jamesplease
    jamesplease over 7 years
    Updated the answer @umair. Let me know if that helps.
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    I wouldn't want to use babel-plugin-rewire tbh. And I am not really sure what you meant in the first option.
  • jamesplease
    jamesplease over 7 years
    Your mapping functions are just variables, so you can export them from the module. Then, you can import them in your test to access them. In the same way that you're doing export {Login} and then import {Login} in your test, you could do export {Login, mapStateToProps} and import {Login, mapStateToProps}.
  • Michael Parker
    Michael Parker over 7 years
    I personally have been following the pattern of exporting mapStateToProps, mapDispatchToProps, and mergeProps for the sake of testing them. I had no idea that babel-plugin-rewire was an option - I'll have to try this out some time.
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    Would that suffice the tests for mapStateToProps and mapDispatch?
  • anoop
    anoop over 7 years
    Yes indeed.. You must check with a coverage tool, u ll find it is fully covered
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    The actual thing I want to know is basically WHAT to test when we are going to test the container/smart component?
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    Have you done integration testing? I've been doing the unit testing but I've read around that we should write integration tests as well. If yes, I've got an important question that I can share the link
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    And by integration testing I mean triggering the action and asserting on the store update
  • anoop
    anoop over 7 years
    Not exactly I basically mount the component and simulate events and assert it.. But may be this can give u a better idea skillsmatter.com/skillscasts/…
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    Thanks a million. Have you worked with 'nock' for API calls?
  • anoop
    anoop over 7 years
    yes, but I prefer moxios and sinon.. one reason is i use axios for network calls
  • Umair Sarfraz
    Umair Sarfraz over 7 years
    I'm using axios as well. I've actually never heard about moxios. Is that any better?
  • Umair Sarfraz
    Umair Sarfraz over 7 years
  • Umair Sarfraz
    Umair Sarfraz over 7 years
  • Umair Sarfraz
    Umair Sarfraz over 7 years
  • PositiveGuy
    PositiveGuy about 7 years
    when I try to mount with provider, I get "Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined."
  • PositiveGuy
    PositiveGuy about 7 years
    can you show us your actual Container Component implementation?
  • anoop
    anoop about 7 years
    @PositiveGuy I think the component is not exported correctly