Next.js と、Cypress 、Playwright 、Jest・React Testing Library といったよく利用されているテストツールとのセットアップ方法について学んでいきましょう。
Cypress は、End-to-End(E2E) テストやインテグレーションテストで使用されるテストツールです。
create-next-app を使って with-cypress example のテンプレートを利用することですぐに始めることができます。
npx create-next-app@latest --example with-cypress with-cypress-app
Cypress を利用するために cypress パッケージをインストールします:
npm install --save-dev cypress
package.json の scripts に Cypress の記述を追加します:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"cypress": "cypress open",
}
Cypress を初回起動して Cypress の推奨のフォルダ構造を使ったテンプレートを生成します:
npm run cypress
生成された例や Cypress のドキュメント Writing Your First Test の章を読むことで Cypress をより深く理解できるでしょう。
Next.js で作られた 2 つのページを想定します:
// pages/index.js
import Link from 'next/link'
export default function Home() {
return (
<nav>
<Link href="/about">
<a>About</a>
</Link>
</nav>
)
}
// pages/about.js
export default function About() {
return (
<div>
<h1>About Page</h1>
</div>
)
}
ページ遷移が正しく動作していることを確認するためのテストを追加します:
// cypress/integration/app.spec.js
describe('Navigation', () => {
it('should navigate to the about page', () => {
// インデックスページからテストを開始
cy.visit('http://localhost:3000/')
// href 属性に "about" が含まれるリンクを探し、クリックする
cy.get('a[href*="about"]').click()
// 遷移した新しい url に "about" が含まれていることを確認
cy.url().should('include', '/about')
// 遷移した新しいページには "About page" と書かれた h1 要素が含まれていることを確認
cy.get('h1').contains('About Page')
})
})
設定ファイル cypress.json に "baseUrl": "http://localhost:3000" を追加することで cy.visit("http://localhost:3000/") ではなく cy.visit("/") と書くこともできます。
Cypress は Next.js アプリケーションの実際の動作をテストするため、Cypress を実行する前には Next.js サーバーを起動する必要があります。本来のアプリケーションの動作に近づけるため、本番用のコードに対してテストを実行することをお勧めします。
npm run build、npm run startを実行し、別のターミナルウィンドウで npm run cypress を実行して Cypress を起動します。
備考: または、
start-server-and-testパッケージをインストールし、package.jsonの scripts に追加することもできます:"test":"start-server-and-test start http://localhost:3000 cypress"とすると、Next.js の本番サーバーが Cypress と連動して起動します。新しい変更があった場合は、アプリケーションの再構築を忘れないようにしてください。
Cypress を実行すると、CI 環境には向いていない管理画面が起動することにお気づきでしょう。cypress run コマンドを使えば、ヘッドレスモードで Cypress を実行できます:
// package.json
"scripts": {
//...
"cypress": "cypress open",
"cypress:headless": "cypress run",
"e2e": "start-server-and-test start http://localhost:3000 cypress",
"e2e:headless": "start-server-and-test start http://localhost:3000 cypress:headless"
}
Cypress と継続的インテグレーションについては、以下のドキュメントから学ぶことができます:
Playwright は、Chromium 、Firefox や WebKit を 1 つの API で自動化できるテストフレームワークです。すべてのプラットフォームで、End-to-End (E2E) テストと統合テストを記述するために利用できます。
一番手軽なのは、create-next-app で with-playwright exampleを使ってみることです。この例を利用することで Playwright がセットアップされた Next.js プロジェクトが作成されます。
npx create-next-app@latest --example with-playwright with-playwright-app
npm init playwright を使用して、既存の NPM プロジェクトに Playwright を追加できます。
Playwright を手動でのセットアップで使い始めるには、@playwright/test パッケージをインストールします:
npm install --save-dev @playwright/test
package.json の scripts に Playwright を追加します:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test:e2e": "playwright test",
}
Next.js で作られた 2 つのページを想定します:
// pages/index.js
import Link from 'next/link'
export default function Home() {
return (
<nav>
<Link href="/about">
<a>About</a>
</Link>
</nav>
)
}
// pages/about.js
export default function About() {
return (
<div>
<h1>About Page</h1>
</div>
)
}
ページ遷移が正しく動作していることを確認するためのテストを追加します:
// e2e/example.spec.ts
import { test, expect } from '@playwright/test'
test('should navigate to the about page', async ({ page }) => {
// インデックスページからテストを開始 ( baseURL は playwright.config.ts の webServer を通じて設定される)
await page.goto('http://localhost:3000/')
// 'About Page' が含まれている要素を見つけてクリック
await page.click('text=About Page')
// 新しい URL は "/about"になる( baseURL はここで利用される)
await expect(page).toHaveURL('http://localhost:3000/about')
// 新しく遷移したページの h1 要素には "About Page" が含まれる
await expect(page.locator('h1')).toContainText('About Page')
})
設定ファイル playwright.config.ts に "baseUrl": "http://localhost:3000" を追加することで page.goto("http://localhost:3000/") ではなく page.goto("/") と書くこともできます。
Playwright は Next.js アプリケーションの実際の動作をテストするため、Playwright を実行する前には Next.js サーバーを起動する必要があります。本来のアプリケーションの動作に近づけるため、本番用のコードに対してテストを実行することをお勧めします。
npm run build、npm run startを実行し、別のターミナルウィンドウで npm run test:e2e を実行して Playwright を起動します。
備考: また、
webServerを使って Playwright に開発サーバーを起動させ、アプリケーションが利用可能になるまで待機させることも可能です。
Playwright はデフォルトで headed mode でテストを実行します。Playwright の依存関係をすべてインストールするには、npx playwright install-deps を実行します。
Playwright と継続的インテグレーションについては、以下のドキュメントから学ぶことができます:
Jest と React Testing Library は、ユニットテストを行うために、よく一緒に使われます。Jest を Next.js のアプリケーションで使い始めるには、3 つの方法があります:
以下の章では、これらの各方法を利用した Jest を設定する方法について説明します:
You can use create-next-app with the with-jest example to quickly get started with Jest and React Testing Library:
create-next-app で with-jest のテンプレートを利用すると、Jest と React Testing Library をすぐに使い始めることができます:
npx create-next-app@latest --example with-jest with-jest-app
Next.js 12 から、Next.js に Jest 用の設定が組み込まれるようになりました。
Jest を利用するには、jest、@testing-library/react、@testing-library/jest-dom をインストールします:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
jest.config.js ファイルをプロジェクトのルートディレクトリに作成し、以下を追加します:
// jest.config.js
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// テスト環境の next.config.js と .env ファイルを読み込むために、Next.js アプリケーションへのパスを記載する
dir: './',
})
// Jest に渡すカスタム設定を追加する
const customJestConfig = {
// 各テストの実行前に渡すオプションを追加
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// TypeScript の設定で baseUrl をルートディレクトリに設定している場合、alias を動作させるためには以下のようにする必要があります
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
}
// createJestConfig は、非同期で next/jest が Next.js の設定を読み込めるようにするため、下記のようにエクスポートします
module.exports = createJestConfig(customJestConfig)
裏側では、next/jest が自動的に以下のような Jest の設定をしています:
transform の設定.css、.module.css や scss 関連事項)と画像の自動モック化.env(とその関連事項)を process.env に読み込むnode_modules を除外する.next を除外するnext.config.js から読み込むRust Compiler を使わない場合、Jest を手動で設定し、上記のパッケージに加えて、babel-jest と identity-obj-proxy をインストールする必要があります。
Jest for Next.js を設定するための推奨オプションは以下のとおりです:
// jest.config.js
module.exports = {
collectCoverageFrom: [
'**/*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
],
moduleNameMapper: {
// CSS インポートの処理 (CSS modules を利用)
// https://jestjs.io/docs/webpack#mocking-css-modules
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
// CSS インポートの処理 (CSS modules を利用しない場合)
'^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
// 画像インポートの処理
// https://jestjs.io/docs/webpack#handling-static-assets
'^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i': `<rootDir>/__mocks__/fileMock.js`,
// モジュールのエイリアスの処理
'^@/components/(.*)$': '<rootDir>/components/$1',
},
// 各テストの実行前に渡すオプションを追加
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
testEnvironment: 'jsdom',
transform: {
// next/babel プリセットでテストをトランスパイルするために babel-jest を使用します
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
},
transformIgnorePatterns: [
'/node_modules/',
'^.+\\.module\\.(css|sass|scss)$',
],
}
各設定オプションの詳細については、Jest Docsを参照してください。
スタイルシートと画像のインポートの処理
スタイルシートと画像はテストでは使用しませんが、インポートした際にエラーの発生する可能性があるため、モックを作成する必要があります。上記の設定で参照したモックファイルである fileMock.js と styleMock.js を __mocks__ ディレクトリ内に作成します:
// __mocks__/fileMock.js
module.exports = {
src: '/img.jpg',
height: 24,
width: 24,
blurDataURL: 'data:image/png;base64,imagedata',
}
// __mocks__/styleMock.js
module.exports = {}
より詳しい静的なファイルの扱いについては、Jest Docsを参照してください。
参考: カスタムマッチャーによる Jest の拡張
@testing-library/jest-dom には .toBeInTheDocument() のような便利なカスタムマッチャーが含まれており、テストを簡単に書くことができます。Jest の設定ファイルに以下のオプションを追加することで、すべてのテストにカスタムマッチャーをインポートできます:
// jest.config.js
setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
そして、jest.setup.js の中に、以下のインポートを追加してください:
// jest.setup.js
import '@testing-library/jest-dom/extend-expect'
各テストの前にさらに設定オプションを追加する必要がある場合は、上記の jest.setup.js ファイルに追加するのが一般的です。
参考: 絶対パスでのインポートとエイリアスを利用したモジュールインポート
プロジェクトでエイリアスを利用したモジュールインポートを使用している場合、jsconfig.json ファイルの paths オプションと jest.config.js ファイルの moduleNameMapper オプションをマッチさせて、インポートを解決するように Jest を構成する必要があります。例えば:
// tsconfig.json or jsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/components/*": ["components/*"]
}
}
}
// jest.config.js
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
}
test script を package.json に追加する
watch モードの Jest コマンドを package.json の scripts に追加します:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "jest --watch"
}
jest --watch は、ファイルが変更されたときにテストを再実行します。その他の Jest CLI のオプションについては、Jest Docs を参照してください。
最初のテストを作成する
これで、あなたのプロジェクトにテストを実行する準備が整いました。Jest の規約に従って、プロジェクトのルートディレクトリにある __tests__ ディレクトリへテストを追加してください。
例えば、<Home /> コンポーネントが見出しの要素を正常にレンダリングするかどうかをチェックするテストを追加できます:
// __tests__/index.test.jsx
import { render, screen } from '@testing-library/react'
import Home from '../pages/index'
describe('Home', () => {
it('renders a heading', () => {
render(<Home />)
const heading = screen.getByRole('heading', {
name: /welcome to next\.js!/i,
})
expect(heading).toBeInTheDocument()
})
})
追加で、<Home /> コンポーネントへの予期せぬ変更を記録するために、スナップショットテストを追加します:
// __tests__/snapshot.js
import { render } from '@testing-library/react'
import Home from '../pages/index'
it('renders homepage unchanged', () => {
const { container } = render(<Home />)
expect(container).toMatchSnapshot()
})
備考: pages ディレクトリの中にあるファイルはすべてルートとみなされるため、pages ディレクトリの中にテストファイルを入れてはいけません。
テスト環境の実行
npm run test を実行して、テストを実行します。テストが成功または失敗した後、より多くのテストを実行する際に役立つ Jest コマンドのリストが表示されることに気づくでしょう。
さらに詳しい情報は、以下の資料が参考になります:
Next.js コミュニティでは、参考になるパッケージや記事を用意しています:
より多くの情報を得るため次に読むべきものとして、以下をお勧めします: