So you have been using React now for some time, and you want to know what you have to test, and If you have seen one of Brian Holt’s videos on FrontEndMaster on Introduction React and Redux. In this video, Brian says that at Netflix they do not have unit tests for their React components because they change so often. The problem with this statement is a new guy, or an inexperienced team might take this out context to say that if Netflix does not write unit tests for their React components why should we. My problem with this is these people can use this as an excuse. Also, we have to look at the capabilities here, Brian Holt is a super talented developer, and at Netflix, He is surrounded by the best of the best, so for them, it works because they are operating on another level (rockstar level lol ??).
I come from Angular 1.5 where it was straightforward what needed to be unit tested. We wrote unit tests for controllers, components, services, directives and custom modules. The view was tested with e2e tests using protractor plus. With these you could get very impressive test coverage, one project I worked on we had 100% test code coverage. However, that was Angular 1.5 days, and we have since moved on from that to React. Like I had with Angular 1.5, I want all my React applications to have excellent test coverage.
With all the React applications I have built thus far I have focused a lot on testing, and within each app, I have been tinkering with different approaches. Moreover, I think I have found a good approach which works well for most applications including enterprise.
3 Step RCV (Redux, Container, View) Testing Approach
To demonstrate this RCV testing approach, I will be using my react-tunes-player repo. Also, for Redux I use the ducks modular pattern.
Step 1: Test Redux (RCV)
- Redux: Test all your reducers and actions. In fact, my suggestion would be to use TDD (Test Driven Development) for this. Testing your reducers and actions has nothing to do with React, at this stage. If you are coming from Angular 1.5, you can think of this as testing some custom modules which had nothing to with Angular.
- Note: If you are building a React application my suggestion is always to use Redux for State management. React + Redux are made for each other.
Testing an action & reducer
Note: This test below is making sure that when the setTunes() action is dispatched to the reducer, the expected state should have the tunes array set with the tunes received from setTunes().
View on GitHub: react-tunes-player-reducer.spec.js
//file-name: react-tunes-player-reducer.spec.js describe("set tunes", () => { it("should return state with tunes set when setTunes action is dispatched", function() { const action = setTunes(tunes()); const actual = ReactTunesPlayerReducer(stateBefore(), action); const expected = { ...stateBefore(), tunes: [...tunes()] }; expect(actual).toEqual(expected); }); });
Step 2: Test the Container (RCV)
- Test your container. You should still use TDD (Test Driven Development) for this. Here you are testing your connected component, basically testing the glue between Redux and Your React Component. The key functions are mapStateToProps and mapDispatchToProps.
Testing mapStateToProps
Note: These tests below make sure that we have mapped state with the props we want to pass down into our view.
View on GitHub: react-tunes-player-container.spec.js
//file-name: react-tunes-player-container.spec.js it("should mapStateToProp tunes to _tunes", function() { expect(wrapper.node.props._tunes).toEqual( initialState.reactTunesPlayerReducer.tunes ); }); it("should mapStateToProp current to _current", function() { expect(wrapper.node.props._current).toEqual( initialState.reactTunesPlayerReducer.current ); }); it("should mapStateToProp player to _player", function() { expect(wrapper.node.props._player).toEqual( initialState.reactTunesPlayerReducer.player ); });
Testing mapDispatchToProps
Note: These tests below make sure that we have mapped our actions to the props we want to pass down into our view.
View on GitHub: react-tunes-player-container.spec.js
//file-name: react-tunes-player-container.spec.js it("should mapDispatchToProps setTunes", function() { expect(wrapper.node.props.setNextTune()).toEqual(setNextTune()); }); it("should mapDispatchToProps setPreviousTune", function() { expect(wrapper.node.props.setPreviousTune()).toEqual(setPreviousTune()); }); it("should mapDispatchToProps setTunes", function() { expect(wrapper.node.props.setTunes()).toEqual(setTunes()); }); it("should mapDispatchToProps setCurrentTune", function() { expect(wrapper.node.props.setCurrentTune()).toEqual(setCurrentTune()); }); it("should mapDispatchToProps playCurrentTune", function() { expect(wrapper.node.props.playCurrentTune()).toEqual(playCurrentTune()); }); it("should mapDispatchToProps pauseCurrentTune", function() { expect(wrapper.node.props.pauseCurrentTune()).toEqual(pauseCurrentTune()); });
Step 3: Testing the View (RCV)
Test your view. This is the controversial part which guys like Brian Holt and many others have said not to waste your time testing this is because the view always changes. My suggestion is the following:
- Newbie – New to React: Test everything in your view
- Inexperienced Team – New to React: Test everything in your view
- Everyone else: Depending what your component does:
- Test all component methods like componentDidMount, componentWillReceiveProps and etc..
- If your component renders differently according to state, test those different render states
Testing different render states, plus componentDidMount and componentWillReceiveProps
View on GitHub: react-tunes-player-view.spec.js
//file-name: react-tunes-player-view.spec.js describe("when not rendered with tunes", () => { beforeEach(function() { wrapper = shallow(); }); it("should render React Tune Player View with warning message displayed", function() { expect(wrapper.contains("Warning! No tunes loaded in player.")).toBe( true ); }); describe("componentDidMount", () => { it("should NOT add event listener when tunes props is supplied", function() { spyOn( ReactTunesPlayerView.prototype, "componentDidMount" ).and.callThrough(); wrapper = mount(); expect( ReactTunesPlayerView.prototype.componentDidMount ).toHaveBeenCalledTimes(1); expect(wrapper.find(".warning-wrapper").length).toEqual(1); }); }); describe("componentWillReceiveProps", () => { it("should NOT call playCurrentTune when isPlaying is true", function() { wrapper = mount( ); wrapper .instance() .componentWillReceiveProps({ _player: { isPlaying: true } }); expect(playCurrentTuneMockFunc).toHaveBeenCalledTimes(0); }); it("should NOT call pauseCurrentTuneMockFunc when isPlaying is false", function() { wrapper = mount( ); wrapper .instance() .componentWillReceiveProps({ _player: { isPlaying: false } }); expect(pauseCurrentTuneMockFunc).toHaveBeenCalledTimes(0); }); }); });
So if you are a Newbie or Inexperienced Team just test everything you will learn a lot about react, redux, enzyme, jest, shallow rendering and jsdom. However if you are NOT going to write tests for your view, the minimum you may check is if the React component still renders after making changes, and this can be done with enzyme using shallow rendering.
Minimum view tests – check if component renders without crashing
import React from 'react'; import { shallow } from 'enzyme'; import App from './App'; it('renders without crashing', () => { shallow(<App />); });
Just like you, I am also always learning and getting better, so the above is my take at this moment in time. I will continue to tweak, experiment and improve. If there’s anything that you come across or a better method, please will you kindly share it? I am open to improvements.
@Brian Holt, thanks man for everything, it is because of you that I know React and Redux, thank you 🙂
Also shout-out to FrontEndMaster,
Also shout-out Anthony Accomazzo, Ari Lerner, Clay Allsopp, David Guttman, Tyler McGinnis, and Nate Murray these are guys that wrote Full Stack React.
Also shout-out to the creators of these excellent tools we use react, redux, enzyme, jest, create-react-app and many more.
Also shout-out to the guys that have written articles on testing React:
- Jack Franklin – https://www.sitepoint.com/test-react-components-jest/
- Max Stoiber – https://academy.plot.ly/react/6-testing/
- Dan Manges – https://blog.joinroot.com/mounting-react-native-components-with-enzyme-and-jsdom/
- Sherry Hsu – https://medium.com/@sherryhsu/jest-and-enzyme-a85828a40c03
- Gethyl George Kurian – https://medium.com/netscape/testing-a-react-redux-app-using-jest-and-enzyme-b349324803a9
- Artem Sapegin – https://hackernoon.com/testing-react-components-with-jest-and-enzyme-41d592c174f
- Alonso Ayala Ortega – https://www.toptal.com/react/tdd-react-unit-testing-enzyme-jest