I’m using connected component to mean a React component connected to a Redux store via React-Redux bindings.

At the library’s heart is the easy separation of presentational (how things look) and container (how things work) components. A presentational component can usually be written as an elegantly simple functional component. A container component knows about the Redux store, and feeds the ‘dumb’ presentational component it renders any props - data and/or callbacks - it requires.

The React-Redux API makes this separation of concerns effortless; Write presentational components, and use the React-Redux connect() function to generate the container components that connect presentational components to the store. But we’re getting ahead of ourselves…

Before we can connect components, we must provide the application with a Redux store. The React-Redux <Provider> component wraps the application’s root component, and bestows magical access to the store on all nested components*:

// index.js

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

*React-Redux uses the React experimental context feature to give container components convenient access to the Redux store, more-or-less via a backdoor.

A Simple Connected Component To Test

Connected components can subscribe and/or dispatch to a Redux store. The following connected component dispatches to the store only:

// FooButton.js

const FooButton = ({ onFooButtonClick }) => (
  <button onClick={ onFooButtonClick }>
    Foo
  </button>
);
FooButton.propTypes = {
  onFooButtonClick: PropTypes.func.isRequired
};

const mapDispatchToProps = (dispatch) => ({
  onFooButtonClick: () => dispatch({type: 'FOO_BUTTON_CLICK'})
});

// Unwrapped (presentational) component
export const Unwrapped = FooButton;

// Connected default component with callback injected
// *null* in place of *mapStateToProps*, as no need to subscribe to store
export default connect(null, mapDispatchToProps)(FooButton);

Because connect() returns a new (connected) component, the unwrapped (presentational) component is still available, unmodified, to us. We’re exporting this as well as the default connected component, as it can be easier to test components when they’re free of the Redux dependency.

We’ll look at two unit tests. The second test will check the onClick handler. Passing the handler in via the props object makes it easy to get hold of in tests.

A Quick Note About Extracting Actions

It’s a common pattern in Redux to extract actions from components, and list action creators (pure functions that return an action object) together for easy reference, e.g.:

const pubFooButtonClick = () => ({
  type: 'FOO_BUTTON_CLICK'
});

Jest With Enzyme

I’m using Jest with Enzyme’s shallow rendering. Shallow rendering means a component’s tests still pass when its children fail / change. This isolation helpfully narrows the field for bug hunting.

Test #1: Snapshot Testing

Snapshot tests flag changes to a component. Perhaps you want the change, in which case, no problem, just update the snapshot. Otherwise, there’s something to fix.

Unlike test-driven development (TDD), the snapshot approach is code first and test second. The first time a snapshot test runs, it saves a representation of the tested component to a snapshot file. Later executions compare the current shape to the saved shape.

Connected components rely on the Redux store being there. For this reason, it’s easier (and so common) to snapshot test the unwrapped component, manually passing in the props it requires*:

// FooButton.test.js - Imports

import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import FooButton, { Unwrapped as UpwrappedFooButton } from './FooButton';

// FooButton.test.js - Snapshot test

it('should pass snapshot test', () => {
  const component = shallow(
    <UpwrappedFooButton onFooButtonClick={() => {}} />
  );
  const tree = toJson(component);
  expect( tree ).toMatchSnapshot();
});

*If we follow the standard practice of declaring PropTypes, we can be confident about the props components receive.

The Snapshot Generated

Snapshot tests can capture a lot, potentially replacing multiple case-by-case unit tests (e.g., case to check id, case to check classname(s), case to check children…). But there’s a noticeable hole in the picture:

exports[`test should pass snapshot test 1`] = `
<button
  onClick={[Function]}>
  Foo
</button>
`;

Because calling toString() on a function yields inconsistent results, snapshots record function placement but nothing more. Make what changes we like to the onClick handler (save deleting it), and the snapshot test will continue to heedlessly pass.

Test #2: Testing Functionality

Is this necessary? Maybe, maybe not. Is this trivial? Yes! Because we passed our handler in as a prop, it’s easy to get hold of. And while we now need to handle the Redux dependency (since we’re testing the connected component), this too can be plain sailing.

If we export a function that handles the store setup and returns an instance of the store, rather than export the store itself, it’s as easy as this*:

// FooButton.test.js - Second test (of functionality)
// **configStore()** returns instance of store

it('should dispatch action onClick', () => {
  const component = shallow(
    <FooButton store={configStore()} />
  );

  expect(
    component.props().onFooButtonClick()
  ).toEqual({type: 'FOO_BUTTON_CLICK'});
});

Note that we’ve manually passed the store instance to our component (outside of tests, you should use <Provider>).

*See Dan Abromov's egghead course video, Redux: Refactoring the Entry Point.

Conclusion

TDD is the development process that might’ve brought you up properly with attention to discipline, but the ease of snapshot testing might encourage a more comprehensive testing habit in more of us.

It takes a minimal amount of code to identify when a component changes, perhaps unexpectedly. There’s something to be said for waiting until a component is somewhat stable before writing a snapshot test for it. Otherwise mechanically updating snapshot files without proper scrutiny (to check changes are wanted) might become as inevitable as writing tests all at once, just before pushing to production.

Should you favour unit testing React components, traditional unit tests still add value; The second test checked the proper operation of an event handler.

Both tests were cheap - quick to implement and fast to run - and their simplicity should mean they won’t cause any headaches down the line.

(Connected) React components: (How) do you test yours?