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
- Demos 👨💻
- Exercises 💪 (pair programming!?)
- Elaboration and Feedback Form 📑
- ? Bonus 👨🎓 👩🎓
- ? Help others 👩🔬 👨🔬
- ? 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,902