Jest + jsdomでCanvas要素を単体テストする

## はじめに

この記事は、Jest(jsdom)でContext2Dインターフェイスを利用するCanvas要素の単体テスト方法を記録、共有するためのものです。

### 想定する読者

この記事は、以下の読者を想定して書かれています。

-   JavaScriptの開発経験がある
-   Jestを使ったことがある

JavaScriptの解説やJestのインストールガイドはこの記事には含まれません。

### 想定する環境

この記事は、以下の環境を想定して書かれています。記事を読む前に、お手元の環境をご確認ください。

```console
$ node --version
v16.3.0
$ npm --version
7.15.1
```

また、この記事で扱うパッケージは以下の通りです。

_▼package.json_

```json
"devDependencies": {
  "canvas": "^2.8.0",
  "jest": "^27.0.4",
  "pixi.js-legacy": "^6.0.4",
}
```

WebGLの単体テストは想定していません。WebGLコンテンツをテストしたい場合、Puppeteerの導入を検討してください。

参考記事 : [JestでPuppeteerを動かしてCIで実行する](https://qiita.com/ozaki25/items/ea0138dfa537f51aabef)

## 先に結論だけ

https://github.com/MasatoMakino/minimal-test-environment-jest-pixijs

JestでCanvas要素をテストする最小限のリポジトリを作りました。

## JestとCanvas

[Jest](https://jestjs.io/ja/)は、ブラウザ環境を想定したテストのために[jsdom](https://github.com/jsdom/jsdom)を内包しています。jsdomとは[DOM](https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Introduction)をJavaScriptで実装したものです。Pure JavaScriptで実装されているため、node.js上で動作します。jsdomを通じて、ユーザーはテストに必要なDOMを組み立てて操作します。

### jsdomのCanvasサポート

jsdomはテストやスクレイピングを目的としています。そのためjsdom単体ではCanvas要素をサポートしていません。Canvas要素をDOMツリーに追加しようとすると、jsdomはdiv要素を代わりに挿入し、処理を簡略化します。

jsdomでCanvas要素をサポートする場合、[node-canvas](https://github.com/Automattic/node-canvas)を追加します。

[jsdom README : Canvas support](https://github.com/jsdom/jsdom#canvas-support)


> jsdom includes support for using the canvas package to extend any canvas elements with the canvas API. To make this work, you need to include canvas as a dependency in your project, as a peer of jsdom.
> jsdomには、canvasパッケージを使用して任意の canvas 要素をcanvas APIで拡張するためのサポートが含まれています。これを動作させるには、jsdom のピアとして、プロジェクトに canvas を依存関係として含める必要があります。

jsdomはnode-canvasがパッケージ内にインストールされていることを自動で認識します。ユーザーがなにか設定する必要はありません。node-canvasがインストールされている場合、JestはCanvasに関わる処理をnode-canvasに渡します。

### Canvasのテスト

それでは実際にCanvasをテストしてみます。今回はCanvas描画ライブラリとして[PixiJS](https://www.pixijs.com/)の2Dコンテキスト対応版[pixi.js-legacy](https://www.npmjs.com/package/pixi.js-legacy)を使います。

#### モジュールのインストール

プロジェクトにJestとnode-canvas、pixi.js-legacyをインストールします。

```bash
npm install --save-dev jest canvas pixi.js-legacy
```

##### npm v7以降の場合

npm v7から、peerDependenciesに指定されたモジュールが自動的にインストールされるようになりました。jsdomはpeerDependenciesにnode-canvasを指定しています。そのため、node-canvasを個別にインストールする必要はありません。

#### テストの作成

テストファイルを作成します。

_▼./\_\_test\_\_/Test.spec.ts_

```js
jest.spyOn(console, "error").mockImplementation()

describe("Test", () => {
    const PIXI = require("pixi.js-legacy");
    jest.spyOn(console, "log").mockImplementation((x) => x);

    const app = new PIXI.Application({
        width: 640,
        height: 480,
        forceCanvas: true,
    });
    document.body.appendChild(app.view);

    test("app should be exist", () => {
        expect(app).toBeTruthy();
    })
})
```

#### テスト環境を指定して実行

Jestの設定ファイルを作成し、環境に`jsdom`を指定します。

_▼jest.config.js_

```js
module.exports = {
  testEnvironment: "jsdom",
};
```

もしくは、jestコマンドに`--env=jsdom`オプションを追加します。

_▼package.json_

```json
"scripts": {
  "test": "jest --env=jsdom"
}
```

#### テスト結果

```console
> minimal-test-environment-jest-pixijs@0.1.0 test
> jest --env=jsdom

 PASS  __test__/Test.spec.js
  Test
    ✓ app should be exist (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.393 s
Ran all test suites.

プロセスは終了コード 0 で終了しました
```

テストがパスすれば成功です。

## GitHub Actions

node-canvasはGitHub Actions上でも動作します。ubuntu-latest上でaptコマンドを使ってパッケージを追加するなどの作業は必要ありません。

https://github.com/actions/starter-workflows/blob/main/ci/node.js.yml

たとえばnode.js用のテンプレートを使えば、そのままテストがGithub Actions上で動作します。

以上、ありがとうございました。