next-jest-testing-library
next-jest-testing-library copied to clipboard
This is a cheat sheet repo for Next.js + Jest + React Testing Library
Next.js + Jest + React Testing Library
This is a cheat sheet repo for Next.js + Jest + React Testing Library. Each page is a different topic
Note: Check this page for a quick cheat sheet from the official react testing library docs
How to use
- clone this repo and
cdinto it - run
npm installto install dependencies - run
npm testto run tests
Create a new Next.js app with Jest and React Testing Library
- run
mkdirfollowed by the name of your project andcdinto it, then:
npx create-next-app --example with-jest .
- make sure to remove the
__tests__folder if already there to clear the example tests - clear the
pages/index.jsboilerplate code - remove
.gitfolder if you want to start fresh:rm -rf .git - initialize a new git repo:
git init(npm testwill fail if you don't do this) - run
npm testto make sure everything is working
Configure eslint with Jest and React Testing Library
Note: For this to work you should already have eslint installed and configured in your project byt choosing it during the
create-next-appsetup
- install eslint plugins for jest and react-testing-library:
npm install --save-dev eslint-plugin-testing-library eslint-plugin-jest-dom
- add the following to your
.eslintrcfile:
{
"plugins": ["testing-library", "jest-dom"],
"extends": ["plugin:testing-library/react"]
}
Note: Check the
.eslintrc.jsonfile in this repo for a full example
- be sure to have this rule on your
settings.jsonfile in vscode:
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
Note: You can either put this on your global settings or on your project settings by adding a
.vscodefolder to your project and adding asettings.jsonfile inside it
Run Jest Tests
npm test
Jest commands
jest --watch- run tests in watch modejest --watchAll- run all tests in watch modejest- run all tests and exitjest --coverage- run tests with coverage reportjest --watch --coverage- run tests in watch mode with coverage report
Jest watch mode commands
f- run only failed testso- run only tests related to changed filesp- filter by a filename regex patternt- filter by a test name regex patternq- quit watch modeEnter- trigger a test runa- run all tests
Jest test.only() and test.skip()
test.only()- run only this testtest.skip()- skip this test
Example:
test.only('should render...', () => {
// ...
})
How does Jest watch mode work?
- Jest will run all tests that have changed since the last commit. If you want to run all tests, you can press
ato run all tests. - When a file is saved or a test file is changed, Jest will re-run the tests
Note: If there are no changes since the last commit, no tests will run.
TDD (Test Driven Development)
- Write tests before writing code
- Write the minimum amount of code to make the tests pass
- Refactor code as needed
Note: Its is called "red-green" testing because the test initially fails (red) and then passes (green) after the code is written
Why TDD?
- You don't need to manually test your code every time you make a change
- You can refactor your code with confidence because you have tests to make sure nothing breaks
- Writing tests before the code forces you to think about the code you are writing
- Writing tests before the code feels part of the process, instead of a "chore" to do at the end
Unit Testing vs Functional Testing
Unit testing - tests individual units of code
| Pros | Cons |
|---|---|
| Mock dependencies | Further from how users interact |
| Easy to pinpoint failures | More likely to break in refactoring |
Functional testing - tests how the app works from the user's perspective
| Pros | Cons |
|---|---|
| Close to how users interact | More difficult to debug |
| Robust tests |
Note: react-testing-library is a functional testing library
render method
import render
import { render } from '@testing-library/react'
import Component from './Component'
use render
render(<Component />)
Testing Library screen methods
import screen
import { screen } from '@testing-library/react'
use screen
const button = screen.getByRole('button')
Commands
e.g.
screen.queryByRole('button')- returns a single element
get- expect an element to be in the DOMquery- expect an element not to be in DOM (useful for popovers etc...), returns null if not foundfind- expect an element to be in the DOM, but wait for it to appear (useful for async data)
All
e.g.
getAllByRole('button')- returns an array of all buttons in the DOM
Add All to the command to return an array of elements
Query Type
e.g.
getByRole('button')
ByRole- query by roleByLabelText- query by label textByPlaceholderText- query by placeholder textByText- query by textByDisplayValue- query by display valueByAltText- query by alt textByTitle- query by titleByTestId- query by test id
Testing Library Order of Priority
Testing Library suggest to follow accessibility guidelines when writing tests. This means that you should use the following order of priority when querying the DOM:
getByRole('button', { name: /click me/i })getByLabelText('First Name')getByPlaceholderText('Enter your first name')getByText('Click me')getByDisplayValue('John')getByAltText('Profile picture')getByTitle('Close')getByTestId('my-element')
Note: You should only use
getByTestIdas a last resort. Assigndata-testid='my-element'to the element that you need to find
Accessibility Roles
- alert, alertdialog, application, article, banner, button, checkbox, columnheader, combobox, complementary, contentinfo, definition, dialog, directory, document, form, grid, gridcell, group, heading, img, link, list, listbox, listitem, log, main, marquee, math, menu, menubar, menuitem, menuitemcheckbox, menuitemradio, navigation, none, note, option, presentation, progressbar, radio, radiogroup, region, row, rowgroup, rowheader, scrollbar, search, searchbox, separator, slider, spinbutton, status, tab, tablist, tabpanel, textbox, timer, toolbar, tooltip, tree, treegrid, treeitem
js-dom Custom Matchers
- toBeDisabled
- toBeEnabled
- toBeEmptyDOMElement
- toBeInTheDocument
- toBeInvalid
- toBeRequired
- toBeValid
- toBeVisible
- toContainElement
- toContainHTML
- toHaveAccessibleDescription
- toHaveAccessibleName
- toHaveAttribute
- toHaveClass
- toHaveFocus
- toHaveFormValues
- toHaveStyle
- toHaveTextContent
- toHaveValue
- toHaveDisplayValue
- toBeChecked
- toBePartiallyChecked
- toHaveErrorMessage
userEvent
userEvent is a library that provides a set of utilities to simulate user interactions with the DOM. It is a wrapper around fireEvent that provides a more natural API for interacting with the DOM.
Note:
userEventalways returns a Promise, so you must useawaitwith it
userEvent Usage
// ... other imports
import userEvent from '@testing-library/user-event'
describe('Component', () => {
it('should do something', async () => {
// NOTE: Setup userEvent
const user = userEvent.setup()
render(<Component />)
const button = screen.getByRole('button')
await user.click(checkbox)
})
})
Read This for more info: https://testing-library.com/docs/ecosystem-user-event/
userEvent methods
userEvent.click(element)- click an elementuserEvent.hover(element)- hover over an elementuserEvent.unhover(element)- unhover over an elementuserEvent.type(element, text)- type text into an elementuserEvent.clear(element)- clear text from an input or textareauserEvent.selectOptions(element, values)- select options in a select elementuserEvent.upload(element, fileOrFiles)- upload a file or files to an elementuserEvent.tab()- tab to the next focusable elementuserEvent.keyboard(text)- type text using the keyboard
Mock Service Worker
Mock Service Worker (MSW) is a service worker based library that allows you to intercept network requests and mock responses.
Install
npm install msw --save-dev
Setup
- Add the following to
jest.setup.js
import { server } from './mocks/server'
// Establish API mocking before all tests.
beforeAll(() => server.listen())
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers())
// Clean up after the tests are finished.
afterAll(() => server.close())
// TIP: This file is similar to setupTests.js from create-react-ap
Note: Make sure to have imported
jest.setup.jsinjest.config.jslike sosetupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
Usage
- Create a
mocksfolder in the root of your project - Create a
server.jsfile in themocksfolder - Add the following to
server.js
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers)
- Create a
handlers.jsfile in themocksfolder - Add the following to
handlers.js
import { rest } from 'msw'
import { apiURL } from '@/config'
export const handlers = [
rest.get(`${apiURL}/scoops`, (req, res, ctx) => {
return res(
ctx.json([
{ name: 'Chocolate', imagePath: '/images/chocolate.png' },
{ name: 'Vanilla', imagePath: '/images/vanilla.png' },
])
)
}),
// ... other handlers
]
- Now you can get fetched data from your mock server in your test files by using
awaitandfindBy*queries
import { render, screen } from '@testing-library/react'
import Options from './Options'
test('my test', async () => {
render(<Options optionType='scoops' />)
const scoopImages = await screen.findAllByRole('img', { name: /scoop$/i })
expect(scoopImages).toHaveLength(2)
const altTextArray = scoopImages.map((element: any) => element.alt)
expect(altTextArray).toEqual(['Chocolate scoop', 'Vanilla scoop'])
})
HOW DOES IT WORK? When we run the test, if the component makes a request to the server, the request will be intercepted by Mock Service Worker (that we setup in jest.setup.js) and it will check if there is a handler for that same request (same url). IF there is, the mock request will be used instead of the real request
Simulate Server Error Response
- use
server.resetHandlers()to setup a new behavior (e.g. return an error) for a route
import { render, screen, waitFor } from '@testing-library/react'
import { rest } from 'msw'
import { server } from '@/mocks/server'
import { apiURL } from '@/config'
import Component from './Component'
test('test error', async () => {
// setup the server to return an error for this route
server.resetHandlers(
rest.get(`${apiURL}/scoops`, (req, res, ctx) => {
return res(ctx.status(500))
})
)
render(<Component />)
// wait for the error message to show up
await waitFor(async () => {
const alerts = await screen.findAllByRole('alert')
expect(alerts).toHaveLength(1)
})
})
Note: See Mock Service Worker section on this page for more info on how to setup the server
TIP: you still need to add the error handling logic in your component
Test components wrapped in a Provider (context)
- To test components that are children of a Provider (components that use context) you need to wrap them with your Provider in your test file during rendering:
render(<Component />, {
wrapper: MyProvider,
...options,
})
Note: Since you would need to do so in every test file, it's better to create a custom render function that does this for you. See
test-utils.tsxfile in this project for an example
Create a custom render function with wrapped context
See: https://testing-library.com/docs/react-testing-library/setup
- Create a
test-utils.tsxfile in the root of your project or in thesrcfolder
import { render } from '@testing-library/react'
import { OrderDetailsProvider } from '@/contexts/OrderDetails'
const renderWithContext = (ui: any, options?: any) =>
render(ui, {
wrapper: OrderDetailsProvider,
...options,
})
// re-export everything
export * from '@testing-library/react'
// override render method with render with context
export { renderWithContext as render }
- Now you can import your custom render function in your test files:
// NOTE: you don't need to import render from @testing-library/react anymore
import { render, screen, waitFor } from '@/test-utils'
test('test', () => {
render(<Component />)
// ...
})
Note: As you can see the only thing you need to do is to import your custom render function in
@/test-utilsinstead of the one from@testing-library/reactNote: You can also add other providers to your custom render function
Debugging Tips
- Use
debug()to log the html of the component to the console
import { render, screen } from '@testing-library/react'
test('test', async () => {
render(<Component />)
// log the html of the component to the console
screen.debug()
// ...
})
- Use logRoles(container) to log the roles of the elements in the container to the console
import { render, screen, logRoles } from '@testing-library/react'
test('test', async () => {
const { container }render(<Component />)
// log the roles of the elements in the container to the console
logRoles(container)
// ...
})
- Use
console.log()to log the html of the component to the console
import { render, screen } from '@testing-library/react'
test('test', async () => {
render(<Component />)
// log the html of the component to the console
console.log(screen.getByRole('alert'))
// ...
})
-
If
getBy*fail than it is likely that the element is not rendered yet. UsefindBy*instead -
userEvent methods always need
await -
Use
test.only()andtest.skip()to run only one test or skip a test -
Prevent
act()...warning from showing up in the test console by using unmount
test('test', async () => {
const { unmount } = render(<Component />)
// ...
unmount()
})
jest.mock()
Use jest.mock() to mock features and avoid errors when running tests
- For example,
jest.mock()can be used to mock props that are passed to a component
<Component prop={jest.fn()} />
Note:
jest.mock()doesn't actually do anything. It just tells jest to mock the prop to avoid errors
- Another instance is when mocking modules such as
next/router
import { useRouter } from 'next/router'
jest.mock('next/router', () => ({
useRouter: jest.fn(),
}))
Note: This will prevent errors related to
useRouternot being available in the test environment
Resources
- Next.js Testing
- Testing Library Cheat Sheet
- userEvent
- Testing Library Queries
- Testing Library Order of Priority
- w3c Accessibility Roles
- getByTestId
- Mock Service Worker
License
- MIT
Go To Top ⬆️