Redux: Use enzyme in tests

Created on 6 Mar 2016  Â·  31Comments  Â·  Source: reduxjs/redux

We should start using enzyme in component tests. It is great and the community seems to have converged on it. We should migrate all our existing tests from ReactTestUtils to enzyme, and use it for any new component tests.

I don’t plan to work on this, but if you’d like, please leave a comment stating your intention, and then send a PR when you have something to show so we keep a conversation. If you feel you don’t have the time, don’t worry—just let us know so another person can pick up the task.

Help wanted :wink:

examples help wanted

Most helpful comment

Hi @duro , here is what I did if I want to use enzyme - mount into my test

import jsdom from 'jsdom';
global.document = jsdom.jsdom('<div id="container"/>');
global.window   = document.defaultView;
global.navigator= window.navigator;

// these imports place it after jsdom: https://github.com/airbnb/enzyme/issues/58
import React from 'react';
import test from 'tape';
import { mount } from 'enzyme';

test('<Sample/>', t => {
    var Sample = React.createClass({
        displayName: 'Sample',
        render: function() {
            return <div>
                    <h1 className='cblue'>My Component</h1>
                </div>;
        }
    });
    var wrapper= mount(<Sample/>);
    var $ = wrapper.find('.cblue');
    $.simulate('click');
    t.equal( /*..test uquality..*/ );
    t.end();
});

All 31 comments

I'll take it.

I've been working on updating the todomvc example. That example had some existing integration tests. I haven't had the opportunity to try enzyme yet. I can work on converting those existing tests over to enzyme.

@fshowalter

Thanks, please do! It seems that counter example is the best candidate right now. We plan to completely replace todomvc so it might _not_ be worth trying there (and @mjw56 plans to take care of it anyway). Please send a PR after you’ve done with counter and we can figure out next steps.

@fshowalter

Thank you for the counter update! If you’d like, feel free to add enzyme tests to either of these examples:

They currently have no component tests at all.

Will do.

Hey I am interested in adding enzyme tests too to the projects with none. May be I'll start with the real world one?

@fshowalter @borisyankov Please work it out between yourselves :wink:

@borisyankov real world is all yours :wink:

@gaearon did you want to keep the tests under /test or colocate them with the source in a tests folder ala React?

I would keep them under /test so we don’t need to change all other projects. We can rearrange later if we desire so.

Sounds good.

On Sun, Mar 6, 2016 at 9:56 AM Dan Abramov [email protected] wrote:

I would keep them under /test so we don’t need to change all other
projects. We can rearrange later if we desire so.

—
Reply to this email directly or view it on GitHub
https://github.com/reactjs/redux/issues/1481#issuecomment-192902585.

Quick question here. Is there any codemod for transitioning from ReactTestUtils to enzyme? I thought it would be nice to have one to automate this procedure and benefits the community.

@tomchentw I don't think so, that would be cool though

I didn't see anyone mention todos, so I think I will work on adding those.

I would love to see how one tests a Container Component with Enzyme. What is the best way to test that actions are called on certain events, etc.

@duro you can use sinon.js to 'spy' on a mock store.

@borisyankov I can't seem to get the component to mount at all. Even using the <Provider /> component and giving it a fake store.

Here is the test I wrote:

import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router';
import createStore from 'redux/create';
import ApiClient from 'helpers/ApiClient';
import App from './App';
import sinon from 'sinon';

const client = new ApiClient();

describe('<App />', () => {

  it('renders component', () => {
    const store = createStore(browserHistory, client);
    const onLayoutChange = sinon.spy();
    const renderer = mount(
      <Provider store={store} key="provider">
        <App layoutChange={onLayoutChange} />
      </Provider>
    );

    console.log(renderer.html());
  });

});

That console.log outputs this: LOG: '<div><div></div></div>'

That definitely not what it should be outputting, and lets me test nothing about the component.

Here is the component I am trying to test:

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { layoutChange } from 'redux/modules/layout';
import styles from './App.less';
import MainNav from './components/MainNav';

@connect(
  state => ({
    layout: state.layout
  }),
  { layoutChange }
)
export default class App extends Component {

  static propTypes = {
    children: PropTypes.object,
    layoutChange: PropTypes.func.isRequired
  }

  componentDidMount() {
    this.calculateLayout();
    window.addEventListener('resize', this.calculateLayout.bind(this));
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.calculateLayout.bind(this));
  }

  calculateLayout() {
    const isIOS = /(iPad|iPhone|iPod)/g.test( window.navigator.userAgent );
    this.props.layoutChange({
      initialLoad: true,
      // navHeight: $('#main_nav').height(),
      notIOS: !isIOS,
      window: {
        width: window.innerWidth,
        height: window.innerHeight
      }
    });
  }

  render() {
    return (
      <div className={styles.container}>
        <MainNav />
        {this.props.children}
      </div>
    );
  }
}

I would suggest you test a component separately from a store.
This comes very naturally to React and Redux as most (or all) of your components will be pure functions - they get their input as parameters.

For example, you can take out the isIOS function in a separate module, and test only it.
You can also render your component and pass props manually (not from the real store)
And test the store totally separately from the component itself.

What you are trying to do, is close to systems or integration testing.
You might need jsdom or even karma, if you are trying to test overall system (for example window and api like localStorage are probably not available to you)

I am using Karma, and I would like some ability to do some more integration
level testing.

On Tue, Mar 8, 2016 at 2:21 AM, Boris Yankov [email protected]
wrote:

I would suggest you test a component separately from a store.
This comes very naturally to React and Redux as most (or all) of your
components will be pure functions - they get their input as parameters.

For example, you can take out the isIOS function in a separate module, and
test only it.
You can also render your component and pass props manually (not from the
real store)
And test the store totally separately from the component itself.

What you are trying to do, is close to systems or integration testing.
You might need jsdom or even karma, if you are trying to test overall
system (for example window and api like localStorage are probably not
available to you)

—
Reply to this email directly or view it on GitHub
https://github.com/reactjs/redux/issues/1481#issuecomment-193641255.

Hi @duro , here is what I did if I want to use enzyme - mount into my test

import jsdom from 'jsdom';
global.document = jsdom.jsdom('<div id="container"/>');
global.window   = document.defaultView;
global.navigator= window.navigator;

// these imports place it after jsdom: https://github.com/airbnb/enzyme/issues/58
import React from 'react';
import test from 'tape';
import { mount } from 'enzyme';

test('<Sample/>', t => {
    var Sample = React.createClass({
        displayName: 'Sample',
        render: function() {
            return <div>
                    <h1 className='cblue'>My Component</h1>
                </div>;
        }
    });
    var wrapper= mount(<Sample/>);
    var $ = wrapper.find('.cblue');
    $.simulate('click');
    t.equal( /*..test uquality..*/ );
    t.end();
});

@wharsojo this still doesn't get me there. I'm trying test react-redux connected containers.

@gaearon do you want tests for the async example too?

@fshowalter Yes!

@gaearon The root reducer in real-world example does export only the combineReducers output.
In my own code I usually export the other reducers too, so I can test them separately.
What do you think about me adding 'export' to the 'entities', 'pagination' and 'errorMessage' ?

@gaearon done in #1525.

@fshowalter @gaearon Why if I add expect(p.text()).toMatch(/^Clicked: 1 times/) to https://github.com/reactjs/redux/blob/master/examples/counter/test/components/Counter.spec.js#L32 fails?

1) Counter component first button should call onIncrement:
     Error: Expected 'Clicked: 0 times + - Increment if odd Increment async' to match /^Clicked: 1 times/
      at assert (node_modules/expect/lib/assert.js:22:9)
      at Expectation.toMatch (node_modules/expect/lib/Expectation.js:140:28)
      at Context.<anonymous> (Counter.spec.js:34:22)

onIncrement is not a real action creator—it’s just a spy. There is no store, so it keeps saying Clicked: 0 times (rather than 1 as you expect).

@gaearon Thanks :) How could I integrate the store? I think is a best test rather than check a function is called.

Currently i have been using the following code in test_helper(see pic) , for TDD Redux and react components. But if i want to switch to airbnb Enzyme would this file be not required? I'm new to TDD so any help will be greatly appreciated.
test_helper

are there any other examples that you were looking to have tests added for?

I have written tests for Real-World example. Will do a PR soon.
Not sure what else remains not covered.

Given that we've since switched over to Jest and it has first-class support for snapshots from react-test-renderer, I'm going to close this one out. I'll open up a new issue to track moving to react-test-renderer instead and to add some snapshot tests into the mix.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rui-ktei picture rui-ktei  Â·  3Comments

ilearnio picture ilearnio  Â·  3Comments

mickeyreiss-visor picture mickeyreiss-visor  Â·  3Comments

CellOcean picture CellOcean  Â·  3Comments

ramakay picture ramakay  Â·  3Comments