Kent C. Dodds

Utah

wife, 4 kids, & a dog

PayPal, Inc.

Testing React and Web Applications

Please Stand...

if you are able ❤️ ♿️

Please Stand...

if you are able

What this workshop is

  • Learn the fundamentals of what a test is and what role testing frameworks play
  • Configure Jest for a client-side React project
  • Learn what Code Coverage is and how to properly use that metric
  • Write unit tests for JavaScript utilities and React components
  • Learn what snapshot testing is and how to use it effectively
  • Write integration tests for a React application
  • Configure Cypress for a web application
  • Write E2E (end-to-end) tests with Cypress

What this workshop is not

  • Technology-agnostic
  • Free of trade-offs/unopinionated
  • Covering all forms of testing

Setup

If you can, do it now, even if you've already done it...

git clone https://github.com/kentcdodds/testing-workshop.git

cd testing-workshop

npm run setup --silent

Logistics

  • 🙋 Raise your hand to ask and answer questions. Feel free to make relevant comments as well!
  • 💬 🌎 Use the workshop chat to ask and answer each others questions.
  • 📑 Fill out the elaboration and feedback forms for every exercise.
  • 📧 Ask questions on my AMA (kcd.im/ama).
  • 🐦 Follow me on twitter 😉 (twitter.com/kentcdodds).

Routine

  1. Demos 👨‍💻
  2. Exercises 💪 (pair programming!?)
  3. Elaboration and Feedback Form 📑
  4. ? Bonus 👨‍🎓 👩‍🎓
  5. ? Help others 👩‍🔬 👨‍🔬
  6. ? Make pull requests 🙏

Let's
Get
STARTED!

Static Code Analysis

Unit tests

import React from 'react'
import ReactDOM from 'react-dom'
import ItemList from '../item-list'

test('renders "no items" when the item list is empty', () => {
  const container = document.createElement('div')
  ReactDOM.render(<ItemList items={[]} />, container)
  expect(container.textContent).toMatch('no items')
})

test('renders the items in a list', () => {
  const container = document.createElement('div')
  ReactDOM.render(<ItemList items={['apple', 'orange', 'pear']} />, container)
  expect(container.textContent).toMatch('apple')
  expect(container.textContent).toMatch('orange')
  expect(container.textContent).toMatch('pear')
})

Integration tests

import React from 'react'
import axiosMock from 'axios'
import {renderWithRouter, generate, Simulate} from 'til-client-test-utils'
import {init as initAPI} from '../utils/api'
import App from '../app'

beforeEach(() => {
  window.localStorage.removeItem('token')
  axiosMock.__mock.reset()
  initAPI()
})

test('login as an existing user', async () => {
  const {
    getByTestId,
    container,
    getByText,
    getByLabelText,
    finishLoading,
  } = renderWithRouter(<App />)

  // wait for the app to finish loading the mocked requests
  await finishLoading()

  // navigate to login
  const leftClick = {button: 0}
  Simulate.click(getByText('Login'), leftClick)
  expect(window.location.href).toContain('login')

  // fill out form
  const fakeUser = generate.loginForm()
  const usernameNode = getByLabelText('Username')
  const passwordNode = getByLabelText('Password')
  const formWrapper = container.querySelector('form')
  usernameNode.value = fakeUser.username
  passwordNode.value = fakeUser.password

  // submit form
  const {post} = axiosMock.__mock.instance
  const token = generate.token(fakeUser)
  post.mockImplementationOnce(() =>
    Promise.resolve({
      data: {user: {...fakeUser, token}},
    }),
  )
  Simulate.submit(formWrapper)

  // wait for the mocked requests to finish
  await finishLoading()

  // assert calls
  expect(axiosMock.__mock.instance.post).toHaveBeenCalledTimes(1)
  expect(axiosMock.__mock.instance.post).toHaveBeenCalledWith(
    '/auth/login',
    fakeUser,
  )

  // assert the state of the world
  expect(window.localStorage.getItem('token')).toBe(token)
  expect(window.location.href).not.toContain('login')
  expect(getByTestId('username-display').textContent).toEqual(fakeUser.username)
  expect(getByText('Logout')).toBeTruthy()
})

End-to-end tests

import {assertRoute} from '../utils'

describe('authentication', () => {
  it('should allow users to register', () => {
    const user = {username: 'bob', password: 'wiley'}
    cy
      .visitApp()
      .getByText('Register')
      .click()
      .getByLabelText('Username')
      .type(user.username)
      .getByLabelText('Password')
      .type(user.password)
      .getByText('Login')
      .click()

    cy.url().should('equal', 'http://localhost:3000/')
    cy.getByTestId('username-display').should('contain', user.username)
  })
})

The Testing Trophy

¢heap

💰🤑💰

🏎💨

🐢

Simple problems 👌

Big problems 😖

Thank you!

Testing React and Web Applications

By Kent C. Dodds

Testing React and Web Applications

Developing and deploying production applications with React is one thing, but being confident that you’re not shipping a hidden bug is something else! Knowing how to configure and use testing tools is critical to your success in shipping with confidence, and React's model opens up testing productivity most of us couldn’t even dream of before.

  • 5,821