import sum from './sum';
it('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
// user.js
import request from './request';
export function getUserName(userID) {
return request(`/users/${userID}`).then(user => user.name);
}
// request.js
const http = require('http');
export default function request(url) {
return new Promise(resolve => {
// This is an example of an http request, for example to fetch
// user data from an API.
// This module is being mocked in __mocks__/request.js
http.get({path: url}, response => {
let data = '';
response.on('data', _data => (data += _data));
response.on('end', () => resolve(data));
});
});
}
// __mocks__/request.js
const users = {
4: {name: 'Mark'},
5: {name: 'Paul'},
};
export default function request(url) {
return new Promise((resolve, reject) => {
const userID = parseInt(url.substr('/users/'.length), 10);
process.nextTick(() =>
users[userID]
? resolve(users[userID])
: reject({
error: `User with ${userID} not found.`,
}),
);
});
}
// async/await can be used.
it('works with async/await', async () => {
expect.assertions(1);
const data = await user.getUserName(4);
expect(data).toEqual('Mark');
});
// async/await can also be used with `.resolves`.
it('works with async/await and resolves', async () => {
expect.assertions(1);
await expect(user.getUserName(5)).resolves.toEqual('Paul');
});
Smoke Test
Shallow Rendering
Full Rendering
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
it('renders without crashing', () => {
shallow(<App />);
});
"With shallow rendering, I can refactor my component's implementation and my tests break. With shallow rendering, I can break my application and my tests say everything's still working."
- Kent C. Dodds
If it relates to rendering components, it should deal with DOM Nodes over rendered component instances.
It should be generally useful for testing the application components in the way the user would use it.
Utility implementations and APIs should be simple and flexible.
import {render, screen} from '@testing-library/react' // (or /dom, /vue, ...)
test('should show login form', () => {
render(<Login />)
const input = screen.getByLabelText('Username')
// Events and assertions...
})
No Match | 1 Match | 1+ Match | Await? | |
---|---|---|---|---|
getBy | throw | return | throw | No |
findBy | throw | return | throw | Yes |
queryBy | null | return | throw | No |
getAllBy | throw | array | array | No |
findAllBy | throw | array | array | Yes |
queryAllBy | [] | array | array | No |
import {screen} from '@testing-library/dom'
document.body.innerHTML = `
<label for="example">Example</label>
<input id="example" />
`
const exampleInput = screen.getByLabelText('Example')
import {render, screen} from '@testing-library/react'
render(
<div>
<label htmlFor="example">Example</label>
<input id="example" />
</div>,
)
const exampleInput = screen.getByLabelText('Example')
import {screen} from '@testing-library/dom'
document.body.innerHTML = `
<button>test</button>
<span>multi-test</span>
<div>multi-test</div>
`
// debug document
screen.debug()
// debug single element
screen.debug(screen.getByText('test'))
// debug multiple elements
screen.debug(screen.getAllByText('multi-test'))
import {render, screen, fireEvent} from '@testing-library/react'
const Button = ({onClick, children}) => (
<button onClick={onClick}>{children}</button>
)
test('calls onClick prop when clicked', () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click Me</Button>)
fireEvent.click(screen.getByText(/click me/i))
expect(handleClick).toHaveBeenCalledTimes(1)
})
npm install --save-dev @testing-library/user-event @testing-library/dom
test('trigger some awesome feature when clicking the button', async () => {
const user = userEvent.setup()
render(<MyComponent />)
await user.click(screen.getByRole('button', {name: /click me!/i}))
// ...assertions...
})
const user = userEvent.setup()
await user.keyboard('[ShiftLeft>]') // Press Shift (without releasing it)
await user.click(element) // Perform a click with `shiftKey: true`
test('movie title appears', async () => {
// element is initially not present...
// wait for appearance and return the element
const movie = await findByText('the lion king')
})
test('movie title appears', async () => {
// element is initially not present...
// wait for appearance inside an assertion
await waitFor(() => {
expect(getByText('the lion king')).toBeInTheDocument()
})
})
test('movie title no longer present in DOM', async () => {
// element is removed
await waitForElementToBeRemoved(() => queryByText('the mummy'))
})
npm install --save-dev @testing-library/react
import { render } from '@testing-library/react';
render(<Login />);
// __tests__/fetch.test.js
import React from 'react'
import {rest} from 'msw'
import {setupServer} from 'msw/node'
import {render, fireEvent, waitFor, screen} from '@testing-library/react'
import '@testing-library/jest-dom'
import Fetch from '../fetch'
const server = setupServer(
rest.get('/greeting', (req, res, ctx) => {
return res(ctx.json({greeting: 'hello there'}))
}),
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('loads and displays greeting', async () => {
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('heading'))
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
expect(screen.getByRole('button')).toBeDisabled()
})
test('handles server error', async () => {
server.use(
rest.get('/greeting', (req, res, ctx) => {
return res(ctx.status(500))
}),
)
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('alert'))
expect(screen.getByRole('alert')).toHaveTextContent('Oops, failed to fetch!')
expect(screen.getByRole('button')).not.toBeDisabled()
})
// import dependencies
import React from 'react'
// import API mocking utilities from Mock Service Worker
import {rest} from 'msw'
import {setupServer} from 'msw/node'
// import react-testing methods
import {render, fireEvent, waitFor, screen} from '@testing-library/react'
// add custom jest matchers from jest-dom
import '@testing-library/jest-dom'
// the component to test
import Fetch from '../fetch'
test('loads and displays greeting', async () => {
// Arrange
// Act
// Assert
})
// declare which API requests to mock
const server = setupServer(
// capture "GET /greeting" requests
rest.get('/greeting', (req, res, ctx) => {
// respond using a mocked JSON body
return res(ctx.json({greeting: 'hello there'}))
}),
)
// establish API mocking before all tests
beforeAll(() => server.listen())
// reset any request handlers that are declared as a part of our tests
// (i.e. for testing one-time error scenarios)
afterEach(() => server.resetHandlers())
// clean up once the tests are done
afterAll(() => server.close())
// ...
test('handlers server error', async () => {
server.use(
// override the initial "GET /greeting" request handler
// to return a 500 Server Error
rest.get('/greeting', (req, res, ctx) => {
return res(ctx.status(500))
}),
)
// ...
})
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
// wait until the `get` request promise resolves and
// the component calls setState and re-renders.
// `waitFor` waits until the callback doesn't throw an error
await waitFor(() =>
// getByRole throws an error if it cannot find an element
screen.getByRole('heading'),
)
// assert that the alert message is correct using
// toHaveTextContent, a custom matcher from jest-dom.
expect(screen.getByRole('alert')).toHaveTextContent('Oops, failed to fetch!')
// assert that the button is not disabled using
// toBeDisabled, a custom matcher from jest-dom.
expect(screen.getByRole('button')).not.toBeDisabled()
{data.posts.map((post, index) => (
<Grid item lg key={post.id}>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.media}
image={getSrc(post.id)}
title={post.author}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{post.author}
</Typography>
...
import React from "react";
import { render, screen } from "@testing-library/react";
import axiosMock from "axios";
import Posts from "./Posts";
jest.mock("axios");
it("Should render the Posts component", async () => {
// Arrange
// Act
// Assert
});
// Arrange
const returnData = [
{
author: "John Doe",
author_url: "https://unsplash.com/@johndoe",
filename: "0000_yC-Yzbqy7PY.jpeg",
format: "jpeg",
height: 3744,
id: 0,
post_url: "https://unsplash.com/photos/yC-Yzbqy7PY",
width: 5616
},
{
author: "Jane Doe",
author_url: "https://unsplash.com/@janedoe",
filename: "0100_pwaaqfoMibI.jpeg",
format: "jpeg",
height: 1656,
id: 1,
post_url: "https://unsplash.com/photos/pwaaqfoMibI",
width: 2500
}
];
axiosMock.get.mockResolvedValueOnce({ data: returnData });
// Act
render(<Posts />);
const [cardNodeForJohnDoe, cardNodeForJaneDoe] = await waitFor(() => [
screen.getByTitle("John Doe"),
screen.getByTitle("Jane Doe"),
]);
// Act
render(<Posts />);
const cardNodeForJohnDoe = await screen.findByTitle("John Doe");
const cardNodeForJaneDoe = await screen.findByTitle("Jane Doe");
// Assert
expect(axiosMock.get)
.toHaveBeenCalledTimes(1);
expect(axiosMock.get)
.toHaveBeenCalledWith("https://picsum.photos/list");
expect(cardNodeForJohnDoe)
.toHaveStyle(
"backgroundImage: 'url(\"https://picsum.photos/250/375?image=0\")'"
);
expect(cardNodeForJaneDoe)
.toHaveStyle(
"backgroundImage: 'url(\"https://picsum.photos/250/375?image=1\")'"
);
By Google [CC BY 4.0], via Wikimedia Commons
Facebook [Public domain or CC BY-SA 1.0], via Wikimedia Commons
By Evan You [CC BY 4.0], via Wikimedia Commons
const button = screen.getByRole('button', {name: /disabled button/i})
// โ
expect(button.disabled).toBe(true)
// error message:
// expect(received).toBe(expected) // Object.is equality
//
// Expected: true
// Received: false
// โ
expect(button).toBeDisabled()
// error message:
// Received element is not disabled:
// <button />
// โ
// assuming you've got this DOM to work with:
// <label>Username</label><input data-testid="username" />
screen.getByTestId('username')
// โ
// change the DOM to be accessible by associating the label and setting the type
// <label for="username">Username</label><input id="username" type="text" />
screen.getByRole('textbox', {name: /username/i})
// โ
expect(screen.queryByRole('alert')).toBeInTheDocument()
// โ
expect(screen.getByRole('alert')).toBeInTheDocument()
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
// โ
await waitFor(() => {})
expect(window.fetch).toHaveBeenCalledWith('foo')
expect(window.fetch).toHaveBeenCalledTimes(1)
// โ
await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
expect(window.fetch).toHaveBeenCalledTimes(1)
// โ
await waitFor(() => {
fireEvent.keyDown(input, {key: 'ArrowDown'})
expect(screen.getAllByRole('listitem')).toHaveLength(3)
})
// โ
fireEvent.keyDown(input, {key: 'ArrowDown'})
await waitFor(() => {
expect(screen.getAllByRole('listitem')).toHaveLength(3)
})