ユニットテストしたい(jest

$ npx jest
$ npx jest --watch

jestはJavaScript用のユニットテストフレームワークです。 ゼロコンフィグに近い設定で使い始められます。

注釈

新しくTypeScriptのGASプロジェクトを始める場合は、 vitestのほうがTypeScriptにネイティブ対応していて設定も少なく済むためオススメです。

インストールしたい

$ npm install --save-dev jest

jest本体をdevDependenciesとして追加します。

スクリプト設定したい(package.json

{
    "name": "...",
    "scripts": {
        "test": "jest",
        "test:watch": "jest --watch",
        "test:coverage": "jest --coverage",
        "...": "..."
    }
}
$ npm test
$ npm run test:watch
$ npm run test:coverage

testは1回実行、test:watchはウォッチモード、test:coverageはカバレッジ計測付きで実行します。

設定したい(jest.config.js

 1/** @type {import('jest').Config} */
 2module.exports = {
 3    testEnvironment: "node",
 4    testMatch: ["**/__tests__/**/*.test.js"],
 5    setupFilesAfterEnv: ["<rootDir>/__tests__/setup.js"],
 6    collectCoverageFrom: [
 7        "src/**/*.js",
 8        "!**/node_modules/**",
 9        "!**/coverage/**",
10    ],
11};

testEnvironmentは、テストを実行する環境の指定です。 GASにはブラウザDOMがないため、nodeを指定します。

testMatchで対象となるユニットテスト用のファイルを設定します。 指定を忘れると、テスト以外のファイル(setup.jsなど)まで テストファイルとして扱われてエラーになることがあるので注意してください。

setupFilesAfterEnvで、Jest実行時に共通して読み込むファイルを設定できます。

collectCoverageFromでカバレッジ測定の対象とするファイルを指定します。

GASのグローバルをモックしたい(__tests__/setup.js

1// GASのグローバルオブジェクトをモックする
2global.Logger = {
3  log: jest.fn(),
4  clear: jest.fn(),
5};

DriveAppSpreadsheetAppLoggerのようなGASのグローバル変数は、 Node.js環境(jestの実行環境)には存在しません。 テストを実行する前に、setupFilesAfterEnvで指定したファイルの中で、 必要な分だけjest.fn()でモックしておく必要があります。

ユニットテストしたい

 1const { Counter } = require("../src/counter");
 2
 3describe("Counter", () => {
 4  describe("constructor", () => {
 5    test("should create counter with default settings", () => {
 6      const counter = new Counter();
 7      expect(counter.getValue()).toBe(0);
 8    });
 9  });
10
11  describe("increment", () => {
12    let counter;
13
14    beforeEach(() => {
15      counter = new Counter();
16    });
17
18    test("should increment the value", () => {
19      expect(counter.increment()).toBe(1);
20    });
21  });
22});

describeでテスト対象をグループ化し、testで個々のテストケースを書きます。 describeは入れ子にできるので、以下のような構造を意識して作成するとよいです。

  • テストするクラス名

    • テストするメソッド1

      • 正常系テストたち

      • 異常系テストたち

    • テストするメソッド2

      • 正常系テストたち

      • 異常系テストたち

beforeEachで、各テストの実行前に共通のセットアップ処理を行えます。

ヒント

テストケースを自分で考えるのは大変です。 最近はClaudeに、アップロードしたソースコードを読み込んでもらい、 それに対するユニットテストを作ってもらうようにしています。

TypeScriptに対応したい(ts-jest

$ npm install --save-dev ts-jest @types/jest

.tsファイルをそのままテストできるようにするts-jestと、 describetestexpectなどの型定義を提供する@types/jestを追加します。

 1const { createDefaultPreset } = require("ts-jest");
 2
 3const tsJestTransformCfg = createDefaultPreset().transform;
 4
 5/** @type {import('jest').Config} */
 6module.exports = {
 7    testEnvironment: "node",
 8    transform: {
 9        ...tsJestTransformCfg,
10    },
11    testMatch: ["**/__tests__/**/*.test.ts"],
12    setupFilesAfterEnv: ["<rootDir>/__tests__/setup.ts"],
13    collectCoverageFrom: [
14        "src/**/*.ts",
15        "!**/node_modules/**",
16        "!**/coverage/**",
17    ],
18};

transformは、.tsファイルをテスト実行前にコンパイルする設定です。 ts-jestが提供するcreateDefaultPreset()を使うのが現在の推奨方法です。 testMatchsetupFilesAfterEnvcollectCoverageFromの拡張子を.tsに変えるだけで、 JS版の設定とほぼ同じ形になります。

注意

ts-jestの設定方法はpreset: "ts-jest"と書く古い書き方をよく見かけますが、 最近のバージョンでは非推奨になっています。 createDefaultPreset()を使う書き方に変わっているので、 古い記事のサンプルをそのまま使うと警告が出ることがあります。

ヒント

tsconfig.jsontypesを明示的に絞っている場合、 "jest"を含めておかないとdescribetestexpectが型エラーになります。 またsetupFilesAfterEnv内でglobal(Node.jsのグローバルオブジェクト)を使う場合は、 "node"も忘れずに含めてください("types": ["jest", "node"])。

リファレンス