コンテンツにスキップ

テストユーティリティ

Rune は、子プロセスを起動せずにコマンドをインプロセスでテストするためのヘルパーを @rune-cli/rune/test から提供しています。runCommand() は単一コマンドをそのまま実行する基本のヘルパーで、createRunCommand() はプロジェクト設定を組み込んだ runCommand を作成するファクトリです。

import { runCommand } from "@rune-cli/rune/test";
import { expect, test } from "vitest";
import greeting from "../src/commands/index.ts";
test("greets by name", async () => {
const result = await runCommand(greeting, ["world"]);
expect(result.exitCode).toBe(0);
expect(result.stdout).toBe("Hello, world!\n");
});

Rune のパース・実行パイプラインを通じてコマンドを実行します。入力は CLI トークンの string[] として渡されるため、argv パース、型変換、スキーマバリデーション、デフォルト値の処理がすべて実際の呼び出しと同様に動作します。

function runCommand(
command: DefinedCommand,
argv?: string[],
context?: RunCommandContext,
): Promise<CommandExecutionResult<TCommandDocument, TCommandRecord>>

出力のかたちは渡した command から推論されます。通常の command では output.kind === "text" になり、json: true の command では run() の戻り値型が output.document に、jsonl: true の command では yield されたレコード型が output.records に反映されます。

  • 型: DefinedCommand
  • 必須

defineCommand() で作成されたコマンド。

  • 型: string[]
  • デフォルト: []

コマンドに転送される CLI トークン。

  • 型: RunCommandContext
  • デフォルト: {}

省略可能な実行コンテキスト。

  • 型: string
  • 省略可能

ctx.cwd に注入されるワーキングディレクトリの値。process.cwd() は変更しません。

  • 型: Record<string, string | undefined>
  • 省略可能

オプションの env フォールバックで使う環境変数です。テスト対象コマンドでは process.env の代わりにこの値が使われ、自動的にはマージされません。省略した場合、runCommand() はホスト環境からテストを切り離すため、空の環境変数マップで実行します。

const command = defineCommand({
options: [{ name: "port", type: "number", env: "PORT", default: 3000 }],
run({ options, output }) {
output.log(String(options.port));
},
});
test("uses PORT from env", async () => {
const result = await runCommand(command, [], { env: { PORT: "4000" } });
expect(result.stdout).toBe("4000\n");
});

現在のプロセス環境を意図的に引き継ぎたい場合は、明示的にマージしてください:

const result = await runCommand(command, [], {
env: { ...process.env, PORT: "4000" },
});
  • 型: string | Buffer | Uint8Array
  • 省略可能

ctx.stdin に注入される stdin です。指定した場合、ctx.stdin.isPipedtruectx.stdin.isTTYfalse になります。省略した場合、runCommand()isPiped: falseisTTY: true の空の stdin を使います。process.stdin は継承しません。

const command = defineCommand({
async run({ stdin, output }) {
const input = stdin.isPiped ? await stdin.text() : "";
output.log(input.trim());
},
});
test("reads stdin", async () => {
const result = await runCommand(command, [], { stdin: "hello\n" });
expect(result.stdout).toBe("hello\n");
});
  • 型: CommandOptionField[]
  • 省略可能

グローバルオプションを注入する低レベル API です。通常のテストでは createRunCommand(config) を使ってください。

  • 型: RuneHooks
  • 省略可能

グローバルフックを注入する低レベル API です。通常のテストでは createRunCommand(config) を使ってください。

  • 型: (ctx: LocalsFactoryContext) => unknown
  • 省略可能

project locals を注入する低レベル API です。通常のテストでは createRunCommand(config) を使ってください。

  • 型: RuneConfigLocals
  • 省略可能

コマンドテストで固定の ctx.locals 値を注入するためのショートハンドです。

const result = await runCommand(command, [], {
locals: { workspace: fakeWorkspace, api: fakeApi },
});

createLocalslocals はどちらか一方だけを渡してください。

  • 型: RunHookCommandMetadata
  • 省略可能

フックに公開されるコマンドルートメタデータです。runCommand() はマニフェストのルーティングを実行しないため、省略した場合は空のメタデータになります。ctx.command.cliNamectx.command.path で分岐するフックをテストするときに指定してください。

プロジェクト設定を組み込んだ runCommand() ヘルパーを作成します。プロジェクトで defineConfig({ options })defineConfig({ hooks })、または defineConfig({ locals }) を定義している場合に使います。

import { createRunCommand } from "@rune-cli/rune/test";
import config from "../rune.config";
const runCommand = createRunCommand(config);

返される関数は runCommand(command, argv, context) と同じかたちで呼び出せ、各コマンド実行に config.optionsconfig.hooksconfig.locals を注入します。特定のテストで設定由来の値を上書きしたい場合は、context.globalOptionscontext.globalHookscontext.createLocals、または context.locals を渡してください。

  • 型: number

プロセスの終了コード(成功時は 0)。

  • 型: string

キャプチャされた stdout 出力。

  • 型: string

キャプチャされた stderr 出力。

  • 型: CommandFailure | undefined

コマンドが失敗した場合の構造化されたエラー情報。

  • 型: { kind: "text" } | { kind: "json"; document: TCommandDocument | undefined } | { kind: "jsonl"; records: TCommandRecord[] }

コマンドの構造化された出力情報です。

通常の command では output{ kind: "text" } です。

json: true の command では、output.documentrun() の戻り値です。--json フラグの有無にかかわらず格納されます。--json が制御するのは主に output.log() の抑制であり、document のキャプチャには影響しません。

jsonl: true の command では、output.recordsrun() から yield されたレコードの配列となります。パース失敗や最初のレコードを yield する前の失敗では空配列になります。

バリデーションエラーのテスト

Section titled “バリデーションエラーのテスト”
test("requires an id argument", async () => {
const result = await runCommand(command, []);
expect(result.exitCode).toBe(1);
expect(result.stderr).not.toBe("");
});
const command = defineCommand({
options: [{ name: "count", type: "number", default: 1 }],
run({ options, output }) {
output.log(`count=${options.count}`);
},
});
test("uses default count", async () => {
const result = await runCommand(command, []);
expect(result.stdout).toBe("count=1\n");
});
const command = defineCommand({
json: true,
run() {
return { items: [1, 2, 3] };
},
});
test("returns structured document", async () => {
const result = await runCommand(command, ["--json"]);
expect(result.output).toEqual({
kind: "json",
document: { items: [1, 2, 3] },
});
expect(result.stdout).toBe("");
});

JSON モードのコマンドが失敗した場合、runCommand() は compact な JSON error envelope を result.stderr にキャプチャし、正規化された失敗情報を result.error でも返します。