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

$ npx vitest run
$ npx vitest

vitestはViteベースの高速なユニットテストフレームワークです。 TypeScriptにネイティブ対応しているため、ts-jestのような別プラグインなしでそのまま使えます。

vitest runは1回だけ実行して終了します。 引数なしのvitestはウォッチモードで起動し、ファイルの変更を検知して自動的に再実行します。

インストールしたい

$ npm install --save-dev vitest @vitest/coverage-v8

vitest本体と、カバレッジ計測用の@vitest/coverage-v8devDependenciesに追加します。

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

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

testは1回実行、test:watchはウォッチモード、test:coverageはカバレッジ計測付きで実行します。 用途に応じて使い分けます。

設定したい(vitest.config.ts

 1import { defineConfig } from "vitest/config";
 2
 3export default defineConfig({
 4  test: {
 5    globals: true,
 6    environment: "node",
 7    include: ["**/__tests__/**/*.test.ts"],
 8    setupFiles: ["__tests__/setup.ts"],
 9    coverage: {
10      provider: "v8",
11      include: ["src/**/*.ts"],
12    },
13  },
14});

globals: trueにすると、describetestexpectviなどを importせずにそのままグローバルに使えるようになります。 jestから移行する場合、書き方を大きく変えずに済むので便利です。

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

includeでテスト対象のファイルパターンを、 setupFilesでテスト実行前に共通で読み込むファイルを指定します。

coverageでカバレッジ計測の設定ができます。 provider: "v8"は、Node.js(V8エンジン)に組み込まれたカバレッジ計測機能を使う設定です。

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

 1// GASのグローバルオブジェクトをモックする
 2/// <reference types="google-apps-script" />
 3
 4(global as any).Logger = {
 5  log: vi.fn(),
 6  clear: vi.fn(),
 7};
 8
 9(global as any).console = {
10  log: vi.fn(),
11  info: vi.fn(),
12  warn: vi.fn(),
13  error: vi.fn(),
14};

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

ヒント

すべてのGASサービスを一度にモックする必要はありません。 テスト対象のコードが実際に呼び出している範囲だけモックすれば十分です。

ユニットテストしたい

 1import { Counter } from "../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: 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でテスト対象をグループ化し、testitのエイリアス)で個々のテストケースを書きます。 describeは入れ子にできるので、以下のような構造を意識して作成するとよいです。

  • テストするクラス名

    • テストするメソッド1

      • 正常系テストたち

      • 異常系テストたち

    • テストするメソッド2

      • 正常系テストたち

      • 異常系テストたち

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

ヒント

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

リファレンス