Basic Testing

Configure Libraries

Let's start by configuring enzyme (because jest is built into create-react-app):

import Enzyme from 'enzyme'
import EnzymeAdapter from 'enzyme-adapter-react-16'

Enzyme.configure({ adapter: new EnzymeAdapter() })

Pro tip: To save time, you can move this configuration into its own file and tell jest to run it before every jest test. In create-react-app, a designated src/setupTests.js is already set up for this.

Writing Tests

Empty Critical Tests

Start by writing out empty tests for all the critical parts of your app: features that, if they were changed, you'd want to be warned.

// This app shows a counter and a button that, when clicked, increments the counter
test('renders without error', () => {}) // <= PRO TIP: this is an excellent basic first test
test('renders increment button', () => {})
test('renders counter display', () => {})
test('counter starts at 0', () => {})
test('pressing button increments counter display', () => {})
// ^ This last test focuses on *BEHAVIOUR* by looking at output on page, not state

data-test attribute

Pro tip: One effective method to test whether a component renders is to (1) add a data-test attribute to the component, (2) find the component using that attribute, and then (3) check that only 1 component is found.

test('renders increment button', () => {
  const wrapper = shallow(<App />)
  const button = wrapper.find('[data-test="incrementButton]')
  expect(button.length).toBe(1)
})

Useful helper functions

This function finds nodes in a wrapper with a given data-test attribute:

const findByTestAttr = (wrapper, val) => {
  return wrapper.find(`[data-test='${val}']`)
}

This function sets up and returns a ShallowWrapper component by encapsulating all configuration in a function:

// Use this to easily instantiate your component in order to test it!
// NOTE 1: The component is passed defaultProps that you can override
// (especially useful when component has required propTypes)
// NOTE 2: If state is provided, state is also set
const defaultProps = { myProp: undefined }

const setup = (props={}, state=null) => {
  const setupProps = { ...defaultProps, ...props }

  const wrapper = shallow(<MyComponent {...setupProps} />)
  if (state) wrapper.setState(state)

  return wrapper
}

Finding child nodes, managing state, and simulating events

Let's test that a button click increments a counter display. This will use:

  • state and setState for state management

  • find for finding child nodes

  • simulate for simulating events

  • text to get inner text of node

test('pressing button increments counter display', () => {
  const wrapper = shallow(<App />)
  wrapper.setState({ counter: 7 }) // <= don't have to do this if component has initial state

  const button = wrapper.find('[data-set="incrementButton"]')
  button.simulate('click')

  const counterDisplay = wrapper.find('[data-set="counterDisplay"]')
  expect(counterDisplay.text()).toContain(8)
})

Note: We use toContain because that allows you to change the text content inside counterDisplay without causing test failure.

propTypes testing

If your component has propTypes set up, you can run tests using check-prop-types to check that your propTypes are doing their job by only accepting the correct props.

import checkPropTypes from 'check-prop-types'

// Helper function
const checkProps = (component, props) => {
  const propsError = checkPropTypes(
    component.propTypes,
    props,
    'prop',
    component.name
  )

  expect(propsError).toBeUndefined()
}

test('expected props don\'t throw error message', () => {
  const expectedProps = { myProp: 'hello' }
  checkProps(MyComponent, expectedProps)
})

Using checkPropTypes inside checkProps, a propsError message is returned if the provided props don't adhere to propTypes. We then check to see if there is in fact an error message using expect.

Contexts, describe, and forEach

We can wrap a set of test functions inside a describe function, creating a context in which all those tests fall under.

For example, a counter may have a set of tests for testing the increment portion of the counter:

describe('if increment button clicked', () => {
  test('does not increment if counter is at 10', () => {
    const wrapper = setup()
    // Expects here
  })
  test('does increment if counter is below 10', () => {
    const wrapper = setup()
    // Expects here
  })
})

Notice how we write const wrapper = setup() before each test? For code that you're repeating, you can move this to a beforeEach. beforeEach will run the code before each test.

// Refactor
describe('if increment button clicked', () => {
  let wrapper // <= accessible scope
  beforeEach(() => {
    wrapper = setup()
  })

  test('does not increment if counter is at 10', () => {
    // Expects here
  })
  test('does increment if counter is below 10', () => {
    // Expects here
  })
})

Last updated