All skills
jwynia avatar

/mastra-hono

@99a8797
by J Wyniajwynia/agent-skills160 stars
20

Develop AI agents, tools, and workflows with Mastra v1 Beta and Hono servers. This skill should be used when creating Mastra agents, defining tools with Zod schemas, building workflows with step data flow, setting up Hono API servers with Mastra adapters, or implementing agent networks. Keywords: mastra, hono, agent, tool, workflow, AI, LLM, typescript, API, MCP.

Use this Skill: https://skilld.dev/gh/jwynia/agent-skills/mastra-hono

This session only. Nothing lands on disk.

referencestesting-patterns.md

≈3.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Testing Patterns

Complete guide to testing Mastra agents, tools, and workflows with Vitest.

Setup

Vitest Configuration

// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "node",
    setupFiles: ["./test/setup.ts"],
    coverage: {
      provider: "v8",
      reporter: ["text", "json", "html"],
    },
  },
});

Test Setup File

// test/setup.ts
import { beforeAll, afterAll, vi } from "vitest";

// Mock environment variables
beforeAll(() => {
  process.env.OPENAI_API_KEY = "test-key";
  process.env.DATABASE_URL = "file:./test.db";
});

// Clean up
afterAll(() => {
  vi.restoreAllMocks();
});

Package.json Scripts

{
  "scripts": {
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:coverage": "vitest --coverage",
    "test:ui": "vitest --ui"
  }
}

Testing Tools

Basic Tool Test

import { describe, it, expect } from "vitest";
import { RuntimeContext } from "@mastra/core";
import { weatherTool } from "../src/mastra/tools/weather-tool";

describe("Weather Tool", () => {
  it("should return weather data for valid location", async () => {
    const result = await weatherTool.execute(
      { location: "Seattle", units: "celsius" },
      {
        mastra: undefined,
        runtimeContext: new RuntimeContext(),
        abortSignal: new AbortController().signal,
      }
    );

    expect(result).toHaveProperty("temperature");
    expect(result).toHaveProperty("conditions");
    expect(typeof result.temperature).toBe("number");
  });

  it("should handle abort signal", async () => {
    const controller = new AbortController();
    controller.abort();

    await expect(
      weatherTool.execute(
        { location: "Seattle" },
        {
          mastra: undefined,
          runtimeContext: new RuntimeContext(),
          abortSignal: controller.signal,
        }
      )
    ).rejects.toThrow("Aborted");
  });
});

Tool with Mocked External API

import { describe, it, expect, vi, beforeEach } from "vitest";

// Mock fetch globally
vi.mock("node-fetch", () => ({
  default: vi.fn(),
}));

import fetch from "node-fetch";
import { apiTool } from "../src/mastra/tools/api-tool";

describe("API Tool", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("should fetch data from API", async () => {
    (fetch as any).mockResolvedValue({
      json: () => Promise.resolve({ data: "test" }),
      ok: true,
    });

    const result = await apiTool.execute(
      { endpoint: "/users" },
      {
        mastra: undefined,
        runtimeContext: new RuntimeContext(),
        abortSignal: new AbortController().signal,
      }
    );

    expect(result.data).toBe("test");
    expect(fetch).toHaveBeenCalledWith(
      expect.stringContaining("/users"),
      expect.any(Object)
    );
  });
});

Tool with Mocked Mastra

describe("Agent Wrapper Tool", () => {
  it("should call nested agent", async () => {
    const mockAgent = {
      generate: vi.fn().mockResolvedValue({ text: "Generated text" }),
    };

    const mockMastra = {
      getAgent: vi.fn().mockReturnValue(mockAgent),
    };

    const result = await copywriterTool.execute(
      { topic: "AI trends" },
      {
        mastra: mockMastra as any,
        runtimeContext: new RuntimeContext(),
        abortSignal: new AbortController().signal,
      }
    );

    expect(mockMastra.getAgent).toHaveBeenCalledWith("copywriter-agent");
    expect(mockAgent.generate).toHaveBeenCalled();
    expect(result.copy).toBe("Generated text");
  });
});

Testing Workflows

Schema Compatibility Tests

import { describe, it, expect } from "vitest";
import { z } from "zod";

describe("Workflow Schema Tests", () => {
  it("step1 output matches step2 input", () => {
    const step1OutputSchema = z.object({ formatted: z.string() });
    const step2InputSchema = z.object({ formatted: z.string() });

    const step1Output = { formatted: "HELLO" };

    // Validate step1 output against step2 input
    const result = step2InputSchema.safeParse(step1Output);
    expect(result.success).toBe(true);
  });

  it("parallel output matches combine step input", () => {
    const parallelOutput = {
      "format-step": { formatted: "HELLO" },
      "count-step": { count: 5 },
    };

    const combineInputSchema = z.object({
      "format-step": z.object({ formatted: z.string() }),
      "count-step": z.object({ count: z.number() }),
    });

    const result = combineInputSchema.safeParse(parallelOutput);
    expect(result.success).toBe(true);
  });

  it("branch output uses optional correctly", () => {
    const branchOutput = {
      "branch-a": { result: "A executed" },
      // branch-b is undefined (didn't execute)
    };

    const afterBranchSchema = z.object({
      "branch-a": z.object({ result: z.string() }).optional(),
      "branch-b": z.object({ result: z.string() }).optional(),
    });

    const result = afterBranchSchema.safeParse(branchOutput);
    expect(result.success).toBe(true);
  });
});

Complete Workflow Test

import { describe, it, expect } from "vitest";
import { dataWorkflow } from "../src/mastra/workflows/data-workflow";

describe("Data Workflow", () => {
  it("should process data through all steps", async () => {
    const run = dataWorkflow.createRun();

    const result = await run.start({
      inputData: { value: 5 },
    });

    expect(result.status).toBe("success");

    // Verify step 1 received correct input
    expect(result.steps["step-1"].payload).toEqual({ value: 5 });
    expect(result.steps["step-1"].output.doubled).toBe(10);

    // Verify data flowed to step 2
    expect(result.steps["step-2"].payload).toEqual({ doubled: 10 });
    expect(result.steps["step-2"].output.result).toBe(20);
  });

  it("should handle errors gracefully", async () => {
    const run = dataWorkflow.createRun();

    const result = await run.start({
      inputData: { value: -1 }, // Invalid input
    });

    expect(result.status).toBe("failed");
    expect(result.error).toBeDefined();
  });
});

Step Unit Test

import { describe, it, expect } from "vitest";
import { formatStep } from "../src/mastra/workflows/steps/format-step";

describe("Format Step", () => {
  it("should format input correctly", async () => {
    const result = await formatStep.execute({
      inputData: { text: "hello world" },
      getStepResult: () => null,
      getInitData: () => ({ text: "hello world" }),
      suspend: async () => {},
      state: {},
      setState: () => {},
      runId: "test-run",
      mastra: undefined,
      runtimeContext: new RuntimeContext(),
    });

    expect(result.formatted).toBe("HELLO WORLD");
  });
});

Mocking LLM Responses

Mock OpenAI Provider

import { vi } from "vitest";

vi.mock("@ai-sdk/openai", () => ({
  openai: vi.fn().mockReturnValue({
    doGenerate: vi.fn().mockResolvedValue({
      text: "Mocked LLM response",
      usage: { promptTokens: 10, completionTokens: 20 },
    }),
    doStream: vi.fn().mockReturnValue({
      stream: (async function* () {
        yield { type: "text-delta", textDelta: "Mocked " };
        yield { type: "text-delta", textDelta: "stream" };
      })(),
    }),
  }),
}));

Mock Agent Response

import { describe, it, expect, vi } from "vitest";
import { Agent } from "@mastra/core/agent";

describe("Agent with Mocked LLM", () => {
  it("should return mocked response", async () => {
    const mockModel = {
      doGenerate: vi.fn().mockResolvedValue({
        text: "Mocked response",
        usage: { promptTokens: 10, completionTokens: 20 },
      }),
    };

    const agent = new Agent({
      name: "test-agent",
      instructions: "Test instructions",
      model: mockModel as any,
    });

    const response = await agent.generate("Hello");
    expect(response.text).toBe("Mocked response");
  });
});

Spy on Agent Calls

describe("Tool with Agent", () => {
  it("should call agent with correct prompt", async () => {
    const generateSpy = vi.fn().mockResolvedValue({ text: "Result" });

    const mockMastra = {
      getAgent: vi.fn().mockReturnValue({
        generate: generateSpy,
      }),
    };

    await myTool.execute(
      { topic: "AI trends" },
      { mastra: mockMastra as any, runtimeContext: new RuntimeContext() }
    );

    expect(generateSpy).toHaveBeenCalledWith(
      expect.stringContaining("AI trends"),
      expect.any(Object)
    );
  });
});

Testing Mastra Instance

Integration Test Setup

import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { Mastra } from "@mastra/core/mastra";
import { LibSQLStore } from "@mastra/libsql";

describe("Mastra Integration", () => {
  let mastra: Mastra;

  beforeAll(async () => {
    mastra = new Mastra({
      agents: { testAgent },
      workflows: { testWorkflow },
      storage: new LibSQLStore({
        url: "file:./test.db",
      }),
    });
  });

  afterAll(async () => {
    // Cleanup test database
    await fs.unlink("./test.db").catch(() => {});
  });

  it("should get registered agent", () => {
    const agent = mastra.getAgent("test-agent");
    expect(agent).toBeDefined();
    expect(agent?.name).toBe("test-agent");
  });

  it("should get registered workflow", () => {
    const workflow = mastra.getWorkflow("test-workflow");
    expect(workflow).toBeDefined();
  });
});

Testing Hono Routes

Route Test Setup

import { describe, it, expect, beforeAll } from "vitest";
import { Hono } from "hono";
import { MastraServer } from "@mastra/hono";
import { mastra } from "../src/mastra";

describe("API Routes", () => {
  let app: Hono;

  beforeAll(async () => {
    app = new Hono();
    const server = new MastraServer({ app, mastra });
    await server.init();
  });

  it("should respond to health check", async () => {
    const res = await app.request("/health");
    expect(res.status).toBe(200);
  });

  it("should generate from agent", async () => {
    const res = await app.request("/api/agents/test-agent/generate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        messages: [{ role: "user", content: "Hello" }],
      }),
    });

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toHaveProperty("text");
  });
});

Custom Route Test

describe("Custom Routes", () => {
  it("should handle custom API route", async () => {
    // Add custom route
    app.post("/custom", async (c) => {
      const mastra = c.get("mastra");
      const agent = mastra.getAgent("test-agent");
      const result = await agent.generate("Test");
      return c.json({ response: result.text });
    });

    const res = await app.request("/custom", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
    });

    expect(res.status).toBe(200);
  });
});

CI/CD Integration

GitHub Actions Workflow

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - run: npm ci

      - run: npm test
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

      - uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json

Running Evals in CI

// test/evals.test.ts
import { describe, it, expect } from "vitest";
import { evaluate } from "@mastra/evals";

describe("Agent Evals", () => {
  it("should pass quality evaluation", async () => {
    const result = await evaluate({
      agent: myAgent,
      testCases: [
        {
          input: "What is 2 + 2?",
          expected: "4",
          scorer: (response, expected) => {
            return response.includes(expected) ? 1 : 0;
          },
        },
      ],
    });

    expect(result.score).toBeGreaterThan(0.8);
  });
});

Best Practices

1. Test Schema Compatibility First

// Always test that schemas chain correctly
describe("Schema Chain", () => {
  it("workflow → step1 → step2 → workflow schemas match", () => {
    // Test each connection in the chain
  });
});

2. Mock External Dependencies

// Never hit real APIs in unit tests
vi.mock("node-fetch");
vi.mock("@ai-sdk/openai");

3. Use Factories for Test Data

function createTestContext(overrides = {}) {
  return {
    mastra: undefined,
    runtimeContext: new RuntimeContext(),
    abortSignal: new AbortController().signal,
    ...overrides,
  };
}

4. Test Error Paths

it("should handle API errors", async () => {
  (fetch as any).mockRejectedValue(new Error("Network error"));

  await expect(tool.execute(input, context)).rejects.toThrow("Network error");
});

5. Isolate Tests

beforeEach(() => {
  vi.clearAllMocks();
});

afterEach(() => {
  vi.restoreAllMocks();
});

Source: SKILL.md on GitHub

2 warnings16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides a comprehensive developer toolkit for building AI agents and workflows with Mastra v1 Beta and Hono servers. It includes scaffolding scripts, code templates, and detailed documentation on best practices for agent orchestration and data flow. All external dependencies are from well-known technology organizations and the code patterns align with standard software development practices.

  • Socket16d

    1 alert: gptAnomaly

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    22/22 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 99a8797. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Dormantupdated 8 months ago
compatibility
Node.js 22.13.0+ required for v1 Beta
Other metadata
metadata
{
  "author": "agent-skills",
  "version": "1.0",
  "type": "utility",
  "mode": "assistive",
  "domain": "development"
}

README badge

README badge for jwynia/agent-skills/mastra-hono