Agent Skillsstacklok/toolhive-studio › testing-api-overrides

testing-api-overrides

GitHub

指导如何通过条件覆盖模拟API响应,验证组件发送的正确查询参数和请求参数。适用于测试过滤、排序、分页等读取操作,确保基于用户可见行为而非内部实现细节进行测试。

.codex/skills/testing-api-overrides/SKILL.md stacklok/toolhive-studio

触发场景

测试前端组件的API请求参数 验证列表过滤、排序或分页逻辑 设置测试作用域的API Mock自定义

安装

npx skills add stacklok/toolhive-studio --skill testing-api-overrides -g -y
更多选项

非标准路径

npx skills add https://github.com/stacklok/toolhive-studio/tree/main/.codex/skills/testing-api-overrides -g -y

不安装直接使用

npx skills use stacklok/toolhive-studio@testing-api-overrides

指定 Agent (Claude Code)

npx skills add stacklok/toolhive-studio --skill testing-api-overrides -a claude-code -g -y

安装 repo 全部 skill

npx skills add stacklok/toolhive-studio --all -g -y

预览 repo 内 skill

npx skills add stacklok/toolhive-studio --list

SKILL.md

Frontmatter
{
    "name": "testing-api-overrides",
    "description": "Test that components send correct query parameters or request arguments. Use when testing filtering, sorting, pagination, or any read operation where request parameters matter. Use for test-scoped mock customization."
}

Testing API Overrides

Test that your code sends correct API parameters by using conditional overrides that respond differently based on the request. This approach tests actual user-facing behavior rather than inspecting internal request details.

Philosophy

Good tests verify what users see, not implementation details.

Instead of:

  1. ❌ Recording the request and checking query params
  2. ❌ Asserting on internal function calls

Do this:

  1. ✅ Set up conditional mocks that respond based on params
  2. ✅ Verify the component renders the expected data

If the code sends wrong parameters, the mock returns wrong data, the UI shows wrong content, and the test fails. This catches real bugs.

When to Use Conditional Overrides

  • Testing list filtering (e.g., filter by group, status, search term)
  • Testing pagination parameters
  • Testing sort order
  • Any read operation where request parameters affect what data is returned

Note: For mutations (create/update/delete), use recordRequests() instead. See the testing-api-assertions skill.

conditionalOverride()

Returns different data based on request properties:

import { mockedGetApiV1BetaWorkloads } from '@mocks/fixtures/workloads/get'

mockedGetApiV1BetaWorkloads.conditionalOverride(
  // Predicate: when should this override apply?
  ({ query }) => query.group === 'archive',
  // Transform: what data to return?
  (data) => ({
    ...data,
    workloads: [], // Archive group is empty
  })
)

Predicate Function

The predicate receives parsed request info with easy access to query params, path params, body, and headers:

;({ query, path, body, headers }) => {
  // Query parameters (pre-parsed)
  query.group // '?group=archive' -> 'archive'
  query.status // '?status=running' -> 'running'

  // Path parameters (from route like /workloads/:name)
  path.name // for /workloads/:name

  // Request body (for POST/PUT/DELETE)
  body?.name // parsed JSON body

  // Headers
  headers.get('Authorization')

  return true // or false
}

Transform Function

The transform receives default data and returns modified data:

;(data) => ({
  ...data, // Spread defaults
  workloads: data.workloads?.filter(
    // Modify as needed
    (w) => w.status === 'running'
  ),
})

Example: Testing Group Filter

import { mockedGetApiV1BetaWorkloads } from '@mocks/fixtures/workloads/get'

it('shows only workloads from the selected group', async () => {
  // Default mock returns workloads from multiple groups
  // Add conditional override for 'production' group
  mockedGetApiV1BetaWorkloads.conditionalOverride(
    ({ query }) => query.group === 'production',
    (data) => ({
      ...data,
      workloads: [
        { name: 'prod-server-1', group: 'production', status: 'running' },
        { name: 'prod-server-2', group: 'production', status: 'running' },
      ],
    })
  )

  render(<WorkloadsList group="production" />)

  // If component sends ?group=production, it gets the filtered data
  // If component forgets the param, it gets default data with mixed groups
  await waitFor(() => {
    expect(screen.getByText('prod-server-1')).toBeVisible()
    expect(screen.getByText('prod-server-2')).toBeVisible()
  })

  // These would appear if the filter param wasn't sent correctly
  expect(screen.queryByText('dev-server')).not.toBeInTheDocument()
})

Example: Testing Empty State

it('shows empty message when group has no workloads', async () => {
  mockedGetApiV1BetaWorkloads.conditionalOverride(
    ({ query }) => query.group === 'empty-group',
    () => ({ workloads: [] })
  )

  render(<WorkloadsList group="empty-group" />)

  await waitFor(() => {
    expect(screen.getByText(/no workloads found/i)).toBeVisible()
  })
})

Multiple Conditional Overrides

Chain multiple overrides for different conditions:

mockedGetApiV1BetaWorkloads
  .conditionalOverride(
    ({ query }) => query.group === 'production',
    () => ({ workloads: productionWorkloads })
  )
  .conditionalOverride(
    ({ query }) => query.group === 'staging',
    () => ({ workloads: stagingWorkloads })
  )

Later overrides wrap earlier ones. If no predicate matches, the default fixture data is returned.

Simple Overrides (No Condition)

For test-scoped data changes without conditions, use .override():

// Override for entire test - no condition
mockedGetApiV1BetaGroups.override(() => ({
  groups: [{ name: 'only-group', registered_clients: [] }],
}))

// Modify default data
mockedGetApiV1BetaGroups.override((data) => ({
  ...data,
  groups: data.groups?.slice(0, 1),
}))

Error Responses

Use .overrideHandler() for full control over the response:

import { HttpResponse } from 'msw'

// Return error
mockedGetApiV1BetaGroups.overrideHandler(() =>
  HttpResponse.json({ error: 'Server error' }, { status: 500 })
)

// Network failure
mockedGetApiV1BetaGroups.overrideHandler(() => HttpResponse.error())

Automatic Reset

All overrides are automatically reset before each test via resetAllAutoAPIMocks() in vitest.setup.ts. No cleanup needed.

Reusable Scenarios

When the same response override is needed across many tests, define it as a scenario in the fixture instead of duplicating .override() calls.

Defining Scenarios (in fixtures)

// In fixtures/workloads/get.ts
export const mockedGetApiV1BetaWorkloads = AutoAPIMock<...>({
  workloads: [/* default data */],
}).scenario('empty', (mock) => mock.override(() => ({ workloads: [] })))

Using Scenarios (in tests)

mockedGetApiV1BetaWorkloads.activateScenario('empty')

Scenario names are defined in renderer/src/common/mocks/scenarioNames.ts. Use existing names when possible to keep scenarios consolidated.

Related Skills

  • testing-with-api-mocks - Auto-generated mocks and fixture basics
  • testing-api-assertions - Verifying mutations with recordRequests() (create/update/delete only)

版本历史

  • 12a6ab3 当前 2026-08-20 10:48

同 Skill 集合

.claude/skills/bug-fix-tdd/SKILL.md
.claude/skills/deep-links/SKILL.md
.claude/skills/security-vuln-remediation/SKILL.md
.claude/skills/skill-creator/SKILL.md
.claude/skills/skill-editor/SKILL.md
.claude/skills/testing-api-assertions/SKILL.md
.claude/skills/testing-api-overrides/SKILL.md
.claude/skills/testing-with-api-mocks/SKILL.md
.codex/skills/bug-fix-tdd/SKILL.md
.codex/skills/deep-links/SKILL.md
.codex/skills/security-vuln-remediation/SKILL.md
.codex/skills/skill-creator/SKILL.md
.codex/skills/skill-editor/SKILL.md
.codex/skills/testing-api-assertions/SKILL.md
.codex/skills/testing-with-api-mocks/SKILL.md
.cursor/skills/bug-fix-tdd/SKILL.md
.cursor/skills/deep-links/SKILL.md
.cursor/skills/security-vuln-remediation/SKILL.md
.cursor/skills/skill-creator/SKILL.md
.cursor/skills/skill-editor/SKILL.md
.cursor/skills/testing-api-assertions/SKILL.md
.cursor/skills/testing-api-overrides/SKILL.md
.cursor/skills/testing-with-api-mocks/SKILL.md

元信息

文件数
0
版本
9d42989
Hash
db8ec958
收录时间
2026-08-20 10:48

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-17 01:35
浙ICP备14020137号-1