Testing state mutations with Enzyme.js and wrapper.update()
When testing React components, I often reach for Enzyme.js, a library of testing utilities for React components developed by the team at Airbnb. It’s become a standard tool among React developers. Recently, I was testing a component that used state to determine the className
given to a child component. Here’s the general idea in code:
import React from 'react';
export default class FormWithCollapsableRows extends Component {
constructor(props: Props) {
this.state = {
hidden: false,
};
}
toggleHidden() {
/**
* Toggles the state for form rows.
*/
this.setState(prevState => ({
hidden: !prevState.hidden,
}));
}
setFormRowClass(rowType: string) {
/**
* Sets the class names for each form row, based on the
* row type and whether `state.hidden` is true or false.
*/
const {
hidden,
} = this.state;
const partyRowClassNames = hidden ? ['hidden'] : [];
return partyRowClassNames.concat([rowType]).join(' ');
}
render() {
return <tbody className='expandable-table'>
<tr className={this.setFormRowClass('row-type-0')}>
<td>
Row 0 content goes here
</td>
</tr>
<tr className={this.setFormRowClass('row-type-1')}>
<td>
Row 1 content goes here
</td>
</tr>
</tbody>;
}
}
In the LESS style sheet, I have something like this going on:
.expandable-table {
tr {
.hidden {
visibility: collapse;
}
}
}
When the component’s has state.expanded === false
, the hidden
class is applied to the rows, hiding them with the style visibility: collapse
. I wanted to test whether the rendered component was correctly applying my class naming logic, so I wrote up a test like this:
import { shallow } from 'enzyme';
import test from 'tape';
import FormWithCollapsableRows from './FormWithCollapsableRows';
test('When `state.hidden === false`:', t => {
const wrapper = shallow(<FormWithCollapsableRows />);
// The component mounts with `state.hidden ==== false`,
// so the class names should not include "hidden" to
// begin with.
t.notOk(
wrapper.containsMatchingElement(
<tr className='hidden row-type-0' />),
'the rows should not be hidden');
t.end();
});
test('When `state.hidden === true`:', t => {
const wrapper = shallow(<FormWithCollapsableRows />);
// Once we toggle `state.hidden` to `true`, the class
// names should include "hidden"
wrapper.instance().toggleHidden();
t.ok(
wrapper.containsMatchingElement(
<tr className='hidden row-type-0' />),
'the rows should be hidden');
t.end();
});
However, I found that my second test case was failing. After I made the call to wrapper.instance().toggleHidden()
, I expected the class name to update according to my render function, which called this.setFormRowClass()
. Instead, it seemed that the wrapper I was testing had not received the state update.
It took some perusal of the Enzyme docs to find update()
, a method on shallow
wrappers that allows you to force a re-render on the component. This turned out to be exactly what I needed. I modified the second test case like this:
test('When `state.hidden === true`:', t => {
const wrapper = shallow(<FormWithCollapsableRows />);
// Once we toggle `state.hidden` to `true`, the class
// names should include "hidden"
wrapper.instance().toggleHidden();
// Triggers a re-render, so we know that `this.setFormRowClass`
// will be called:
wrapper.update();
t.ok(
wrapper.containsMatchingElement(
<tr className='hidden row-type-0' />),
'the rows should be hidden');
t.end();
});
Now the test passes, as expected.
Leave a Reply
Want to join the discussion?Feel free to contribute!