mcpField
T
testing-flow

testing-flow

github.com/robomotionioテスト・品質5120 回インストール3 日前に更新
リポジトリ

testing-flow について

`@robomotion/sdk/testing` と `bun test` で Robomotion フローの動作テストを作成・実行するスキルです。フローの検査、サービスのモック、Function ノードとサブフローの個別テストに対応します。

主な機能

  1. 01`bun test` による `*.test.ts` の実行
  2. 02フロー構造と実行パスの検査
  3. 03外部サービスのモックと呼び出し追跡
  4. 04Function ノードコードの個別テスト
  5. 05サブフローの単独実行
  6. 06ユニット/統合モードの切り替え

活用シーン

  1. 01Robomotion フローの動作テスト実行
  2. 02サービスをモックしてノード動作を検証
  3. 03フロー全体を実行せず Function ノードを確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/robomotionio/agent-skills --skill testing-flow

インストール後、Claude Code で/testing-flowを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

config.json
reference/
flow-tester.md
function-extraction.md
integration-mode.md
mock-registry.md
subflow-tester.md

詳細

Robomotion フローのテスト

Robomotion フローの動作テストを実行します。@robomotion/sdk/testing と bun test を使用します。

pspec スキーマの検証には、validating-flow を使用してください。

目的

  • bun test で *.test.ts ファイルを実行する
  • FlowTester、MockRegistry、SubflowTester を使って新しいテストを作成する
  • 構造を調べ、サービスをモックし、実行をトレースし、Function ノードのロジックをテストする

ワークフロー

  1. テストを確認する — ls path/to/flow/*.test.ts
  2. 実行する — cd path/to/flow && bun test
  3. 失敗を修正する — 原因を調べ、編集し、再実行する

クイックスタート

main.ts の隣に main.test.ts を作成します:

import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { FlowTester, MockRegistry } from '@robomotion/sdk/testing';

describe('My Flow', () => {
  let tester: FlowTester;

  beforeAll(async () => {
    tester = await FlowTester.load('./main.ts');
  });

  afterAll(() => {
    MockRegistry.clear();
  });

  test('validates', () => {
    expect(tester.validate().valid).toBe(true);
  });

  test('has expected node count', () => {
    expect(tester.getNodes().length).toBe(10);
  });
});

API マップ

必要なリファレンスファイルだけを読み込んでください。それぞれ単独で完結しており、このスキルの ./reference/ 内にあります。

領域内容リファレンス
フローの読み込み・調査、チェックポイントの実行、パスと複雑さの分析FlowTester、analyzeFlow、findPaths、generateDotGraph./reference/flow-tester.md
外部サービスのモック(ノードごと、タイプごと、ヘルパー、呼び出し追跡)MockRegistry、mockResponse/Error/Sequence/Conditional./reference/mock-registry.md
Function ノードの JavaScript を分離してテストするextractFunc、runFunc、testFunctionWithCases、analyzeFunctionNode./reference/function-extraction.md
サブフローを分離して実行し、フロー内で検出するSubflowTester、discoverSubflows、loadAllSubflows./reference/subflow-tester.md
環境変数によるユニットテストモードと実サービスモードの切り替えisIntegrationMode、hasCredentials、createTestHelper./reference/integration-mode.md

完全な例

import { describe, test, expect, beforeAll, afterAll, afterEach } from 'bun:test';
import {
  FlowTester, MockRegistry, extractFunc,
  isIntegrationMode, hasCredentials, analyzeFlow,
} from '@robomotion/sdk/testing';

describe('Reddit to WordPress Flow', () => {
  let tester: FlowTester;

  beforeAll(async () => { tester = await FlowTester.load('./main.ts'); });
  afterEach(() => MockRegistry.resetCalls());
  afterAll(() => MockRegistry.clear());

  describe('Structure', () => {
    test('validates', () => expect(tester.validate().valid).toBe(true));
    test('all branches terminate', () => expect(tester.allBranchesTerminate()).toBe(true));
    test('loops have Goto->Label', () => {
      expect(tester.findLoops().every(l => l.hasGotoLabel)).toBe(true);
    });
    test('no dead ends', () => {
      expect(analyzeFlow(tester).deadEnds).toHaveLength(0);
    });
  });

  describe('Transform Logic', () => {
    test('formats Reddit post', () => {
      const transform = extractFunc(tester, '2bf967');
      const out = transform({ post: { title: 'Test', selftext: 'Body', subreddit: 'programming' } });
      expect(out.wpTitle).toContain('Test');
    });
  });

  describe('With Mocked Services', () => {
    beforeAll(() => {
      MockRegistry.registerNode('c8f412', async () => ({
        posts: [{ title: 'Mock 1', selftext: 'c1', score: 100 }],
      }));
      MockRegistry.register('Core.Net.HttpRequest', async (p, m, nodeId) => {
        console.warn(`Unmocked HTTP in ${nodeId}`);
        return { statusCode: 200, body: {} };
      });
    });

    test('fetches posts', async () => {
      const res = await tester.runUntil('c5d901', {
        input: { subreddit: 'programming', limit: 10 },
      });
      expect(res.msg.posts).toHaveLength(1);
      expect(MockRegistry.getCallCount('c8f412')).toBe(1);
    });
  });

  describe('Integration', () => {
    test('real WordPress posting', async () => {
      if (!isIntegrationMode() || !hasCredentials('wordpress')) return;
      const res = await tester.runWithTrace({
        input: { subreddit: 'programming', postLimit: 1 },
        useRealServices: true,
      });
      expect(res.output.publishedUrl).toBeDefined();
    });
  });
});

ヒント

  • 正確に制御するにはノード ID のモックを使用し、すべてに適用するモックにはタイプのモックを使用します。
  • getLastCall() でモックへの入力を確認します。
  • ファイル間の汚染を防ぐため、afterAll() でモックをクリアします。
  • INTEGRATION=1 bun test で実サービスに切り替わります。

関連スキル

  • validating-flow — スキーマの検証(プロパティ名、ポート、ノードタイプ)
  • creating-flow — TypeScript の生成
  • running-flow — ロボット上で実行

コメント