Welcome to Part 13 of this comprehensive review and summary of Cory House’s Pluralsight course Building Applications with React and Redux in ES6.
Cory is a Microsoft MVP in C#, founder of OutlierDeveloper.com, avid tech reader, and speaker.
He believes in clean code, pragmatic development, and responsive native UIs.
Also in this series:
Redux was designed with testability in mind, but as we’ll see in this module, it’s a little more challenging than just testing basic React presentation components. Once you’ve seen these challenges surmounted, you’ll be able to do the same yoursel, and will be able to write your Redux tests with confidence.
Testing Connected Components
Cory puts it simply: we have two objectives here:
- Test our component’s markup
- Test the component behavior
As we’ve already seen examples of testing markup in Part 12 – Testing React, we focus on testing behavior now.
Cory explains that all container components are wrapped in a call to connect, and in this clip he describes two ways to handle this:
We can create a custom store for our test by wrapping our component with a Provider.
If you only want to test the local rendering and state related behavior, just add a named export for an unconnected component.
The demonstration begins at (1:45) and we begin with ManageCoursePage.test.js, adding the necessary imports, and writing the spec
it(‘sets error message when trying to save empty title’,
Again we’re using enzyme to simplify our tests, and we see how to use mount. Our first roadblock is the error:
Invariant Violation: Could not find “store” in either the context or props of “Connect(ManageCoursePage)”. Either wrap the root component in a <Provider>, or explicitly pass “store” as a prop to “Connect(ManageCoursePage)”.
This error is helpful and tells us that we can either wrap our component in a Provider component, or export the raw component. If you want to test Redux related code such as mapStateToProps, use the former as the solution.
Cory prefers the second option, and demonstrates exporting the ManageCoursePage class and updating the import in ManageCoursePage.test.js. Unfortunately we still get an error:
TypeError: Cannot create property ‘map’ of undefined
We see that in SelectInput.js we are not exporting the information that we need in our test, and how we can easily fix this. But a minute later we get a similar error:
TypeError: Cannot create property ‘saveCourse’ of undefined
To solve this, Cory defines all props in an object in the test, and passes that property into the component using the ES6 spread operator.
We also stub our saveCourse action to return merely a resolved promise: effectively a no-op.
Cory could have made this look much simpler and easier by just pasting this code all in from the beginning, but here we see a realistic test troubleshooting process, which I think is far more valuable.
The test passing now but we haven’t written our assertion yet. Once added, we’re back to failing again. But this is because we haven’t written this production code yet. So we’re at the beginning of our Test Driven Development experience in React and Redux.
To get this test passing, Cory creates a new function, courseFormIsValid() in ManageCoursePage.js
We also see this running up in the browser successfully.
So we’ve tested our connect components but not yet tested the Redux related pieces. mapStateToProps is a function that can potentially get complex, and we should not forget to also test this.
Our ManageCoursesPage.js is now huge. Cory extracts the function authorsFormattedForDropdown into a new file selectors.js. The idea is any functions that select data are selectors.
We create selectors.test.js to test
it(‘should return author data formatted for use in a dropdown’
Testing Action Creators
We begin with courseActions.test.js:
it(‘should create a CREATE_COURSE_SUCCESS action’
“One feedback I hear particularly often is that people who never wrote unit tests for front-end apps started writing them because it is just so easy to test reducers.”
– Dan Abramov
All we need to do is say given this input, assert this output.
Cory introduces us to Conor Hastings’ Redux Test Recorder. Because it’s currently experimental we don’t use it in this course, but it is a great idea and in my opinion a project well worth supporting.
Onto the demo, and we are testing our courseReducer.js with 2 tests:
it(‘should add course when passed CREATE_COURSE_SUCCESS’
it(‘should update course when passed UPDATE_COURSE_SUCCESS’
We mock two things:
In courseActions.test.js, we add imports for both of these as well as thunk from ‘redux-thunk’.
We create our mockStore with configureMockStore, passing in our thunk middleware as an argument to configureMockStore.
This test doesn’t have the same high signal to noise ratio that our reducer test had. It quite a lot of code, which Cory explains in this clip.
Testing the Store
Cory explains that we’re writing integration tests rather than unit tests, and why this is so.
it(‘Should handle creating courses’
Here we’re testing the result that we get from the Store equals our expected value.
And that covers all of the different elements of a Redux app that you’ll want to test.
The final module of the course is on setting your production build.