Some Thoughts On Testing React/Redux Applications

On Component and Reducer Tests, Asynchronous Actions and Generative Testing

A. Sharif
A. Sharif
Sep 4, 2016 · 6 min read

TL;DR:

This is how I currently approach testing React/Redux Applications.
  • Minimal component tests: verify that the component actually renders.
  • Avoid testing implementation details.
  • Verify important callbacks and other props, but keep it minimal.
  • The need to test logic inside a component might signal the need for refactoring.
  • Using Eslint, PropTypes and Flow will add value.
  • Test reducers as they are functions. Same data in. Same data out.
  • Action Creators: only indirectly, when testing reducers.
  • Async Action Creators: yes fully tested.
  • React-Redux: focus on selectors when complex logic is involved.
  • Connected components: might be useful as a type of integration test, not a prime focus.
  • Fully test any services, common and utility functions. Should all be functions. Making testing simple as same data in, same data out.
  • Advanced Testing: Use generative or property-based testing when applicable, catch edge-cases. Reducers are predestined for property-based testing.
  • E2E Tests for the critical parts.

Introduction

Tooling

Regarding components, Enzyme is what appears to be the de-facto standard for testing real-world applications. The other option is Jest, that comes with snapshot-testing and improved auto-mock handling. Finally you can also use the low-level React Test Utilities. There is no general rule or recommendation regarding the one or the other. This is up to the developer or team to decide what suits best for a specific project.

Create-React-App now comes with a Jest test setup out of the box. This might be a good entry point if you still need to figure out how to get things off the ground.

In case you need a quick-start for a Mocha/Chai/Sinon/Enzyme setup, then checkout this gist.

For an example on how to setup your tests with Karma/Jasmine/Enzyme read this.

The following component test examples have been written using Enzyme.

Components

A very minimal approach would be to only test if the component did render. Considering how inconsistent components can be, this makes total sense. Changing the class name will break the test, if we start verifying class names for example. Taking into account how quickly component internals change, it’s clear that taking this route doesn’t make much sense*.

(*Jest seems to tackle the problem with snapshot-testing if I’m not mistaken. If anyone can give insights on this, please leave a comment.)

This is how the most minimal but very effective component test would look like a.k.a ”The only React.js component test you’ll ever need”

The only React.js component test you’ll ever need (Enzyme + Chai) https://gist.github.com/thevangelist/e2002bc6b9834def92d46e4d92f15874

Additionally we might also verify important callbacks being triggered via sinon or similar libraries.

it('toggles the completed status of an item when clicking', () => {
const app = mount(<Root />);
const item = app.find('#item-1');
item.simulate('click');
expect(app.find('.completed').length).toBe(1);
item.simulate('click');
expect(app.find('.completed').length).toBe(0);
});

Also testing a certain number of items being rendered when passing in certain props is a valid approach.

it('renders items', () => {        
const props = {items: { id: 1', title: 'foo'}}
const list = shallow(<Release {...props} />);
expect(list.find('.item').length).toBe(1);
});

What we definitely want to do, is avoid testing React itself. A test like the following adds no value, it just confirms that React knows how to handle jsx.

const Header = ({title}) => <h1>{title}</h1>
it('filters out any out of print items', () => {
const headerTitle = shallow(<Header title="foo" />);
expect(headerTitle.find('h1').length).toBe(1);
});

Neglect testing any implementation details like tags, elements or attributes for the sake of testing if they’re being rendered. These details tend to change very quickly and the tests add no real value.

Bonus: If you’re still wondering about how to test any logic inside the component, the best answer is to not have to test any logic in the first place. Components should simply render a given input, logic should remain outside the component whenever possible.

Summary:

  • Minimal component test to verify that the component actually renders.
  • Avoid verifying tags or class names.
  • Verify important callbacks or props, but keep it minimal.
  • The need to test logic inside a component might signal the need for refactoring.

Additional: Using Eslint, PropTypes and Flow will add more value than simply trying to verify any internal details.

For more in-depth on the topic also read Testing in React: Getting Off The Ground.

Testing Redux

Testing reducers should be straight forward. Same data in, same data out. Another thing to consider is to use action creators when creating the action for the reducer under test. By taking this approach we don’t need to explicitly test any action creators, as this might involve some overhead without real benefits.

it('should handle ADD_TODO', () => {
expect(
todos([], addTodo('Test AddTodo') // use the action creator
).toEqual([
{
text: 'Test AddTodo',
completed: false,
id: 0
}
])
})

Bonus: Using generative tests to verify reducers. Read this for or a more detailed writeup.

Action Creator

See reducers. No explicit action creator tests. See the following tests, we’re rewriting the action creator to be able to test it.

it('addTodo should create ADD_TODO action', () => {
expect(addTodo('Test addTodo')).toEqual({
type: types.ADD_TODO,
text: 'Test addTodo'
})
})

Even a better example.

const completeAll = () => ({ type: types.COMPLETE_ALL })expect(completeAll()).toEqual({
type: types.COMPLETE_ALL
})

By creating the actions via action creators when testing reducers, we’re already verifying that the action creators work as expected.

Async Action Creators with Redux-Thunk

Testing includes mocking the store or mocking a specific call. There are a couple of possible approaches, but best is to consult the redux documentation.

The following example is taken straight from redux “Writing Tests” section.

it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => {
nock('http://example.com/')
.get('/todos')
.reply(200, { body: { todos: ['do something'] }})

const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS,
body: { todos: ['do something'] }
}]
const store = mockStore({ todos: [] })

return store.dispatch(actions.fetchTodos())
.then(() => { // return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})

Yes, asynchronous action creators based on redux-thunk for example should be tested when possible.

Async Action Creators with Redux-Saga

Testing asynchronous actions with redux-saga is as simple as calling next on the generated object. Take a look at the shopping-cart test example:

test('getProducts Saga test', function (t) {
const generator = getAllProducts(getState)
let next = generator.next(actions.getAllProducts())

t.deepEqual(next.value, call(api.getProducts),
"must yield api.getProducts"
)

next = generator.next(products)

t.deepEqual(next.value, put(actions.receiveProducts(products)),
"must yield actions.receiveProducts(products)"
)
t.end()
})

We don’t have to deal with mocking api calls and other asynchronous actions, as we only verify if the returned action is the expected one. This also enables us to verify that the actions are returned in the correct order.

Testing React-Redux

In regards to the connect method, mapStateToProps might be interesting to test, especially when defined as a selector. This depends on the fact if logic is involved when selecting the state and might make a lot of sense in given situations.

Like mentioned above, I would like to hear feedback on how you approach testing the react-redux specific parts.

Services and Utility Functions

Advanced: Property-based Testing

If you’re interested in understanding how to approach generative testing with JavaScript in general and redux in specific you might want to read this and this.

E2E Tests

Outro

In case you have any questions or feedback leave a comment here or leave feedback via twitter.

JavaScript Inside

All things JavaScript.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store