So far in my series of posts on React development for WordPress, I’ve covered React basics, create-react-app and testing with Jest. Jest is useful for testing the rendering of React components.
There are a few big buckets of functionality we have not looked at testing yet. Specifically how the internal state of stateful component changes, DOM events, and isolated testing of component class methods. Jest alone can’t do that.
Using Enzyme For DOM Testing
I hope it’s clear now how Jest, with the default test renderer, can do a lot of test coverage quickly. A snapshot covers one component, with all of its props. One limitation I mentioned was it doesn’t render the components to any type of DOM, so there is no way to trigger an event. Luckily Enzyme, which can be run by Jest lets us do exactly that — simulate DOM events. It also has cool features to check the props and state of a component or call methods of class directly.
Enzyme is not installed in create-react-app by default. To install Enzyme and the React adapter for Enzyme:
Now, to setup Enzyme in a test file, we import one or both of its renders — mount and shallow — and the React adapter. The React adapter must be added to the Enzyme instance for this scope. I don’t use any of its options. My test file headers for Enzyme tests look like this:
Shallow vs Deep Rendering React Components For Enzyme Testing
Enzyme has two renderers — mount and shallow. Shallow will shallow render the component which means it will not render any of the components children. That makes it faster than mount which performs a deep render and therefore will render a component and all of its children.
If the limitation of shallow is not a problem for a test, then use shallow, it’s faster. If you need to test children of a component, use mount, because that way it will work. For now, we will use shallow as we’re just testing one component. I’ll cover a few cases where mount is the better choice later.
Simulating Change Events With Enzyme
Rendering a component with Enzyme’s shallow is similar to what we did before with Jest’s react render. But the object that is returned is a lot more useful. We can use Enzyme’s find() method, with jQuery-like selectors to find elements in the rendered component. We can assert values based on what it finds or simulate events.
For our post editor, let’s find the input, change it and see if our change handler function behaved properly. Here’s the test, I’ll walk through it below:
At the top, I’m importing my component and the test tools. I created a test suite for all events of this component and one test. The mock post is the same as I used for snapshot tests. This actually goes in the same file. I’m cutting those tests out for clarity here.
I introduced the need for Enzyme by noting my snapshot tests used a change handler that did nothing. Now that those tests prove this component has its props set up properly for that change handler function prop, let’s add a test to prove it works. The benefit of dependency injection is here — we can test the update in isolation, in a component, when its rendered inside of another component. Each test adds more layers of coverage, without creating strong coupling.
This time, let’s create a change handler that updates a variable in the test’s scope. Then we can ensure it revived the right value. Earlier I said that I think a component like this should pass the whole updated post, not just the title when it changes.
So that is part of what we will test. I’m going to test the value of the updated object’s title.rendered property. My change handler captures that.
Let’s zoom into the change event though. React passes a synthetic DOM event to the callback handler. This is a great example of polymorphism in object-oriented programming. In React, we should never touch the DOM, we touch the virtual DOM abstraction. Therefore we have to interact with an abstraction of a JavaScript event that works the same way as a “real” JavaScript event. I use “real” in quotes because it is actually JavaScript’s abstraction over the web API that React is extending. Everything is an abstraction, nothing is real, we live in a simulation. As a result, we let React deal with cross-browser issues.
This also makes events easy to simulate:
We do not have to mock the whole event, just event.target.value. Manual mocking like this is limited and you may wish to use Sinon to make mocking more manageable.
Testing Loops With Enzyme
Earlier in this post, I created a component to list posts. They will be wrapped in a specific class. One way to test this is to find all of the elements with that class, and count the length of the results.
Note, I’m using the word “class” as in “CSS class” not as in “extends the React.Component class.”
In this test, I’m using Enzyme’s find method to search for elements with a class. Again, the syntax is jQuery-like. Also, note that I used mount instead of shallow? Why, shallow would not render the child components, which is what I am testing — how this component renders its children.
Class Components Own State
So far, we’ve looked at components that are unaware of their state. They take in props and communicate changes via functions passed in as props. But state does have to live somewhere. That’s what class components are for — they handle state and are aware of React lifecycle events.
Let’s put our Posts component in a container component, and use that component to manage state. Keep in mind, this is what react-redux does. Let’s understand the concept before offloading the concern to Facebook, which in general should be our strategy for managing the fact that there is more work to do than there are work hours in a day. It also feels like a good payback for all of the time that the Facebook stole from me before I uninstalled that addictive behavior from my phone.
What I would actually do is leave my main container dumb and wrap it in a higher-order component from Redux or WordPress that injects state, but that’s a different Torque post and also a video from the JavaScript for WP conference you can watch on YouTube.
Still, stateful components are useful when used sparingly. Let’s turn the component that create-react-app generate as “App” into a container for the post list. Because we started from the smallest part — the post — then worked up from there — post list and now the app the post list sits in — this should be pretty simple. All of the small details are already covered. We’re not starting with a big array of posts and designing multiple components at once. Instead, we’re waiting until we have all of the building blocks built before we assemble them.
Let’s start by adding state to the class constructor, with posts as an empty array.
My posts component expects an array that isn’t empty. What to do when there are no posts is not its concern. So in the Render method, I want to use a JSX conditional to only show the posts when that array is not empty:
Here is my whole component:
Testing State Of React Components With Enzyme
Before we talk about how to get a data into state, let’s make sure that once we do it will work correctly. One thing at a time. One good thing about enzyme is it can directly mutate state and also read state of a component.
Practically speaking, that means we can test if this component is going to work when posts are added to state without worrying about how to add posts to state. That’s a separate complication we will get to at once we’re ready.
What we need to test first is two things: does our loop how posts when state.posts is not empty and does it show nothing as well as not generate errors when state.posts is empty. Enzyme provides a setState method that allows us to call the class’ setState method in our tests. This class is the top-level of our program’s state management, so this functionality should be blackboxed here.
Let’s look at a test that adds posts to state and checks that they are rendered correctly:
This is very similar to the test I showed earlier for the Posts component. We’re just making sure it works properly in this context.
Calling React Class Methods With Enzyme
Let’s say we wanted to add the ability to edit one post with this app. We already have a PostEdit component. But we need to supply it with the right post. Let’s add a property to state to track the ID of the post currently being edited. I do not want to copy that post from state.posts, just its ID.
Finding the post in state is a separate concern, that gets its own method. Let’s look at the constructor and the new method:
Notice how in the constructor, I used the function bind to explicitly bind the constructor’s this to the method’s this. If I didn’t do that, this.state, this.setState and this.props would be undefined. This is an extra step you must take for every single method in a class that uses props or state.
Then I can use this and my PostEdit component in render:
To test this functionality — that the right post is found and the editor shows when the post is found, I will add a dew tests. Each one builds on the last. Our previous test covered post.state, so I can safely to do the same thing, then test the next step:
Once I trusted that worked — believe me, the test did not pass with my first version of this method — I can move on to testing that the editor shows when state dictates it should.
This test proves we get an editor when we should. It doesn’t prove that the editor does not show when it should not. So this test, by itself, could be a false positive. We need a test for the other possibility to prove it is not a false positive:
Testing React Change Handler’s Effect On State
Eventually, if I was to drag this series out for aanother 3-4 posts. I’d add a component to update which post is updated and wire it into this App component. That will require a change handler in the App components state. Let’s add that and test it so we can see an example of how to test state, after a component’s change handler is invoked.
Testing the change handler in isolation, before implementing the component to control the value means that the control is designed around the needs of the interface it sits in, not the other way around. Also, this control is swappable, as long as it works with the change handler, we don’t care what it is or what it changes to later.
Here is the simple change handler function, we can pass down to the control:
Here is the test, which calls that method directly then tests state using Enzyme’s state method
What About Loading Posts?
At some point, you need to actually add the posts. Again, I’d leave that to state management in anything complex. But, if you do want to encapsulate everything in this app, that’s why we need a class — to take advantage of React lifecycle events.
React lifecycle events are like WordPress actions — a way opportunity to run code at a specific place in the program’s execution. The earliest or component can safely do an AJAX-request for data and update state is componentDidMount. That’s the event that runs when the component is mounted to the DOM. Before that event ,we could not update state.
Again, I think that API requests should be handled by Redux, or another state management system. This approach strongly couples API interactions to the UI components, making them less reusable and means the API client can not be re-used in tests or without React.
Test-Driven React Development
I hope in this series you’ve taken away two things. The first is that by using test-driven development, we can make something simple, ensure it works, and then slowly add complexity. For me, this means developing one small unit of functionality at a time. That’s easier to think about, and easier to work on. I like that.
The other big takeaway here is not to over-use React class components. Using stateless components as much as possible reduces complication and increases reusability and makes functional components easier to test than class components.
Most importantly I hope you’ve seen the value of separating concerns and how React helps follow this practice that the benefit of this software design ideal and React are more clear. In the next post I’ll cover sharing React components between Gutenberg and React.
No Comments