Simplifying React component testing
Tags: react, testing
Published: 2019-12-17 10:24:14

With the rollout of React hooks and newer libraries which claim to test your components "the way the user would use it", it's easier than ever to get overwhelmed with all the fragmented information about the best way to test your components.

> I want to try something different.

I'm not going to insist that you need to use a specific library, organise your tests in alphabetical order, and pray to the testing gods every night.

Instead, I want to demonstrate a way in which I've found testing components to be useful, simple and effective.

## What do components do?

Before we look at how to test a component, let's consider what a component is. For testing purposes, we can consider a component to have the following 2 core responsibilities.

### 1. Rendering

Whether it's a string, null, DOM elements, a component, or a collection of all these things, your component will need to return some renderable output.

```jsx
const MyComponent = ({ loading }) => (
  if (loading) {
    return <Spinner />;
  }
  
  return <MainContent />;
);
```

### 2. Reacting to events

Following the initial render, most components will also react to some kind event - DOM events, props changing, or maybe even an update to context.

This manifests in one of two results, either the component's render output changes, or a side-effect is triggered.

```jsx
// Trigger event
const handleClick = useCallback(() => fetch(url), []);

// Trigger change to render output
const handleClick = useCallback(() => setNewState(s => s+1), []);
```

## How do we test components?

Once you have a full grip on what a component's responsibilities are, knowing what to test becomes fairly trivial.

### 1. Check the initial render

Shallow render a component and snapshot the results.

While assertions make sense for more complex functionality (e.g. calculations or custom data formatting) this is usually the exception, not the rule. It takes discipline to get used to updating snapshots but the time savings are more than worth it.

```jsx
describe('on mount', () => {
  it('renders friends list', () => {
    expect(shallow(<FriendsList friends={friends} />)).toMatchSnapshot()
  });
 
  it('renders "no friends found"', () => {
    expect(shallow(<FriendsList />)).toMatchSnapshot()
  });
});
```

> Tip: Mix and match different props to snapshot all the different possible render outputs

### 2. Check for reactions to events

Once you're confident with your component's initial state, it's time to make sure that it's reacting to events as expected.

If the change triggers a side effect, make sure to test that. If the change alters the render output, consider a snapshot, or write an assertion if preferred.

```jsx
describe('on user click', () => {
  const props = {
    friends,
    onUserChange: jest.fn(),
  };
  
  it('calls on user change', () => {
     const wrapper = shallow(<FriendsList {…props} />);
     wrapper.find('FriendItem[value="Paul"]').simulate('click'); 
     expect(onUserChange).toBeCalledWith('Paul');
  });
});
```

## That's all there is to it!

Here are a few additional tips you might want to consider.

 * Too much complexity? You probably need to break down your component.
 * Too much visual complexity (e.g. drag-and-drop)? Save it for an E2E test.

### But what about testing end-user interactions?

It's incredibly important to test user interactions… but user interactions and component testing don't really go hand in hand.

> Component tests are for testing your code, not user interactions.

I'm yet to find a testing library that can catch user interaction issues such as visually hidden/unclickable elements.

If you're looking for a one-size-fits-all solution to testing and can swallow the productivity impact of extensive E2E/browser- testing, maybe consider going the browser-only route. For everyone else, I think it's a matter of finding a balance of both browser and component testing.


---

Hopefully, you found this interesting! If you have any thoughts or comments, feel free to drop them below or hit me up on twitter - @andyrichardsonn
Disclaimer: All opinions expressed in this article are my own.