kintoneプラグイン開発でVitestを導入しユニットテストを実践する

kintoneプラグイン開発でVitestを導入しユニットテストを実践する


Vitestによるユニットテストを導入したkintoneプラグイン作成用のテンプレートを作成しました!とにかく動かしたい方はリポジトリをクローンなりフォークしてREADME通りコマンドを叩いてみてください!

この記事ではkintoneプラグイン開発・カスタマイズにおける面倒をVitestを導入することで解決することを目指します。

課題

プラグイン開発は通常以下のフローでするかと思います。(順番の前後はあると思いますが)

  1. 検証アプリを作成する
  2. テストレコード、テスト用プラグイン設定を用意する
  3. コードを書く
  4. コードをビルドし、検証アプリにアップロードする
  5. DevToolsでデバッグする
  6. 2-5を繰り返す

個人的にこのフローにおける面倒は以下です。

  • 検証アプリ、テストレコード、テスト用プラグイン設定の用意
  • デバッグしたいときは都度ビルドとアップロードが必要

事前にCSVでテストレコード用意、プラグインに設定JSON読み書き機能つける、ビルド&アップロードはスクリプト組んでwatchオプションつけるなど工夫はありますが、エディタ⇔ブラウザ間の行き来は面倒です。

これをVitestによって以下のように解決できます。

  • テストレコード、テスト用プラグイン設定をコードで書く(つまりCopiotの支援を受けることができる)
  • ビルド不要でエディタ上でデバッグできる

また、ユニットテストを導入することで以下の嬉しいこともあります。

  • ユニットテストをできるようにコードを書く必要があるので関数が単一責務になる
  • テストコードがドキュメントになる

ユニットテストありなしを比較すると以下のようになります

ユニットテストなし

ユニットテストあり

手動でのテストが必要

自動でテストが実行される

テストレコード、テスト用プラグイン設定を手動で用意

テストレコード、テスト用プラグイン設定をCopilotが生成

ビルドとアップロードが都度必要

ビルド不要でエディタ上でデバッグ可能

変更が動作に影響しないか手動確認が必要

変更時に自動で影響を検知できる

コードがテストしにくい構造になりがち

単一責務の関数を意識した設計になる

設計書などコードとは別にドキュメントが必要

テストコードがそのままドキュメントになる

では補足していきます。

ユニットテストとは

ユニットテストとは、プログラムの最小単位(関数やメソッド)が意図したとおりに動作するかを確認するテスト手法です。厳密な定義や詳細な解説については、専門書籍などを参照することをおすすめします。

Vitestとは

Vitestは、Viteをベースにした高速なユニットテストフレームワークです。詳細な仕様については公式ドキュメントを参照してください。ほぼインストールするだけ環境できるので楽です。

VitestによりNode.jsによるランタイムでコードを実行できるため、VSCodeなどのエディタ内でデバッグまで可能になります。また、テストレコードなどはコードとして記述が必要ですが、ここはCopilotによって楽に作成できます。

ここでCopilotの生成精度を高めるために、TypeScriptでコードを書くことが重要です。@kintone/rest-api-clientの型情報を使うことで、Copilotがkintoneのレコードオブジェクトなどを生成しやすくなります。TypeScriptの導入などは前回の記事をご参照ください。

実践

実際にどのようにユニットテストを実践するかを、テンプレートリポジトリを元に解説します。環境構築については package.json を参照してください。

プラグインの機能は以下の通りです。これはテンプレート用のプラグインで、特に用途は想定していません。

  • トリガー:レコード一覧画面でボタンをクリック
  • 対象:一覧の絞り込み条件に該当するレコード
  • 機能(MessageServiceクラス)
    • レコードを取得する(fetchRecordsメソッド)
    • 指定したフィールドの値を連結する(generateMessagesメソッド)
    • アラートメッセージとして出力

テストコードの解説

以下では generateMessages メソッドのテストコードを元に、ユニットテストの仕組みを解説します。また、適宜 モック(Mock) についても補足します。

モックとは?

モック(Mock) とは、テスト対象のコードが依存する外部の機能(例えばAPIやデータベース)を、テスト用のダミーオブジェクトで置き換える手法です。モックを使用することで、外部システムに依存せずにユニットテストを実行できます。

例えば fetchRecords メソッドは kintone REST API を利用してレコードを取得しますが、テスト時に実際のAPIを呼び出してしまうと、環境によって結果が変わる可能性があります。そこで、mockkintoneSdk.getRecords をモック化し、テスト用のデータを返すようにしています。

generateMessages.test.ts の解説

1. テストのセットアップ

src/desktop/service/MessageService.test.ts
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";

import { KintoneSdk } from "../../shared/util/kintoneSdk";
import { MessageService } from "./MessageService";

import type { ConfigSchema } from "../../shared/types/Config";
import type { Record } from "@kintone/rest-api-client/lib/src/client/types";

vi.mock("../shared/util/kintoneSdk");

  • vi.mock("../shared/util/kintoneSdk") → KintoneSdk をモック化し、テストで使用できるようにします。
  • beforeEach → 各テスト実行前に mockkintoneSdk や mockRestApiClient を初期化し、テスト間で状態が影響しないようにしています。

2. fetchRecords メソッドのテスト

src/desktop/service/MessageService.test.ts
describe("fetchRecords", () => {
  it("取得したレコードを返す", async () => {
    const mockConfig: ConfigSchema = {
      prefix: "",
      fields: [],
    };

    const messageService = new MessageService(mockConfig, mockkintoneSdk);
    const appId = "1";

    vi.spyOn(kintone.app, "getQueryCondition").mockReturnValue("");
    mockkintoneSdk.getRecords.mockResolvedValue({
      records: [
        {
          field1: { type: "SINGLE_LINE_TEXT", value: "value1" },
          field2: { type: "SINGLE_LINE_TEXT", value: "value2" },
        },
      ] as Record[],
    });

    const records = await messageService.fetchRecords(appId);
    expect(records).toEqual([
      {
        field1: { type: "SINGLE_LINE_TEXT", value: "value1" },
        field2: { type: "SINGLE_LINE_TEXT", value: "value2" },
      },
    ]);
  });
});

  • const mockConfig: ConfigSchemaでConfigSchema型を明示することでCopilotがプラグインのテスト設定を生成してくれます。
  • as Record[]でRecord型を明示することでCopilotがテストレコードを生成してくれます。
  • mockkintoneSdk.getRecords.mockResolvedValue(...) → getRecords メソッドのモックを作成し、テスト用のデータを返すように設定しています。
  • expect(records).toEqual([...]) → 取得したレコードが期待通りのデータであることを確認します。

3. generateMessages メソッドのテスト

src/desktop/service/MessageService.test.ts
describe("generateMessages", () => {
  it("プレフィックスとフィールド値を含むメッセージを返す", () => {
    const mockConfig: ConfigSchema = {
      prefix: "prefix\\\\n",
      fields: ["field1", "field2"],
    };

    const messageService = new MessageService(mockConfig, mockkintoneSdk);

    const records: Record[] = [
      {
        field1: {
          type: "SINGLE_LINE_TEXT",
          value: "value1",
        },
        field2: {
          type: "SINGLE_LINE_TEXT",
          value: "value2",
        },
      },
      {
        field1: {
          type: "SINGLE_LINE_TEXT",
          value: "value3",
        },
        field2: {
          type: "SINGLE_LINE_TEXT",
          value: "value4",
        },
      },
    ];
    const message = messageService.generateMessage(records);

    expect(message).toBe("prefix\\\\nvalue1 value2\\\\nvalue3 value4");
  });
});

  • const mockConfig: ConfigSchemaでConfigSchema型を明示することでCopilotがプラグインのテスト設定を生成してくれます。
  • as Record[]でRecord型を明示することでCopilotがテストレコードを生成してくれます。
  • generateMessage(records) → records の field1 と field2 の値をスペースで連結し、prefix を先頭に追加する処理をテストしています。
  • expect(message).toBe("prefix\nvalue1 value2\nvalue3 value4") → 期待通りのメッセージが生成されているかを確認します。

4. テストを実行する

ターミナルでテストを実行します。package.jsonに以下のスクリプトを追加しておくと楽です

package.json
  "scripts": {
    "test": "vitest",
  },

npm run testで実行するとターミナルに以下のように表示されます。

ターミナル
$ npm run test

> kintone-plugin-template@1.2.1 test
> vitest

 DEV  v3.0.6 /home/kintone-plugin-template

 ✓ src/shared/util/KintoneSdk.test.ts (3 tests) 8ms
 ✓ src/desktop/service/MessageService.test.ts (2 tests) 6ms

 Test Files  2 passed (2)
      Tests  5 passed (5)
   Start at  12:13:36
   Duration  445ms (transform 140ms, setup 0ms, collect 286ms, tests 14ms, environment 1ms, prepare 168ms)

 PASS  Waiting for file changes...
       press h to show help, press q to quit

また、VSCodeだとJavaScript Debug Terminalでテストを実行すると、ソースコードにおいたブレークポイントで処理を止めることができます。これでブラウザのDevToolsと同様のデバッグも可能です。

まとめ

Vitest を活用したユニットテストの導入により、以下のメリットを得られました。

  • 開発効率の向上
    • すぐにテストを実行でき、手動での検証作業が減る
    • ビルドやアップロードなしでエディタ内でデバッグ可能
    • Copilotの支援を活かし、テストデータを効率よく作成
  • コード品質の向上
    • テストしやすい構造を意識することで、関数が単一責務になり保守性が向上
    • テストコード自体がドキュメントとして機能する

一方で、ユニットテストだけでは解決できない課題もあります。

  • UIの動作確認
    • ユニットテストはあくまでロジックの検証が主であり、実際のプラグインのUI動作は確認できない
    • UIの検証にはE2Eテスト(例えばPlaywrightやPuppeteer)を併用するとよい
  • kintone独自の挙動
    • kintone のようなオブジェクトの動作(例えばkintone.events.on )は、ユニットテスト環境ではエミュレートが必要
    • 実際のkintone環境との動作差異が発生する可能性がある

ユニットテストは万能ではありませんが、導入することで開発の手間を大きく減らせます。特にkintoneプラグインのように手動検証の負担が大きい開発では、その恩恵は大きいでしょう。

ぜひ、ユニットテストを活用しながら効率よく開発してみてください!