kintoneプラグイン開発でVitestを導入しユニットテストを実践する
Vitestによるユニットテストを導入したkintoneプラグイン作成用のテンプレートを作成しました!とにかく動かしたい方はリポジトリをクローンなりフォークしてREADME通りコマンドを叩いてみてください!
この記事ではkintoneプラグイン開発・カスタマイズにおける面倒をVitestを導入することで解決することを目指します。
課題
プラグイン開発は通常以下のフローでするかと思います。(順番の前後はあると思いますが)
- 検証アプリを作成する
- テストレコード、テスト用プラグイン設定を用意する
- コードを書く
- コードをビルドし、検証アプリにアップロードする
- DevToolsでデバッグする
- 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. テストのセットアップ
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 メソッドのテスト
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 メソッドのテスト
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に以下のスクリプトを追加しておくと楽です
"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プラグインのように手動検証の負担が大きい開発では、その恩恵は大きいでしょう。
ぜひ、ユニットテストを活用しながら効率よく開発してみてください!