【 Next.js 12.1 + TypeScript 】next/jest と E2Eテスト の Cypress を共存させてミル貝。
少しのことにも、先達はあらまほしき事なり。
(ちょっとしたことにも、先達はあってほしいものである。)
―― 兼好法師『徒然草』 第五十二段[1]
この記事は何でしょうか?
TypeScript 環境で Next.js 12.1 の ユニットテスト用の next/jest
プラグインと E2E テスト の Cypress を共存させてみます。
Windows 11 の WSL 環境(Ubuntu 20.04 WSLg も使用)での設定に関しても触れます。
題材としては、以前、Zenn にも書きました、React チュートリアルの三目並べを Next.js + TypeScript + Recoil の構成で作り変えたものに Jest と Cypress を追加して動かします。
全体のコード(制作したもの)
バージョン情報
Node.js 16.11.0
"next": "^12.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"recoil": "^0.6.1"
"@herp-inc/eslint-config": "^0.13.0",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@types/node": "^17.0.21",
"@types/react": "^17.0.40",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"cypress": "^9.5.1",
"eslint": "8.10.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^16.1.1",
"eslint-config-next": "12.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-cypress": "^2.12.1",
"jest": "^27.5.1",
"prettier": "^2.5.1",
"start-server-and-test": "^1.14.0",
"typescript": "^4.6.2"
記事を書いた動機。概要。
先日(2022年2月18日)、Next.js 12.1 で単体テスト(Jest)に関する新機能が追加されました。
Zero-configuration Jest plugin
Setting up Jest (with the Rust Compiler)
これにより、Jest の設定がいままでよりシンプルにできるようになりました。
Zenn でもすでに解説されている記事があります。
先行記事
Next.js 12でJestの設定がかなり楽になった
Vercel のこの Zero-configuration Jest plugin の発表をみて思ったのは、
「TypeScript で この
next/jest
プラグインとE2Eテストの Cypress と組み合わせてうまく動作するのだろうか」
ということでした。
以前、仕事で、TypeScript 環境で、Jest と Cypress を共存させる設定で苦労した記憶があるからです。
今回、改めて、Next.js 12.1 の next/jest
プラグインと Cypress を共存させるという技術調査をした結果、以下の技術的課題が浮き彫りとなりました。
解決が必要な課題
- Windows 11 での WSL2、WSLg の Ubuntu 20.04 で Cypress の GUI を動作させるための設定
- Jest と Cypress の 競合問題。
- キーワードが重複していることによる TypeScript(
tsconfig.json
) ESLint(.eslintrc.json
)の設定の問題
- キーワードが重複していることによる TypeScript(
一点ごとに解説していきます。
WSL2上で Cypress を動作させようとして発生するエラー
(※詳細は後で記載いたします。)
WSL 環境で、Cypress をインストールして、cypress open
を実行します。
(下では、package.json
で yarn run cy:open
で cypress open
が実行されるようにしています。)
すると、以下のようなエラーが発生します。
❯ yarn run cy:open
yarn run v1.22.17
$ cypress open
It looks like this is your first time using Cypress: 9.5.1
Cypress failed to start.
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
Please refer to the error below for more details.
----------
/home/mumei/.cache/Cypress/9.5.1/Cypress/Cypress: error while loading shared libraries: libxshmfence.so.1: cannot open shared object file: No such file or directory
----------
Platform: linux-x64 (Ubuntu - 20.04)
Cypress Version: 9.5.1
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
このエラーに関して、二点、問題があります。
- WSL 上で GUI を動かす問題
- Cypress に依存しているパッケージ問題
一点目。
Cypress は GUI を内包しています。
わかりやすい GUI で 直観的に、E2E テストを行うこともできます。
しかし、WSL で、GUI ツールである、Cypress を実行することは以前は困難でした。
詳細な手順は以下の記事にあります。
ところが、喜ばしいことに、Windows 11 で Linux GUI アプリケーションを動作させるための WSLg というシステムが登場しました。
今回は、Windows 11 の WSLg を利用して、Cypress の GUI を動作させてみます。
二点目、Cypress に依存しているパッケージ問題については、Cypress 公式を参考に、Ubuntu で必要なパッケージをインストールします。
Jest と Cypress の 競合問題。
Jest と Cypress を共存させる例については、Cypress 公式に例があります。
cypress-and-jest-typescript-example
上の cypress-and-jest-typescript-example の README にも書かれていますが、
Jest と Cypress は競合している部分があります。
そのため、TypeScript 環境で2つを共存させるときに、工夫が必要になります。
両者の何が競合しているのでしょうか。
具体的には、 expect
などのキーワードが競合しています。
名前は同じ expect
なのですが、中身は別物なのです。
試しに、Visual Studio Code で Jest の expect
をホバーすると、以下のような型情報が表示されます。
const expect: jest.Expect
<SquareValueType>(actual: SquareValueType) => jest.JestMatchers<SquareValueType>
Cypress の expect
をホバーすると、以下のような型情報が表示されます。
const expect: Chai.ExpectStatic
(val: any, message?: string | undefined) => Chai.Assertion
Visual Studio Code で見てもわかる通り、Jest の expect
は、Jest の expect
ですが、
Cypress の expect
は Chai ベースであることがわかります。
名前は同じですが、別物であることがわかります。
このようなキーワードの競合があるため、Jest と Cypress で型設定(tsconfig.json
など)は分離する必要があります。
また、それに伴い、ESLint の設定も分離させる必要があります。(テストコードも Lint の対象にする場合)
最終的なディレクトリ構成
Jest と Cypress の 競合問題があるため、 Jest のテストコードと Cypress のテストコードはディレクトリを分けます。
Cypress は <rootDir>/cypress
配下、Jest は、<rootDir>/src/__tests__
配下にファイルを配置します。
最終的なディレクトリ構成は以下のようになりました。
.
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ ├── 1-getting-started
│ │ │ └── todo.spec.ts
│ │ ├── 2-advanced-examples
│ │ │ ├── actions.spec.ts
│ │ │ ├── ……(以下 サンプルファイル 略)……
│ │ └── tic-tac-toe
│ │ └── tic-tac-toe.spec.ts(Cypress E2Eテスト)
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ ├── support
│ │ ├── commands.js
│ │ └── index.js
│ ├── tsconfig.json
│ └── videos
│ └── tic-tac-toe
│ └── tic-tac-toe.spec.ts.mp4
├── cypress.json
├── jest.config.js
├── LICENSE
├── next.config.js
├── next-env.d.ts
├── package.json
├── package-lock.json
├── public
│ ├── favicon.ico
│ └── vercel.svg
├── README.md
├── src
│ ├── components
│ │ ├── Board.tsx
│ │ ├── GameHistory.tsx
│ │ ├── Game.tsx
│ │ └── Square.tsx
│ ├── infrastructure
│ │ └── recoil
│ │ ├── historyAtom.ts
│ │ ├── index.ts
│ │ ├── stepNumberState.ts
│ │ └── xIsNextAtom.ts
│ ├── pages
│ │ ├── api
│ │ │ └── hello.tsx
│ │ ├── _app.tsx
│ │ └── index.tsx
│ ├── styles
│ │ ├── globals.css
│ │ └── Home.module.css
│ ├── __tests__
│ │ └── useCases
│ │ └── calculateWinner.spec.ts(Jest のテスト)
│ ├── @types
│ │ └── global.d.ts
│ └── useCases
│ └── calculateWinner.ts
├── tsconfig.json
└── yarn.lock
Jest の設定
この部分は、先行記事と重複する部分があります。
Next.js 12.1 の next/jest
を利用するために、Next.js を Upgrade します。
yarn upgrade next
一応、package.json
で、バージョンが12.1になったことを確認しておきます。
続いて、Jest のインストールです。
yarn add -D jest @testing-library/react @testing-library/jest-dom
jest.config.js の設定。
Next.js 12.1 の next/jest
を利用した Jest の設定です。
Jest と Cypress の競合問題があるため、Jest を実行するルート(ここでは、roots: ['<rootDir>/src']
)を明記します。
これは、Jest が、<rootDir>/cypress
配下の Cypress のテストコードを無視するようにするためです。
また、tsconfig.json
のエイリアス設定に合わせて、エイリアスの設定もします。
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './'
})
// Add any custom config to be passed to Jest
const customJestConfig = {
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
+ roots: ['<rootDir>/src'],
moduleDirectories: ['node_modules', '<rootDir>/'],
// エイリアスの設定。
moduleNameMapper: {
'^@/(.+)$': '<rootDir>/src/$1'
},
testEnvironment: 'node'
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)
また、jest.config.js
は TS 化するとうまく動作しませんでした。
(なにか設定があるかもしれませんが……)
ESLint にもひっかかるので、今回は、ignore しました。
{
"extends": ["next/core-web-vitals", "airbnb-typescript", "@herp-inc", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "import"],
+ "ignorePatterns": ["jest.config.js"],
"rules": {
"import/no-unassigned-import": ["error", { "allow": ["**/*.css"] }]
}
}
tsconfig.json
の設定。
ルートの ここでも、Jest と Cypress のキーワードの競合問題を考慮する必要があります。
-
<rootDir>/src
配下の Jest のテストコードの型エラーの回避- これは、グローバルに定義されている Cypress の型情報が、Jest のキーワードに適用されることによって生じます。
- Cypress E2E テストコードの
<rootDir>/cypress
配下のファイルをコンパイル対象から除外。
一点目、<rootDir>/src
配下の Jest のテストコードの型エラーについて。
下記キャプチャは tsconfig.json
調整前の Jest のテストコードです。
Jest の toBe
アサーションについて「プロパティ 'toBe' は型 'Assertion' に存在しません。」というエラーが発生しています。
本来は expect
に Jest の型が適用されるべきところに、Chai の型が適用されてしまった結果、エラーが生じてしまいました。
ここは、Jest の型を適用する必要があります。
そこで、"types": ["jest"]
を設定し, include
で、コンパイル対象に <rootDir>/src
配下を指定しました。
これにより、<rootDir>/src
配下に Jest の型情報が適用され、上記のエラーは解消します。
そして、コンパイル対象でない Cypress の E2E テストコードがコンパイル対象から外れます。
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
+ "types": ["jest"],
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}
ここまで、設定して、一旦、Jest で簡単なテストを書いて動かしてみます。
テスト対象は、三目並べ勝者判定の関数 calculateWinner
です。
package.json
で yarn jest
で Jest のテストが実行されるように、設定します。
src/__tests__/useCases/calculateWinner.spec.ts
にテストを書いていきます。
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
+ "jest": "jest",
……(略)……
},
import { calculateWinner } from '@/useCases/calculateWinner'
describe('三目並べ勝者判定の関数 calculateWinner', (): void => {
test('勝者なし(引き分け)', (): void => {
const winner = calculateWinner(['X', 'X', 'O', 'O', 'O', 'X', 'X', 'O', 'X'])
expect(winner).toBeNull()
})
test('上横一列で先手(X)勝ち。', (): void => {
const winner = calculateWinner(['X', 'X', 'X', 'O', 'O', null, null, null, null])
expect(winner).toBe('X')
})
test('下横一列で後手(O)勝ち。', (): void => {
const winner = calculateWinner(['X', null, null, 'X', 'X', null, 'O', 'O', 'O'])
expect(winner).toBe('O')
})
})
yarn jest
でテストを実行すると、以下のように Jest のテストが成功しました。
❯ yarn jest
yarn run v1.22.17
$ jest
PASS src/__tests__/useCases/calculateWinner.spec.ts
三目並べ勝者判定の関数 calculateWinner
✓ 勝者なし(引き分け) (3 ms)
✓ 上横一列で先手(X)勝ち。
✓ 下横一列で後手(O)勝ち。
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 0.2 s, estimated 1 s
Ran all test suites.
Done in 1.26s.
Cypress の設定。
Windows 11 で WSLg を利用できるようにする。
これに関しては、別に記事に解説を譲ります。
例えば、以下の記事などです。
動作確認として、xeyes
というアプリをインストールします。
sudo apt install x11-apps
コマンド
xeyes
の実行で、gif のように、GUI が立ち上がり、カーソルの動きに合わせて目玉が動けば OK です。
Cypress 依存パッケージのインストール
以下の Cypress のドキュメントを参考に、依存パッケージをインストールします。
Dependencies Introduction | Cypress Documentation
その前に、Ubuntu のパッケージリストの更新をします。
sudo apt-get update
sudo apt-get upgrade
筆者の環境は、 Ubuntu なので、Ubuntu で必要なパッケージをインストールします。
コマンドは、ここにも記載しますが、公式の最新のドキュメントで必要とされているものをインストールしていただければと思います。
apt-get install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
Cypress のインストール
ここで、Cypress をインストールします。
yarn add cypress --dev
正常にインストールされたか確認します。
npx cypress verify
✔ Verified Cypress! /home/XXXX/.cache/Cypress/9.5.1/Cypress
「✔ Verified Cypress!」というメッセージがでれば、OK です。
Cypress は正常にインストールされました。
Cypress の初期化
cypress open
で Cypress の初期設定をします。
package.json
で yarn run cy:open
で cypress open
が実行されるように設定します。
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "eslint src --config .eslintrc.json --ext .js,jsx,.ts,.tsx --fix",
"lint:cypress": "eslint cypress --config cypress/.eslintrc.json --ext .js,jsx,.ts,.tsx --fix",
"prettier": "prettier --write --ignore-path .gitignore './**/*.{js,jsx,ts,tsx,json,css}'",
+ "cy:open": "cypress open",
"test": "jest",
"format": "yarn run prettier && yarn run lint:fix"
},
yarn run cy:open
を実行すると、以下のような GUI が立ち上がれば成功です。
tsconfig.json
に依存した ESLint エラー
ここで、cypress/integration
配下で大量のエラーが発生します。
Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: cypress/integration/1-getting-started/todo.spec.js.
The file must be included in at least one of the projects provided.eslint
こちらのエラーメッセージの意味は、
『 .eslintrc.json
の parserOptions
の project
で、tsconfig.json
を読み込んでますよね?
で、その対象ファイルの設定で、
"include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx"],
でなってますよね?
でも、このファイルは、cypress/integration/1-getting-started/todo.spec.js
で、上の、「"next-env.d.ts", "src/**/*.ts", "src/**/*.tsx"
」 のどのパターンとも合致しないんですけど?』
ということになります。
したがって、実際のファイル構成(新規に追加された Cypress の JavaScript ファイル群)と、.eslintrc.json
の parserOptions
の project
の対象ファイルの設定を合致させる必要があります。
ここでいろいろな方針が考えられますが
今回は、
-
新規に追加する Cypress のテストコードも TypeScript で書き、ESLint の対象に含める。
- (見本用に残しておくテストコード、ボイラープレートの JS は、ESLint の ignorePatterns で妥協。)
とします。
これは、テストコードにも TypeScript の型チェックと ESLint を効かせ、テストコードの内部品質を保つ意図です。
また、これにより、.eslintrc.json
の設定もシンプルになります。
そして、cypress open
のコマンドで実行した際に、生成された見本のテストコードは、一旦は、動作確認用として残しておくことにします。
これらの JavaScript コードを TypeScript 化していきます。(拡張子を.js
から.ts
に変えていきます。)
当プロジェクトでは以下のファイル群です。
cypress/integration/1-getting-started 配下
cypress/integration/2-advanced-examples 配下
'--isolatedModules' のエラー
すると、今度は、以下のようなエラーが出ます。
'todo.spec.ts' は、グローバル スクリプト ファイルと見なされるため、
'--isolatedModules' でコンパイルすることはできません。
import、export、または空の 'export {}' ステートメントを追加して、
これをモジュールにしてください。ts(1208)
「'--isolatedModules'
でコンパイルすることはできません。」とメッセージがでます。
これについて下記のページに解説があります。ここでも簡単に意味を解説いたします。
ts-jestでcannot be compiled under '--isolatedModules'と出た時の対処法
このエラーが起こる原因は、tsconfig.json
の isolatedModules
オプションが true
になっているからです。
isolatedModules
については、以下に公式のリンクを貼ります。
isolatedModules
- TypeScript: TSConfig Reference - Docs on every TSConfig option
isolatedModules
オプションとは、エラーメッセージ通り、「コンパイルできない」というエラーです。
どういう場合にでしょうか。
そのファイル単一で見た場合にコンパイルできない場合にです。
具体的に見ますと、例えば、
cypress/integration/1-getting-started/todo.spec.ts
のファイルを見れば、describe
などの Cypress のボイラープレートがあります。
これは、Cypress 本体に定義されているものです。
したがって、この todo.spec.ts
だけ単体でみても TypeScript のコンパイラはこの describe
がなにか判断できません。
これが、「'--isolatedModules'
でコンパイルすることはできません。」の意味です。
cypress/tsconfig.json、cypress/.eslintrc.json の追加
さて、現在、判明している問題は、
- 上記の Parsing error
- Jest と Cypress のキーワード競合
- isolatedModules のエラー
です。
上記にリンクを掲載した cypress-io/cypress-and-jest-typescript-example
ではどうしているのかといいますと、
<rootDir>/cypress
配下に、Cypress のテストコード用の、cypress/tsconfig.json
を用意しているようです。
そこで、今回は、
cypress/tsconfig.json
cypress/.eslintrc.json
と2つのファイルをおいて、TS と ESLint の設定をすることにしました。
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"isolatedModules": false,
// be explicit about types included
// to avoid clashing with Jest types
"types": ["cypress"]
},
"include": ["../node_modules/cypress", "**/*.ts"]
}
{
"extends": ["next/core-web-vitals", "plugin:cypress/recommended", "airbnb-typescript", "@herp-inc", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./cypress/tsconfig.json",
"sourceType": "module"
},
// https://github.com/cypress-io/eslint-plugin-cypress
"plugins": ["@typescript-eslint", "import", "cypress"]
}
cypress/.eslintrc.json
を配置することにより、<rootDir>/cypress
配下では、cypress/.eslintrc.json
の設定が優先して適用されるようになります。
また、cypress/.eslintrc.json
では、parserOptions.project
で cypress/tsconfig.json
を参照するようにします。
-
上記の Parsing error について。
拡張子を.ts
にしますと、cypress/tsconfig.json
のinclude
のパターンと合致するようになるで解消します。 -
Jest と Cypress のキーワード競合
cypress/tsconfig.json
のcompilerOptions
に"types": ["cypress"]
を設定しました。
これにより、<rootDir>/cypress
配下で、expect
などのキーワードに明示的に、Cypress の型が適用されるようになります。
Cypress 公式の TypeScript での設定のページを参考にしました。
- isolatedModules のエラーについて。
これは上でも述べたように、「コンパイルできない」というエラーです。
しかし、<rootDir>/cypress
配下のテストコードは、コンパイルの必要がありません。
なので、cypress/tsconfig.json
で"isolatedModules": false
を設定すれば、isolatedModules
のエラーは解消します。
その他、
Cypress 公式が作っている、Cypress ESLint Plugin を今回見つけたので、 ESLint 設定を入れてみました。
Cypress ESLint Plugin
インストール
yarn add eslint-plugin-cypress --dev
設定の仕方は、公式の README や上記の cypress/.eslintrc.json
を見ていただければと思います。
package.json
で ESLint コマンド追加、動作確認
続いて、package.json
で ESLint 用のコマンドを修正します。
-
yarn lint:fix
で<rootDir>/src
配下の Lint を ルートの.eslintrc.json
の設定 -
yarn lint:fixCypress
で<rootDir>/cypress
配下の Lint をcypress/.eslintrc.json
の設定
で行うようにします。
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
- "lint:fix": "eslint src --ext .js,jsx,.ts,.tsx --fix",
+ "lint:fix": "eslint src --config .eslintrc.json --ext .ts,.tsx --fix",
+ "lint:fixCypress": "eslint cypress --config cypress/.eslintrc.json --ext .ts,.tsx --fix",
"prettier": "prettier --write --ignore-path .gitignore './**/*.{js,jsx,ts,tsx,json,css}'",
"cy:open": "cypress open",
"jest": "jest",
- "format": "yarn run prettier && yarn run lint:fix"
+ "format": "yarn run prettier && yarn run lint:fix && yarn run lint:fixCypress"
},
ここで、yarn lint:fixCypress
を実行して、ESLint の動作確認をしてみます。
❯ yarn lint:fixCypress
yarn run v1.22.17
$ eslint cypress --config cypress/.eslintrc.json --ext .ts,.tsx --fix
(略)
/home/XXXXX/ZZZZZZZZ/cypress/integration/2-advanced-examples/viewport.spec.ts
29:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
31:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
33:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
35:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
37:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
39:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
41:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
43:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
45:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
47:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
52:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
54:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
/home/XXXXX/ZZZZZZZZ/cypress/integration/2-advanced-examples/waiting.spec.ts
13:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
15:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
17:5 error Do not wait for arbitrary time periods cypress/no-unnecessary-waiting
✖ 94 problems (94 errors, 0 warnings)
正常に、<rootDir>/cypress
配下への ESLint が動作していることが確認できました。
新規に追加した Cypress ESLint Plugin も効いていることも確認できました。
ただし、今回は、上に書いたように、見本用に残しておくテストコード、ボイラープレートの JS 等は、ESLint の ignorePatterns で ignore します。
{
"extends": ["next/core-web-vitals", "plugin:cypress/recommended", "airbnb-typescript", "@herp-inc", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./cypress/tsconfig.json",
"sourceType": "module"
},
+ "ignorePatterns": ["support", "plugins", "integration/1-getting-started", "integration/2-advanced-examples"],
// https://github.com/cypress-io/eslint-plugin-cypress
"plugins": ["@typescript-eslint", "import", "cypress"]
}
これで、TypeScript(tsconfig.json
) ESLint の .eslintrc.json
の設定は一旦終わりです。
Cypress で実際にテストを動かす。
まず、E2E テストのためには、テストコード側で DOM を取得する必要があります。
これには、Cypress 公式にベストプラクティスがあります。
それは、テスト用にdata-cy
などのデータ属性を設定することです。
下準備としてこれを行います。
export const Square: React.FC<SquarePropsType> = ({ value, squareIndex, onClick }) => {
return (
- <button className="square" onClick={onClick}>
+ <button className="square" onClick={onClick} data-cy={`square_${squareIndex}`}>
{value}
</button>
)
}
return (
<div className="game-info">
- <div>{status}</div>
+ <div data-cy="winner_status">{status}</div>
<ol>{moves}</ol>
</div>
)
デベロッパーツールでみると下のような感じになります。
ここで設定したデータ属性で E2E テストを書いていきます。
/**
* @description 三目ならべの盤面のコンポーネント
*/
describe('三目ならべ', () => {
beforeEach(() => {
cy.visit('http://localhost:3000/')
})
it('次の手番は、先手(X)', () => {
cy.get('[data-cy=square_0]').click()
cy.get('[data-cy=square_1]').click()
cy.get('[data-cy=winner_status]').should('have.text', 'Next player: X')
})
it('次の手番は、後手(O)', () => {
cy.get('[data-cy=square_0]').click()
cy.get('[data-cy=winner_status]').should('have.text', 'Next player: O')
})
it('先手(X)上横一列勝ち', () => {
cy.get('[data-cy=square_0]').click()
cy.get('[data-cy=square_3]').click()
cy.get('[data-cy=square_1]').click()
cy.get('[data-cy=square_4]').click()
cy.get('[data-cy=square_2]').click()
cy.get('[data-cy=winner_status]').should('have.text', 'Winner: X')
})
it('後手(O)下一列勝ち', () => {
cy.get('[data-cy=square_0]').click()
cy.get('[data-cy=square_6]').click()
cy.get('[data-cy=square_3]').click()
cy.get('[data-cy=square_7]').click()
cy.get('[data-cy=square_1]').click()
cy.get('[data-cy=square_8]').click()
cy.get('[data-cy=winner_status]').should('have.text', 'Winner: O')
})
})
ここで、yarn dev
などで、サーバーを立ち上げた状態で、yarn cy:open
して、テストを実行します。
正常にテストが行われていることが確認できました。
(※) WSL Ubuntu で Cypress を実行して日本語が文字化けする場合
上記のテストの実行で、日本語が「□□□」のように、うまく表示されない場合があります。
「豆腐フォント」とも言われる現象です。
その場合は、Ubuntu の日本語の設定をします。
sudo apt-get update
sudo apt-get install -y locales locales-all
sudo apt-get remove fonts-vlgothic
sudo apt-get install -y fonts-vlgothic
sudo locale-gen ja_JP.UTF-8
sudo localedef -f UTF-8 -i ja_JP ja_JP.utf8
CI を見据えた cypress run の設定。(start-server-and-test 使用)
AWS Code build などで Cypress で E2E テストを行うとき、
- サーバー( http://localhost:3000/ 等)を起動する。
- サーバーの起動したら、Cypress で E2E テストを行う。
という手順を得る必要があります。
それを実現するのが、start-server-and-test というライブラリです。
Cypress 公式でも紹介されています。
三目ならべのみをテストしたい場合は以下のようになります。
"scripts": {
……
+ "ci:cy": "start-server-and-test 'yarn build && yarn start' http://localhost:3000 'cypress run --spec 'cypress/integration/tic-tac-toe/**''",
……
},
実際にコマンド実行してみると、
- Next.js をビルドしたあと、
- サーバーを立ち上げ、
- その後に、三目ならべの E2E テストを行う、
という挙動が実現できているということを確認できました。
start-server-and-test の詳細な使用方法については、上記に掲載した start-server-and-test 公式などの解説をご参照いただければ幸いです。
Jest と Cypress のテストを連続して実行。
最後に、Jest のユニットテストと Cypress の E2E テストを連続して実行してみます。
yarn test
コマンドで行います。
"scripts": {
……
"cy:open": "cypress open",
"cy:run": "cypress run",
"jest": "jest",
"ci:cy": "start-server-and-test 'yarn build && yarn start' http://localhost:3000 'cypress run --spec 'cypress/integration/tic-tac-toe/**''",
+ "test": "yarn jest && yarn ci:cy",
……
},
実行すると以下のような形となり成功しました。
(一部フォルダ名を変更。)
「 yarn test 」の実行結果を click で展開します。
❯ yarn test
yarn run v1.22.17
$ yarn jest && yarn ci:cy
$ jest
PASS src/__tests__/useCases/calculateWinner.spec.ts
三目並べ勝者判定の関数 calculateWinner
✓ 勝者なし(引き分け) (10 ms)
✓ 上横一列で先手(X)勝ち。
✓ 下横一列で後手(O)勝ち。 (1 ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 0.872 s, estimated 1 s
Ran all test suites.
$ start-server-and-test 'yarn build && yarn start' http://localhost:3000 'cypress run --spec 'cypress/integration/tic-tac-toe/**''
1: starting server using command "yarn build && yarn start"
and when url "[ 'http://localhost:3000' ]" is responding with HTTP status code 200
running tests using command "cypress run --spec cypress/integration/tic-tac-toe/**"
$ next build
info - Checking validity of types
info - Creating an optimized production build
info - Compiled successfully
info - Collecting page data
info - Generating static pages (3/3)
info - Finalizing page optimization
Page Size First Load JS
┌ ○ / 6.06 kB 99.2 kB
├ └ css/4fbf8bdb1226e764.css 668 B
├ /_app 0 B 93.2 kB
├ ○ /404 194 B 93.4 kB
└ λ /api/hello 0 B 93.2 kB
+ First Load JS shared by all 93.2 kB
├ chunks/framework-e70c6273bfe3f237.js 42 kB
├ chunks/main-a054bbf31fb90f6a.js 27.6 kB
├ chunks/pages/_app-fde71f0e06f79000.js 22.7 kB
├ chunks/webpack-69bfa6990bb9e155.js 769 B
└ css/79aa2f09e4a3fce4.css 421 B
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
○ (Static) automatically rendered as static HTML (uses no initial props)
$ next start
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 9.5.1 │
│ Browser: Electron 94 (headless) │
│ Node Version: v16.11.0 (/home/XXXX/.nodenv/versions/16.11.0/bin/node) │
│ Specs: 1 found (tic-tac-toe/tic-tac-toe.spec.ts) │
│ Searched: cypress/integration/tic-tac-toe/tic-tac-toe.spec.ts │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: tic-tac-toe/tic-tac-toe.spec.ts (1 of 1)
三目ならべ
✓ 次の手番は、先手(X) (713ms)
✓ 次の手番は、後手(O) (215ms)
✓ 先手(X)上横一列勝ち (527ms)
✓ 後手(O)下一列勝ち (640ms)
4 passing (3s)
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 4 │
│ Passing: 4 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: true │
│ Duration: 3 seconds │
│ Spec Ran: tic-tac-toe/tic-tac-toe.spec.ts │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- Started processing: Compressing to 32 CRF
- Finished processing: /home/XXXX/XXXXX/cypress/videos/tic-tac-toe/tic-tac-toe.spec.ts.mp4
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ tic-tac-toe/tic-tac-toe.spec.ts 00:03 4 4 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! 00:03 4 4 - - -
Done in 78.96s.
これで当初の目標である、
Next.js 12.1 の next/jest プラグインと E2E テスト の Cypress を共存させてみる
(Windows 11 の WSL 環境(WSLg も使用)で)
が実現できました。
結語
この記事を執筆するにあたって、複数のテストに関する記事を参照しました。
「E2E テストは最小限にする」という趣旨のものが多かったです。(例えば新しいものでは下記の記事です)
ただ、E2E テスト自体を否定しているようなものは、私の観測範囲では、なかったです。
この記事は、私が遭遇したエラーの備忘録としての面があります。
なので、記述がまどろっこしい部分もあるかもしれませんが、なにかのヒントになれば幸いに存じます。
その他参考にした記事
Next.js プロジェクトに Cypress を導入して GitHub Actions で E2E テストをする
Cypress on Docker で自動テストすると日本語が文字化ける
Discussion