Hot React Testing Practices II

Blaine Kasten
3 min readMar 28, 2017

--

Every idea starts off small and grows over time. It evolves, finds new arguments for and against itself, faces tests and challenges. If the idea is strong, if it’s correct, if it’s worth it, it just may survive. This post is an evolution of my original post Hot React Testing Practices.

That post talked through a few main points, primarily in separating your testing concerns between the UI and the UX. After doing this for a few months, I found myself still struggling with the stability and strength of my tests. Last week at ReactConf I was hanging out with my friend Dmitrii Abramov who works on Jest and other testing efforts at Facebook. Dmitrii talked about how at Facebook he encourages developers to keep business logic outside of their components. A lightbulb sparked in my mind as I realized this is exactly what my testing needs. My tests don’t need new frameworks or patterns, instead they need me to write my code differently! This blog effectively describes this idea from Dmitrii.

Let’s talk through an example so I can highlight what we are going for. One of the components my team is responsible for at Hudl is a toggle-able Sidebar component.

I’ve redacted away much of the code to focus on testing the updateWindowWidthState method. If we were to test this function based on my last blog post, we would create a file named __tests__/SidebarUX.test.js.

Then we would be trying to mock out the window sizes, the screen sizes, inspecting the state values of Sidebar. If you’ve tried this approach, you know that getting functions out of components and testing them can be quite difficult at times. This is probably what that test would look like:

That test, being amidst 10 other tests, quickly becomes confusing and difficult. We are tying logic tightly to React ergonomics. I see a few downsides to this approach:

  1. React components public API is bloated with internal methods.
  2. Files are growing in length and complexity unnecessarily
  3. Testing is an afterthought, not a forethought when designing components.

Now it’s time to look back at how we could implement this based on what Dmitrii and I talked about. But first, a quick side note. Have you ever heard of pure functions?

Pure Functions

A pure function is a function that given the same input will always give you the same output. To quote the co-creator of Haskell, this is his thoughts on pure functions.

When asked, “What are the advantages of writing in a language without side effects?,” Simon Peyton Jones, co-creator of Haskell, replied, “You only have to reason about values and not about state. If you give a function the same input, it’ll give you the same output, every time. This has implications for reasoning, for compiling, for parallelism.”

Here is an example of a pure function:

(x, y) => x + y

This function does not hold state in any side-way. This function executes operations on it’s arguments and returns a value. Now why are we talking about this? It relates back to the way we write our components. When declaring our business logic within our components, we never need to write pure functions. The components logic in general can take in any number of sources of arguments (state, window, flux store, etc), compute them, then usually result in calling setState.

As we’ve seen, the difficulty comes in testing when we need to mock out all these different sources, reach into internals of a component, and find ways to assert. Now back to what Dmitrii and I talked about at ReactConf.

Zero Business Logic within Components

That’s the idea. It’s simple. It does result in more files which has it’s downsides, but also has the upsides that each file is responsible for little bits of code, which should be easier to grok. So what would happen if we pulled out that function straight out into it’s own file and how would we test it?

In some ways this is already much better. We don’t have to reach into window.addEventListener, or create the component at all! But, the whole .call(component) part is really weird and we only tested one branch of this function. We’d have to do this same setup each test. This is where pure functions come in. Let’s rebuild this, including the component with pure functions.

We were now able to test every branch of when the sidebar should toggle with simple tests. This is a maintainable test and component. Use pure functions everywhere you can, keep your components light, and test on!

--

--