Lately I’ve been writing a lot of UI in React and testing a lot of that code using Enzyme (running on Karma with Chai assertions). It’s a great combination, but I don’t think we (the JavaScript community) have got the idiom down yet.
For example, I’ve been reading a lot of test cases that look like this:
// TeacherHomepageTest.js it('if there are no courses, RecentCourses component shows a SetUpMessage', () => { const wrapper = mount( <TeacherHomepage announcements={[]} courses={[]} sections={[]} codeOrgUrlPrefix="http://localhost:3000/" /> ); const recentCourses = wrapper.childAt(4); assert.equal(recentCourses.name(),'RecentCourses'); const coursesContentContainer = recentCourses.childAt(0); assert.equal(coursesContentContainer.name(), 'ContentContainer');] const coursesSetUpMessage = coursesContentContainer.childAt(5).childAt(0); assert.equal(coursesSetUpMessage.name(), 'SetUpMessage'); assert.equal(coursesSetUpMessage.props().type, 'courses'); assert.equal(coursesSetUpMessage.childAt(0).text(), 'Start learning'); assert.equal(coursesSetUpMessage.childAt(1).text(), 'Assign a course to your classroom or start your own course.'); assert.equal(coursesSetUpMessage.childAt(2).name(), 'Button'); assert.equal(coursesSetUpMessage.childAt(2).props().href, '/courses'); assert.equal(coursesSetUpMessage.childAt(2).props().text, 'Find a course'); });
That’s one of the simpler React tests in our repo, with a lot of assertions cut for brevity – I’ve seen upwards of 50 assertions in a test case trying pin the exact DOM output of a React component. Needless to say, these tests are not fun to write and they’re even worse to read. Let’s turn this into a readable unit test together!
Test where the logic lives
The first problem with the test above is that it’s not a unit test at all. The test is supposed to be checking the TeacherHomepage
component, but the logic being tested – whether the SetUpMessage
gets rendered – lives in the RecentCourses
component, so it should be tested there. As a first step, let’s change this into a test case for RecentCourses
.
// RecentCoursesTest.js it('shows a SetUpMessage when there are no courses', () => { const wrapper = mount( <RecentCourses courses={[]} heading="Recent Courses" showAllCoursesLink isTeacher /> ); const coursesContentContainer = recentCourses.childAt(0); assert.equal(coursesContentContainer.name(), 'ContentContainer');] const coursesSetUpMessage = coursesContentContainer.childAt(5).childAt(0); assert.equal(coursesSetUpMessage.name(), 'SetUpMessage'); assert.equal(coursesSetUpMessage.props().type, 'courses'); assert.equal(coursesSetUpMessage.childAt(0).text(), 'Start learning'); assert.equal(coursesSetUpMessage.childAt(1).text(), 'Assign a course to your classroom or start your own course.'); assert.equal(coursesSetUpMessage.childAt(2).name(), 'Button'); assert.equal(coursesSetUpMessage.childAt(2).props().href, '/courses'); assert.equal(coursesSetUpMessage.childAt(2).props().text, 'Find a course'); });
Test as shallow as possible
That takes care of the layers above the component under test, now how about the layers below? Right now our test is making several assertions about the behavior of SetUpMessage
but it would be better if the SetUpMessage
‘s own unit tests took care of that. Once we’ve extracted those tests, we can make fewer assertions here… and we can use Enzyme’s shallow()
rendering instead of mount()
.
// RecentCoursesTest.js it('shows a SetUpMessage when there are no courses', () => { const wrapper = shallow( <RecentCourses courses={[]} showAllCoursesLink isTeacher heading="Recent Courses" /> ); const coursesContentContainer = recentCourses.childAt(0); assert.equal(coursesContentContainer.name(), 'ContentContainer');] const coursesSetUpMessage = coursesContentContainer.childAt(5).childAt(0); assert.equal(coursesSetUpMessage.name(), 'SetUpMessage'); assert.equal(coursesSetUpMessage.props().type, 'courses'); assert.equal(coursesSetUpMessage.props().isTeacher, true); });
Extract default props
I find this makes a big difference for test readability. Your component will have several required props, but you want your unit tests to check one thing – why not highlight the props that matter? In our case the empty courses array is the pivotal factor in our test case. Let’s extract the other props to minimize distractions.
// RecentCoursesTest.js const defaultProps = { showAllCoursesLink: true, isTeacher: true, heading="Recent Courses", }; it('shows a SetUpMessage when there are no courses', () => { const wrapper = shallow( <RecentCourses {...defaultProps} courses={[]} /> ); const coursesContentContainer = recentCourses.childAt(0); assert.equal(coursesContentContainer.name(), 'ContentContainer');] const coursesSetUpMessage = coursesContentContainer.childAt(5).childAt(0); assert.equal(coursesSetUpMessage.name(), 'SetUpMessage'); assert.equal(coursesSetUpMessage.props().type, 'courses'); assert.equal(coursesSetUpMessage.props().isTeacher, true); });
Assert in JSX
It’s still awfully clunky to query our way down the tree of components and assert one property at a time. Wouldn’t it be nice if we could just use JSX to describe the expected output of the component?
Enzyme has a fairly powerful .containsMatchingElement(node)
method that lets you do exactly this. It’s not obvious from the documentation, but you can pass a fairly complex JSX structure to compare to your wrapper. All children must be present, but you can skip props you don’t care about for this test case (like the ContentContainer
‘s props here) and still get a match.
// RecentCoursesTest.js it('shows a SetUpMessage when there are no courses', () => { const wrapper = shallow( <RecentCourses {...defaultProps} courses={[]} /> ); assert(wrapper.containsMatchingElement( <div> <ContentContainer> <SetUpMessage type="courses" isTeacher /> </ContentContainer> </div> )); });
It’s that so much better? It even gets a little tigheter with chai-enzyme‘s containMatchingElement
assertion, and it provides nicer output when the assertion fails.
// RecentCoursesTest.js it('shows a SetUpMessage when there are no courses', () => { expect(shallow( <RecentCourses {...defaultProps} courses={[]} /> )).to.containMatchingElement( <div> <ContentContainer> <SetUpMessage type="courses" isTeacher /> </ContentContainer> </div> )); });
I’ve been looking for React+Enzyme best practices online, but for some reason I can’t find any guides describing tests like this. I’m not sure why – it’s so much nicer to write and read than what we started with. If you’ve got other ways to improve on this pattern, please let me know. Thanks!