.codex/skills/testing-api-overrides/SKILL.md
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.
npx skillsauth add stacklok/toolhive-studio testing-api-overridesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Good tests verify what users see, not implementation details.
Instead of:
Do this:
If the code sends wrong parameters, the mock returns wrong data, the UI shows wrong content, and the test fails. This catches real bugs.
Note: For mutations (create/update/delete), use recordRequests() instead. See the testing-api-assertions skill.
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
})
)
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
}
The transform receives default data and returns modified data:
;(data) => ({
...data, // Spread defaults
workloads: data.workloads?.filter(
// Modify as needed
(w) => w.status === 'running'
),
})
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()
})
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()
})
})
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.
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),
}))
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())
All overrides are automatically reset before each test via resetAllAutoAPIMocks() in vitest.setup.ts. No cleanup needed.
When the same response override is needed across many tests, define it as a scenario in the fixture instead of duplicating .override() calls.
// In fixtures/workloads/get.ts
export const mockedGetApiV1BetaWorkloads = AutoAPIMock<...>({
workloads: [/* default data */],
}).scenario('empty', (mock) => mock.override(() => ({ workloads: [] })))
mockedGetApiV1BetaWorkloads.activateScenario('empty')
Scenario names are defined in renderer/src/common/mocks/scenarioNames.ts. Use existing names when possible to keep scenarios consolidated.
recordRequests() (create/update/delete only)development
Start here for all API mocking in tests. Covers auto-generation, fixtures, and when to use other skills. Required reading before creating, refactoring, or modifying any test involving API calls.
development
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.
development
Verify API requests in tests. Use when testing that correct API calls are made for create, update, or delete operations. Use when testing mutations, form submissions, or actions with backend side effects.
development
REQUIRED for editing any skill file. Ensures changes sync to Claude, Codex, and Cursor. Never edit .claude/skills/ files directly - always use this skill.