|
| 1 | +# Testing |
| 2 | + |
| 3 | +<details open> |
| 4 | + <summary><b>Examples</b></summary> |
| 5 | + <ul> |
| 6 | + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-jest">Next.js with Jest and React Testing Library</a></li> |
| 7 | + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-cypress">Next.js with Cypress</a></li> |
| 8 | + </ul> |
| 9 | +</details> |
| 10 | + |
| 11 | +Learn how to set up Next.js with three commonly used testing tools: [Jest](https://jestjs.io/docs/tutorial-react), [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/), and [Cypress](https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/). |
| 12 | + |
| 13 | +## Jest and React Testing Library |
| 14 | + |
| 15 | +Jest and React Testing Library are frequently used together for Unit Testing. |
| 16 | + |
| 17 | +### Quickstart |
| 18 | + |
| 19 | +You can use `create-next-app` with the [with-jest example](https://github.com/vercel/next.js/tree/canary/examples/with-jest) to quickly get started with Jest and React Testing Library: |
| 20 | + |
| 21 | +```bash |
| 22 | +npx create-next-app --example with-jest with-jest-app |
| 23 | +``` |
| 24 | + |
| 25 | +### Manual setup |
| 26 | + |
| 27 | +To manually set up Jest and React Testing Library, install `jest` , `@testing-library/react`, `@testing-library/jest-dom` as well as some supporting packages: |
| 28 | + |
| 29 | +```bash |
| 30 | +npm install --save-dev jest babel-jest @testing-library/react @testing-library/jest-dom identity-obj-proxy react-test-renderer |
| 31 | +``` |
| 32 | + |
| 33 | +**Configuring Jest** |
| 34 | + |
| 35 | +Create a `jest.config.js` file in your project's root directory and add the following configuration options: |
| 36 | + |
| 37 | +```jsx |
| 38 | +// jest.config.js |
| 39 | + |
| 40 | +module.exports = { |
| 41 | + collectCoverageFrom: [ |
| 42 | + '**/*.{js,jsx,ts,tsx}', |
| 43 | + '!**/*.d.ts', |
| 44 | + '!**/node_modules/**', |
| 45 | + ], |
| 46 | + moduleNameMapper: { |
| 47 | + // Handle CSS imports (with CSS modules) |
| 48 | + // https://jestjs.io/docs/webpack#mocking-css-modules |
| 49 | + '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', |
| 50 | + |
| 51 | + // Handle CSS imports (without CSS modules) |
| 52 | + '^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js', |
| 53 | + |
| 54 | + // Handle image imports |
| 55 | + // https://jestjs.io/docs/webpack#handling-static-assets |
| 56 | + '^.+\\.(jpg|jpeg|png|gif|webp|svg)$': `<rootDir>/__mocks__/fileMock.js`, |
| 57 | + }, |
| 58 | + testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'], |
| 59 | + transform: { |
| 60 | + // Use babel-jest to transpile tests with the next/babel preset |
| 61 | + // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object |
| 62 | + '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }], |
| 63 | + }, |
| 64 | + transformIgnorePatterns: [ |
| 65 | + '/node_modules/', |
| 66 | + '^.+\\.module\\.(css|sass|scss)$', |
| 67 | + ], |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +You can learn more about each option above in the [Jest docs](https://jestjs.io/docs/configuration). |
| 72 | + |
| 73 | +**Handling stylesheets and image imports** |
| 74 | + |
| 75 | +These files aren't useful in tests but importing them may cause errors, so we will need to mock them. Create the mock files we referenced in the configuration above - `fileMock.js` and `styleMock.js` - inside a `__mocks__` directory: |
| 76 | + |
| 77 | +```json |
| 78 | +// __mocks__/fileMock.js |
| 79 | + |
| 80 | +(module.exports = "test-file-stub") |
| 81 | +``` |
| 82 | + |
| 83 | +```json |
| 84 | +// __mocks__/styleMock.js |
| 85 | + |
| 86 | +module.exports = {}; |
| 87 | +``` |
| 88 | + |
| 89 | +For more information on handling static assets, please refer to the [Jest Docs](https://jestjs.io/docs/webpack#handling-static-assets). |
| 90 | + |
| 91 | +**Extend Jest with custom matchers** |
| 92 | + |
| 93 | +`@testing-library/jest-dom` includes a set of convenient [custom matchers](https://github.com/testing-library/jest-dom#custom-matchers) such as `.toBeInTheDocument()` making it easier to write tests. You can import the custom matchers for every test by adding the following option to the Jest configuration file: |
| 94 | + |
| 95 | +```json |
| 96 | +// jest.config.js |
| 97 | + |
| 98 | +setupFilesAfterEnv: ['<rootDir>/jest.setup.js'] |
| 99 | +``` |
| 100 | + |
| 101 | +Then, inside `jest.setup.js`, add the following import: |
| 102 | + |
| 103 | +```jsx |
| 104 | +// jest.setup.js |
| 105 | + |
| 106 | +import '@testing-library/jest-dom/extend-expect' |
| 107 | +``` |
| 108 | + |
| 109 | +If you need to add more setup options before each test, it's common to add them to the `jest.setup.js` file above. |
| 110 | + |
| 111 | +**Absolute Imports and Module Path Aliases** |
| 112 | + |
| 113 | +If your project is using [Module Path Aliases](https://nextjs.org/docs/advanced-features/module-path-aliases), you will need to configure Jest to resolve the imports by matching the paths option in the `jsconfig.json` file with the `moduleNameMapper` option in the `jest.config.js` file. For example: |
| 114 | + |
| 115 | +```json |
| 116 | +// tsconfig.json or jsconfig.json |
| 117 | +{ |
| 118 | + "compilerOptions": { |
| 119 | + "baseUrl": ".", |
| 120 | + "paths": { |
| 121 | + "@/components/*": ["components/*"] |
| 122 | + } |
| 123 | + } |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +```jsx |
| 128 | +// jest.config.js |
| 129 | +moduleNameMapper: { |
| 130 | + '^@/components/(.*)$': '<rootDir>/components/$1', |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +**Add a test script to package.json** |
| 135 | + |
| 136 | +Add the Jest executable in watch mode to the `package.json` scripts: |
| 137 | + |
| 138 | +```jsx |
| 139 | +"scripts": { |
| 140 | + "dev": "next dev", |
| 141 | + "build": "next build", |
| 142 | + "start": "next start", |
| 143 | + "test": "jest --watch" |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +`jest --watch` will re-run tests when a file is changed. For more Jest CLI options, please refer to the [Jest Docs](https://jestjs.io/docs/cli#reference). |
| 148 | + |
| 149 | +**Create your first tests** |
| 150 | + |
| 151 | +Your project is now ready to run tests. Follow Jests convention by adding tests to the `__tests__` folder in your project's root directory. |
| 152 | + |
| 153 | +For example, we can add a test to check if the `<Index />` component successfully renders a heading: |
| 154 | + |
| 155 | +```jsx |
| 156 | +// __tests__/testing-library.js |
| 157 | +import React from 'react' |
| 158 | +import { render } from '@testing-library/react' |
| 159 | +import Index from '../pages/index' |
| 160 | + |
| 161 | +describe('App', () => { |
| 162 | + it('renders a heading', () => { |
| 163 | + const { getByRole } = render(<Index />) |
| 164 | + |
| 165 | + const heading = getByRole('heading', { |
| 166 | + name: /welcome to next\.js!/i, |
| 167 | + }) |
| 168 | + |
| 169 | + expect(heading).toBeInTheDocument() |
| 170 | + }) |
| 171 | +}) |
| 172 | +``` |
| 173 | + |
| 174 | +Optionally, add a [snapshot test](https://jestjs.io/docs/snapshot-testing) to keep track of any unexpected changes to your `<Index />` component: |
| 175 | + |
| 176 | +```jsx |
| 177 | +// __tests__/snapshot.js |
| 178 | +import React from 'react' |
| 179 | +import renderer from 'react-test-renderer' |
| 180 | +import Index from '../pages/index' |
| 181 | + |
| 182 | +it('renders homepage unchanged', () => { |
| 183 | + const tree = renderer.create(<Index />).toJSON() |
| 184 | + expect(tree).toMatchSnapshot() |
| 185 | +}) |
| 186 | +``` |
| 187 | + |
| 188 | +Test files should not be included inside the pages directory because any files inside the pages directory are considered routes. |
| 189 | + |
| 190 | +**Running your test suite** |
| 191 | + |
| 192 | +Run `npm run jest` to run your test suite. After your tests pass or fail, you will notice a list of interactive Jest commands that will be helpful as you add more tests. |
| 193 | + |
| 194 | +For further reading, you may find these resources helpful: |
| 195 | + |
| 196 | +- [Jest Docs](https://jestjs.io/docs/getting-started) |
| 197 | +- [React Testing Library Docs](https://testing-library.com/docs/react-testing-library/intro/) |
| 198 | +- [Testing Playground](https://testing-playground.com/) - use good testing practices to match elements. |
| 199 | + |
| 200 | +## Cypress |
| 201 | + |
| 202 | +Cypress is a test runner used for **End-to-End (E2E)** and **Integration Testing**. |
| 203 | + |
| 204 | +### Quickstart |
| 205 | + |
| 206 | +You can use `create-next-app` with the [with-cypress example](https://github.com/vercel/next.js/tree/canary/examples/with-jest) to quickly get started. |
| 207 | + |
| 208 | +```bash |
| 209 | +npx create-next-app --example with-cypress with-cypress-app |
| 210 | +``` |
| 211 | + |
| 212 | +### Manual setup |
| 213 | + |
| 214 | +To get started with Cypress, install the `cypress` package: |
| 215 | + |
| 216 | +```bash |
| 217 | +npm install --save-dev cypress |
| 218 | +``` |
| 219 | + |
| 220 | +Add Cypress to the `package.json` scripts field: |
| 221 | + |
| 222 | +```json |
| 223 | +"scripts": { |
| 224 | + "dev": "next dev", |
| 225 | + "build": "next build", |
| 226 | + "start": "next start", |
| 227 | + "cypress": "cypress open", |
| 228 | +} |
| 229 | +``` |
| 230 | + |
| 231 | +Run Cypress for the first time to generate examples that use their recommended folder structure: |
| 232 | + |
| 233 | +```bash |
| 234 | +npm run cypress |
| 235 | +``` |
| 236 | + |
| 237 | +You can look through the generated examples and the [Writing Your First Test](https://docs.cypress.io/guides/getting-started/writing-your-first-test) section of the Cypress Documentation to help you get familiar with Cypress. |
| 238 | + |
| 239 | +### Creating your first Cypress integration test |
| 240 | + |
| 241 | +Assuming the following two Next.js pages: |
| 242 | + |
| 243 | +```jsx |
| 244 | +// pages/index.js |
| 245 | +import Link from 'next/link' |
| 246 | + |
| 247 | +export default function Home() { |
| 248 | + return ( |
| 249 | + <nav> |
| 250 | + <Link href="/about"> |
| 251 | + <a>About</a> |
| 252 | + </Link> |
| 253 | + </nav> |
| 254 | + ) |
| 255 | +} |
| 256 | +``` |
| 257 | + |
| 258 | +```jsx |
| 259 | +// pages/about.js |
| 260 | +export default function About() { |
| 261 | + return ( |
| 262 | + <div> |
| 263 | + <h1>About Page</h1> |
| 264 | + </div> |
| 265 | + ) |
| 266 | +} |
| 267 | +``` |
| 268 | + |
| 269 | +Add a test to check your navigation is working correctly: |
| 270 | + |
| 271 | +```jsx |
| 272 | +// cypress/integration/app.spec.js |
| 273 | + |
| 274 | +describe('Navigation', () => { |
| 275 | + it('should navigate to the about page', () => { |
| 276 | + // Start from the index page |
| 277 | + cy.visit('http://localhost:3000/') |
| 278 | + |
| 279 | + // Find a link with an href attribute containing "about" and click it |
| 280 | + cy.get('a[href*="about"]').click() |
| 281 | + |
| 282 | + // The new url should include "/about" |
| 283 | + cy.url().should('include', '/about') |
| 284 | + |
| 285 | + // The new page should contain an h1 with "About page" |
| 286 | + cy.get('h1').contains('About Page') |
| 287 | + }) |
| 288 | +}) |
| 289 | +``` |
| 290 | + |
| 291 | +You can use `cy.visit("/")` instead of `cy.visit("http://localhost:3000/")` if you add `"baseUrl": "http://localhost:3000"` to the `cypress.json` configuration file. |
| 292 | + |
| 293 | +### Running your Cypress tests |
| 294 | + |
| 295 | +Because Cypress is testing a real Next.js application, it requires the Next.js server to be running prior to starting Cypress. Run `npm run dev` to start the development server then run `npm run cypress` in another terminal window to start Cypress. |
| 296 | + |
| 297 | +Alternatively, you can install the `start-server-and-test` package and add it to the `package.json` scripts field: `"test": "start-server-and-test dev http://localhost:3000 cypress"` to start the Next.js development server when you run Cypress. |
| 298 | + |
| 299 | +### Getting ready for Continuous Integration (CI) |
| 300 | + |
| 301 | +You will have noticed that running Cypress so far has opened an interactive browser which is not ideal for CI environments. You can also run Cypress headlessly using the `cypress run` command: |
| 302 | + |
| 303 | +```json |
| 304 | +// package.json |
| 305 | + |
| 306 | +"scripts": { |
| 307 | + //... |
| 308 | + "cypress": "cypress open", |
| 309 | + "cypress:headless": "cypress run", |
| 310 | + "e2e": "start-server-and-test dev http://localhost:3000 cypress", |
| 311 | + "e2e:headless": "start-server-and-test dev http://localhost:3000 cypress:headless" |
| 312 | +} |
| 313 | +``` |
| 314 | + |
| 315 | +You can learn more about Cypress and Continuous Integration from these resources: |
| 316 | + |
| 317 | +- [Cypress Continuous Integration Docs](https://docs.cypress.io/guides/continuous-integration/introduction) |
| 318 | +- [Official Cypress Github Action](https://github.com/cypress-io/github-action) |
| 319 | + |
| 320 | +## Community Packages and Examples |
| 321 | + |
| 322 | +The Next.js community has created packages and articles you may find helpful: |
| 323 | + |
| 324 | +- [next-page-tester](https://github.com/toomuchdesign/next-page-tester) for DOM Integration Testing. |
| 325 | +- [next-router-mock](https://github.com/scottrippey/next-router-mock) for Storybook. |
| 326 | +- [Test Preview Vercel Deploys with Cypress](https://glebbahmutov.com/blog/develop-preview-test/) by Gleb Bahmutov. |
| 327 | + |
| 328 | +For more information on what to read next, we recommend: |
| 329 | + |
| 330 | +<div class="card"> |
| 331 | + <a href="/docs/basic-features/environment-variables#test-environment-variable.md"> |
| 332 | + <b>Test Environment Variables</b> |
| 333 | + <small>Learn more test environments.</small> |
| 334 | + </a> |
| 335 | +</div> |
0 commit comments