The Enzyme/Jest survival guide: React testing 101

Enzyme/Jest: the survival guide

So you've got a shiny, new React application up and running? Great. But have you tested it?

A couple of months ago my team and I* started working on new React (with Redux) applications and we debated, briefly, between using the Jest/Enzyme framework or the Ava framework. We decided on the former largely due to community momentum (to say nothing negative about Ava). There exists a large amount of documentation for both Enzyme and Jest, but I found the useful examples lacking. This article is my attempt to remedy that with examples for commonly-needed test scenarios:

  • Snapshots
  • Clicking
  • Mocking component functions
  • Mocking external functions
  • Passing props
  • Mock redux stores

I'll start at easy scenarios and move towards the more complicated, with each main strategy relegated to its own section. By giving a scenario, how we tested it, and an explanation, hopefully you can apply it to your project. If you know of a better, more straightforward way, please leave a comment and drop some knowledge. Enjoy!

Quick note: this article is by no means intended to be a deep-dive into Enzyme or Jest, but a simple "get started testing" overview for common testing scenarios

Snapshots

These are a really easy way to at least get something tested. Roughly, a snapshot test renders the markup for a given component, stores a file (commit these), and then checks subsequent test runs against the previous snapshot. If something changes it'll alert you and you can see if it was expected or not. Basically, it's the canary in the coal mine of testing.

Scenario

// ... more react stuff above
render () {
  return (
    <App />
  )
}

/* say this would render as:
  <div>
    <h1>Hello, World!</h1>
  </div>
*/

How to test

  it('checks the snapshot', () => {
    const renderedComponent = shallow(component)
    expect(renderedComponent).toMatchSnapshot()
  })

Explanation

This one is straightforward. shallow comes from Enzyme and without going into too much detail it renders the topmost component (in this case App) but none of its children. All we have to do is render that component (in this case, App) and see if it matches the stored snapshot which is stored automatically in the __snapshots__ directory. If one doesn't exist, it will be created for you when you run the tests.

Clicking simple things

It's likely that you'll need to click something in order to change some state, transition to another page or whatever so this is crucial to know how to do. In this scenario we're testing a hypothetical button that updates a quantity on our component's props via a component-level function incrementQuantity. Let's say the prop in question is quantity and the button is a simple <div> with an onClick handler attached and it has a unique identifier. In this case we'll have that identifier be a CSS class called increment-button.

Scenario

// Say you have a button "Increment" that you click to update the quantity in your shopping cart
class ShoppingCart extends Component {
  //...

  incrementQuantity = () => {
    this.props.quantity++
  }

  render () {
    return (
      <div onClick={this.incrementQuantity} className='increment-button'>
        // styling, sub-attrs, etc...
      </div>
    )
  }
}

How to test

it('clicking Increment updates the quantity prop', () => {
  const renderedComponent = mount(<ShoppingCart />)

  const componentInstance = renderedComponent.find(ShoppingCart).first()
  renderedComponent.find('.increment-button').first().simulate('click')

  const actualPropValue = componentInstance.props.quantity
  const expectedPropValue = 6

  expect(expectedPropValue).toEqual(actualPropValue)
})

Explanation

Here we use mount which calls more component lifecycle methods than shallow does. It also does a full-child render, which isn't necessary here but is worth noting. After mounting the ShoppingCart component we get the component instance off of the mounted object, and we also look for the attribute with the increment-button class. In this example our div is the only one with that attribute so we can safely grab the first occurence. Your scenario may be different and you can either add a data-test-id=some-unique-id to the attribute or you can look for the nth() occurence. It's your call, both work well, but the former is more involved.

After locating the element we're interested in we simlulate a click event on it. The actualPropValue var is a value taken directly from the mounted component's props.

Mocking a component function

Sometimes you may have a function defined in your class that makes a call to an external source (a REST endpoint, for example). You don't necessarily want this function to behave as defined, but would rather mock it and have it return a value of your choosing to allow deterministic execution of your component code. In this example I'll show you how to mock an async function that gets called on a hypothetical button click and returns some data (we'll use the async/await ES6 syntax) based on some imaginary complex logic. For example, it returns true if the API has seen this user before, and false otherwise.

Scenario

class MyComponent extends Component {
  //...

  apiCallingFunction = async () => {
    // ...async logic
    return data
  }

  onSubmit = async () => {
    await this.apiCallingFunction()
  }

  render () {
    return (
      /* component markup with some form that invokes `onSubmit` and
       * changes the props based on the response and some logic returned from `apiCallingFunction`
       */
    )
  }
}

How to test

it('something something something, MyComponent', async () => {
  const renderedComponent = mount(<MyComponent />)

  const myComponent = renderedComponent.find(MyComponent).instance()
  myComponent.apiCallingFunction = jest.fn().mockImplementation(() => (/* some data object you'd expect */))

  await myComponent.onSubmit()

  const expectedProp = true
  const actualProp = myComponent.props.beenHereBefore

  expect(myComponent.apiCallingFunction).toHaveBeenCalled()
  expect(expectedProp).toEqual(actualProp)
})

Explanation

Mocking was a difficult concept for me to wrap my head around the first time I was introduced to it, so don't get discouraged if it doesn't immediately click for you.

In this example we're doing the familiar mount of MyComponent and getting the instance. When we are given this instance we are also given access to all of its class-level functions, in this case apiCallingFunction. We immediately redefine this function as a jest mock function with an implementation that returns an object we define. When we invoke the onSubmit function our function will be called. But because we've redefined this function it never really gets invoked in the originally-defined sense. Instead, our mock function of the same name is invoked and our fake data is returned. We assert that it was called, and that the downstream logic behaved as expected via our expects.

In doing this we can be sure that we're not making external API calls, which is a no-no for unit tests anyway, and we can be certain we'll get the response we want so that we can test the interesting downstream logic. For the curious, see what happens when you remove the await from the onSubmit call in the test above. Why might the result occur?

Mocking an external function

What happens when you have a function that you import from, say, an external source (a lib, a utils dir, whatever) and you don't want to execute that function?

Scenario

import { someFunction } from 'SomeLib'

class MyComponent extends Component {
  //... stuff

  onSubmit = () => {
    // ... stuff
    someFunction()
    // .. more stuff
  }

  render () {
    return (
      // component markup
    )
  }
}

How to test

jest.mock('SomeLib', () => ({
  someFunction: jest.fn()
}))
import { someFunction } from 'SomeLib'

it('checks something without calling `someFunction`', () => {
  //... same setup we've seen before

  // `someFunction` will never be invoked as originally defined
})

Explanation

A lot of this stuff was glossed-over because I think the point has been received by this point. The important piece is taken care of at the top of our testing file. Namely, the jest.mock... piece. We're essentially saying to our test suite,

Hey in the file I'm testing there will be an import from this library, and specifically I'm importing this function. But instead of actually calling that function from that library, just invoke a mock function instead.

And, as shown in the previous example, if you need to return data from that libs function, you know how to do so.

Passing props to your tested component

What if your component has props set by some resource that isn't available in your testing scenario? Perhaps because you're testing the child and the parent is what sets its props. In this example we'll just shove the props into the tested component. This one's easy.

Scenario

class MyComponent extends Component {
  //... say you need a firstName that's passed from the parent component into the props

  render () {
    const name = this.props.firstName

    return (
      // component markup
    )
  }
}

How to test

it('does stuff', () => {
  const props = {
    firstName: 'Matt'
  }

  const renderedComponent = shallow(<MyComponent {...props} />)

  // the rest of the test...
})

Explanation

This is pretty straightforward. We're using ES6's splatting syntax to override the component's props with our test-defined props. You may have to bite off more than you want to chew if your situation calls for doing this, because you may have to stub-out a lot of props properties if your component requires them.

Mocking your redux store

Let's be real with each other. You're probably using redux in your application (at least until Abramov releases the next game-changer). How do you test a component that is coupled with redux? If you don't already know, lemme show you (this is also easy)...

Scenario

class MyComponent extends Component {
  render () {
    const userId = this.props.user.id
    return (
      // component markup
    )
  }
}

const mapStateToProps = ({ user }) => ({ user })
export default connect(mapStateToProps, {})(MyComponent)

How to test

import configureMockStore from 'redux-mock-store'

it('does stuff', () => {
  const mockStore = configureMockStore([])({
    user: {
      id: 1234
    }
  })

  const renderedComponent = mount(
    <Provider store={mockStore}>
      <MyComponent {...props} />
    </Provider>
  )

  // the rest of the test...
})

Explanation

To achieve this you'll need to install redux-mock-store into your project. In fact, if your component uses connect your test will (loudly) complain about not having a store provided to it. configureMockStore implemented in this way will take care of all the nastiness of setting up a redux store for you so you only have to worry about giving the store the object structure the rest of the component is expecting.


I hope you found this useful.

Useful links:

* not my team, but the team I'm on

comment

Comments