Skip to main content

add-test

Scaffold a test file for an existing tool, resource, or service. Use when the user asks to add tests, improve coverage, or when a definition exists without a matching test file.

インストールへ移動

ソース情報

リポジトリ
cyanheads/crossref-mcp-server
ソースの最終更新活動
2026年6月12日 03:45
検出された SKILL.md の言語
英語
スター
3
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
add-test
description
Scaffold a test file for an existing tool, resource, or service. Use when the user asks to add tests, improve coverage, or when a definition exists without a matching test file.
metadata
{"author":"cyanheads","version":"1.4","audience":"external","type":"reference"}
## Context Tests use Vitest and `createMockContext` from `@cyanheads/mcp-ts-core/testing`. If the repo already has tests, match the existing layout. If the repo has no existing tests, create a root `tests/` directory that mirrors the `src/` structure (e.g. `tests/mcp-server/tools/definitions/echo.tool.test.ts` for `src/mcp-server/tools/definitions/echo.tool.ts`). For the full `createMockContext` API and testing patterns, read: skills/api-testing/SKILL.md ## Steps 1. **Identify the target** — which tool, resource, or service needs tests 2. **Read the source file** — understand the handler's logic, input/output schemas, error paths, and which `ctx` features it uses 3. **Create the test file** in the repo's existing test layout — search for existing `*.test.ts` files to confirm whether tests are colocated with source or under a root `tests/` directory 4. **Write test cases** covering happy path, error paths, and edge cases 5. **Run `bun run test`** to verify 6. **Run `bun run devcheck`** to verify lint, types, and MCP definitions ## Determining What to Test Read the handler and identify: | Aspect | Test Strategy | |:-------|:-------------| | **Happy path** | Valid input → expected output. Include at least one. | | **Input variations** | Optional fields omitted, defaults applied, boundary values | | **Error paths** | Invalid state, missing resources, service failures → correct error thrown | | **`ctx.state` usage** | Use `createMockContext({ tenantId: 'test' })` to enable storage | | **`ctx.elicit`** | Mock with `vi.fn()`, also test the absent case (undefined) | | **`ctx.progress`** | Use `createMockContext({ progress: true })` for task tools | | **`ctx.fail` (typed contract)** | Definitions with `errors[]` need `fail` attached to the mock ctx — `createMockContext({ errors: myTool.errors })` does it for you. Assert on `data.reason` (stable per-contract entry), not just `code`. | | **`format` function** | Test separately if defined — it's pure, no ctx needed. Verify it renders the IDs and fields the model needs, not just a count or title. For projection-style tools, test non-default field selections. | | **Sparse upstream payloads** | For third-party API integrations, build a fixture with omitted fields. Assert normalized output still validates and `format()` preserves unknown values instead of inventing facts. | | **Form-client payloads** | If handler has optional fields: test with empty-string inner values (form clients send `""` instead of `undefined`). Assert handler doesn't break or produce invalid output. | | **Auth scopes** | Not tested at handler level (framework enforces) — skip | ## Templates ### Tool test ```typescript /** * @fileoverview Tests for {{TOOL_NAME}} tool. * @module tests/tools/{{TOOL_NAME}}.tool.test */ import { describe, expect, it } from 'vitest'; import { createMockContext } from '@cyanheads/mcp-ts-core/testing'; import { {{TOOL_EXPORT}} } from '@/mcp-server/tools/definitions/{{tool-name}}.tool.js'; describe('{{TOOL_EXPORT}}', () => { it('returns expected output for valid input', async () => { const ctx = createMockContext(); const input = {{TOOL_EXPORT}}.input.parse({ // valid input matching the Zod schema }); const result = await {{TOOL_EXPORT}}.handler(input, ctx); expect(result).toMatchObject({ // expected output shape }); }); it('throws on invalid state', async () => { const ctx = createMockContext(); const input = {{TOOL_EXPORT}}.input.parse({ // input that triggers an error path }); await expect({{TOOL_EXPORT}}.handler(input, ctx)).rejects.toThrow(); }); // Only when the tool declares `errors: [...]`. Drop this block otherwise. it('throws ctx.fail("{{REASON}}") for the declared failure mode', async () => { const ctx = createMockContext({ errors: {{TOOL_EXPORT}}.errors }); const input = {{TOOL_EXPORT}}.input.parse({ // input that triggers the declared failure mode }); await expect({{TOOL_EXPORT}}.handler(input, ctx)).rejects.toMatchObject({ data: { reason: '{{REASON}}' }, }); }); it('formats output completely', () => { const output = { /* mock output matching the output schema */ }; const blocks = {{TOOL_EXPORT}}.format!(output); expect(blocks.some((block) => block.type === 'text')).toBe(true); // Assert the rendered text includes the IDs/fields the LLM needs to act on. }); }); ``` ### Resource test ```typescript /** * @fileoverview Tests for {{RESOURCE_NAME}} resource. * @module tests/resources/{{RESOURCE_NAME}}.resource.test */ import { describe, expect, it } from 'vitest'; import { createMockContext } from '@cyanheads/mcp-ts-core/testing'; import { {{RESOURCE_EXPORT}} } from '@/mcp-server/resources/definitions/{{resource-name}}.resource.js'; describe('{{RESOURCE_EXPORT}}', () => { it('returns data for valid params', async () => { const ctx = createMockContext({ tenantId: 'test-tenant' }); const params = {{RESOURCE_EXPORT}}.params.parse({ // valid params matching the Zod schema }); const result = await {{RESOURCE_EXPORT}}.handler(params, ctx); expect(result).toBeDefined(); }); it('throws when resource not found', async () => { const ctx = createMockContext({ tenantId: 'test-tenant' }); const params = {{RESOURCE_EXPORT}}.params.parse({ // params for a non-existent resource }); await expect({{RESOURCE_EXPORT}}.handler(params, ctx)).rejects.toThrow(); }); // For resources that declare an `errors: [...]` contract, pass the contract via // `createMockContext` so the typed `ctx.fail` is wired automatically: // const ctx = createMockContext({ errors: {{RESOURCE_EXPORT}}.errors }); // const err = await {{RESOURCE_EXPORT}}.handler(params, ctx).catch((e) => e); // expect(err.code).toBe(JsonRpcErrorCode.NotFound); // expect(err.data.reason).toBe('no_match'); // Include this block only when the resource definition exports a `list` function. // Check the source — `list` is optional on resource definitions. it('lists available resources', async () => { const listing = await {{RESOURCE_EXPORT}}.list!(); expect(listing.resources).toBeInstanceOf(Array); expect(listing.resources.length).toBeGreaterThan(0); for (const r of listing.resources) { expect(r).toHaveProperty('uri'); expect(r).toHaveProperty('name'); } }); }); ``` ### Service test ```typescript /** * @fileoverview Tests for {{SERVICE_NAME}} service. * @module tests/services/{{domain}}/{{domain}}-service.test */ import { beforeEach, describe, expect, it } from 'vitest'; import { createMockContext } from '@cyanheads/mcp-ts-core/testing'; import { StorageService } from '@cyanheads/mcp-ts-core/storage'; import { get{{ServiceClass}}, init{{ServiceClass}} } from '@/services/{{domain}}/{{domain}}-service.js'; // Derive the minimal mock config from src/config/server-config.ts — read // the server's Zod schema to see which fields init{{ServiceClass}}() needs. const mockConfig = { /* fields from server config schema */ } as AppConfig; describe('{{ServiceClass}}', () => { beforeEach(async () => { const mockStorage = await StorageService.create({ type: 'in-memory' }); init{{ServiceClass}}(mockConfig, mockStorage); }); it('performs the expected operation', async () => { const ctx = createMockContext({ tenantId: 'test-tenant' }); const service = get{{ServiceClass}}(); const result = await service.doWork('input', ctx); expect(result).toBeDefined(); }); }); ``` If you need to test the accessor's "not initialized" guard, do it in a separate isolated-module test (`vi.resetModules()` before importing the service module). Don't mix that assertion into a suite that already calls `init{{ServiceClass}}()` in `beforeEach()`. ### Task tool test For tools with `task: true`, use `createMockContext({ progress: true })`: ```typescript it('reports progress during execution', async () => { const ctx = createMockContext({ progress: true }); const input = {{TOOL_EXPORT}}.input.parse({ count: 3, delayMs: 10 }); await {{TOOL_EXPORT}}.handler(input, ctx); const progress = ctx.progress as ContextProgress & { _total: number; _completed: number; _messages: string[]; }; expect(progress._total).toBe(3); expect(progress._completed).toBe(3); }); it('respects cancellation', async () => { const controller = new AbortController(); const ctx = createMockContext({ progress: true, signal: controller.signal }); const input = {{TOOL_EXPORT}}.input.parse({ count: 100, delayMs: 10 }); // Abort after a short delay setTimeout(() => controller.abort(), 50); const result = await {{TOOL_EXPORT}}.handler(input, ctx); // Should have returned a partial result rather than throwing on cancellation. // Assert on a field from the tool's actual output schema. expect(result).toBeDefined(); }); ``` ### Prompt test ```typescript /** * @fileoverview Tests for {{PROMPT_NAME}} prompt. * @module tests/prompts/{{PROMPT_NAME}}.prompt.test */ import { describe, expect, it } from 'vitest'; import { {{PROMPT_EXPORT}} } from '@/mcp-server/prompts/definitions/{{prompt-name}}.prompt.js'; describe('{{PROMPT_EXPORT}}', () => { it('generates valid messages for valid args', () => { const args = {{PROMPT_EXPORT}}.args!.parse({ // valid args matching the Zod schema }); const messages = {{PROMPT_EXPORT}}.generate(args); expect(messages).toBeInstanceOf(Array); expect(messages.length).toBeGreaterThan(0); for (const msg of messages) { expect(msg).toHaveProperty('role'); expect(msg).toHaveProperty('content'); } }); // Include only when the prompt has no required args (args is optional or all fields optional). it('generates messages with no args', () => { const messages = {{PROMPT_EXPORT}}.generate({}); expect(messages.length).toBeGreaterThan(0); }); }); ``` ## Fuzz Testing For schema-heavy or input-validation-critical handlers, the framework ships fuzz helpers that generate valid + adversarial inputs from your Zod schemas via `fast-check` and assert handler invariants (no crashes, no prototype pollution, no stack-trace leaks): ```typescript import { fuzzTool } from '@cyanheads/mcp-ts-core/testing/fuzz'; it('survives fuzz testing', async () => { const report = await fuzzTool({{TOOL_EXPORT}}, { numRuns: 100 }); expect(report.crashes).toHaveLength(0); expect(report.leaks).toHaveLength(0); expect(report.prototypePollution).toBe(false); }); ``` Available helpers from `@cyanheads/mcp-ts-core/testing/fuzz`: `fuzzTool`, `fuzzResource`, `fuzzPrompt`, `zodToArbitrary` (custom property-based tests), `adversarialArbitrary` and `ADVERSARIAL_STRINGS` (targeted injection sets). Returns a `FuzzReport` you can assert against. Options: `numRuns`, `numAdversarial`, `seed` (reproducibility), `timeout`, `ctx` (`MockContextOptions` for stateful handlers). ## Generating Tests from Schemas When scaffolding tests for an existing handler, use the Zod schemas to generate meaningful test cases:
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る