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!