/testing-api-assertions
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.
$ npx -y skills add stacklok/toolhive-studio --skill testing-api-assertions --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/testing-api-assertions
Context preview
The summary Claude sees to decide when to auto-load this skill.
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.
SKILL.md
testing-api-assertions.SKILL.mdname: testing-api-assertions
description: 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.
Testing API Assertions
Verify that your code sends the correct API requests for operations with side effects.
When to Use Request Assertions
**DO use** for operations with side effects:
- Creating resources (POST)
- Updating resources (PUT/PATCH)
- Deleting resources (DELETE)
- Any mutation that changes backend state
**DON'T use** for read operations:
- Fetching data (GET)
- For these, just verify the component displays the data correctly
- The mock API is not stateful, so verifying GET requests adds no value
recordRequests()
Use `recordRequests()` to capture all API requests made during a test:
import { recordRequests } from '@/common/mocks/node'
it('creates a group with correct payload', async () => {
const rec = recordRequests()
// ... perform action that triggers API call ...
await userEvent.click(screen.getByRole('button', { name: /create/i }))
// Find the request
const request = rec.recordedRequests.find(
(r) => r.method === 'POST' && r.pathname === '/api/v1beta/groups'
)
// Assert it was made with correct data
expect(request).toBeDefined()
expect(request?.payload).toEqual({ name: 'my-group' })
})Recorded Request Shape
Each recorded request contains:
{
pathname: '/api/v1beta/groups', // URL path
method: 'POST', // HTTP method
payload: { name: 'my-group' }, // Parsed JSON body (if present)
search: { filter: 'active' }, // Query parameters
}Common Patterns
Verify POST payload
const rec = recordRequests()
// ... trigger create action ...
const createRequest = rec.recordedRequests.find(
(r) => r.method === 'POST' && r.pathname === '/api/v1beta/workloads'
)
expect(createRequest?.payload).toMatchObject({
name: 'my-server',
group: 'default',
})Verify DELETE was called
const rec = recordRequests()
// ... trigger delete action ...
const deleteRequest = rec.recordedRequests.find(
(r) =>
r.method === 'DELETE' && r.pathname === '/api/v1beta/workloads/my-server'
)
expect(deleteRequest).toBeDefined()Verify request order
const rec = recordRequests()
// ... trigger actions ...
const postRequests = rec.recordedRequests.filter((r) => r.method === 'POST')
const groupIndex = postRequests.findIndex((r) => r.pathname.includes('/groups'))
const workloadIndex = postRequests.findIndex((r) =>
r.pathname.includes('/workloads')
)
// Group must be created before workload
expect(groupIndex).toBeLessThan(workloadIndex)Verify request count
const rec = recordRequests()
// ... trigger batch action ...
const deleteRequests = rec.recordedRequests.filter(
(r) =>
r.method === 'DELETE' && r.pathname.startsWith('/api/v1beta/workloads/')
)
expect(deleteRequests).toHaveLength(3)Important Notes
- `recordRequests()` clears previous recordings when called
- Call it at the start of your test, before triggering actions
- Each test starts fresh - recordings don't persist between tests
- Use `toMatchObject()` for partial matching when payload has extra fields
When NOT to Use This
For read operations (GET) where you need to verify query parameters are sent correctly, **don't** use `recordRequests()`. Instead, use conditional overrides that return different data based on params, then verify the UI shows the expected data. See **testing-api-overrides** skill.
This approach is more robust because it tests actual user-facing behavior.
Related Skills
- **testing-with-api-mocks** - Auto-generated mocks and fixture basics
- **testing-api-overrides** - Conditional responses for testing filters/params (read operations)
Read more
name: testing-api-assertions description: 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.
Testing API Assertions
Verify that your code sends the correct API requests for operations with side effects.
When to Use Request Assertions
**DO use** for operations with side effects:
- Creating resources (POST)
- Updating resources (PUT/PATCH)
- Deleting resources (DELETE)
- Any mutation that changes backend state
**DON'T use** for read operations:
- Fetching data (GET)
- For these, just verify the component displays the data correctly
- The mock API is not stateful, so verifying GET requests adds no value
recordRequests()
Use `recordRequests()` to capture all API requests made during a test:
import { recordRequests } from '@/common/mocks/node'
it('creates a group with correct payload', async () => {
const rec = recordRequests()
// ... perform action that triggers API call ...
await userEvent.click(screen.getByRole('button', { name: /create/i }))
// Find the request
const request = rec.recordedRequests.find(
(r) => r.method === 'POST' && r.pathname === '/api/v1beta/groups'
)
// Assert it was made with correct data
expect(request).toBeDefined()
expect(request?.payload).toEqual({ name: 'my-group' })
})Recorded Request Shape
Each recorded request contains:
{
pathname: '/api/v1beta/groups', // URL path
method: 'POST', // HTTP method
payload: { name: 'my-group' }, // Parsed JSON body (if present)
search: { filter: 'active' }, // Query parameters
}Common Patterns
Verify POST payload
const rec = recordRequests()
// ... trigger create action ...
const createRequest = rec.recordedRequests.find(
(r) => r.method === 'POST' && r.pathname === '/api/v1beta/workloads'
)
expect(createRequest?.payload).toMatchObject({
name: 'my-server',
group: 'default',
})Verify DELETE was called
const rec = recordRequests()
// ... trigger delete action ...
const deleteRequest = rec.recordedRequests.find(
(r) =>
r.method === 'DELETE' && r.pathname === '/api/v1beta/workloads/my-server'
)
expect(deleteRequest).toBeDefined()Verify request order
const rec = recordRequests()
// ... trigger actions ...
const postRequests = rec.recordedRequests.filter((r) => r.method === 'POST')
const groupIndex = postRequests.findIndex((r) => r.pathname.includes('/groups'))
const workloadIndex = postRequests.findIndex((r) =>
r.pathname.includes('/workloads')
)
// Group must be created before workload
expect(groupIndex).toBeLessThan(workloadIndex)Verify request count
const rec = recordRequests()
// ... trigger batch action ...
const deleteRequests = rec.recordedRequests.filter(
(r) =>
r.method === 'DELETE' && r.pathname.startsWith('/api/v1beta/workloads/')
)
expect(deleteRequests).toHaveLength(3)Important Notes
- `recordRequests()` clears previous recordings when called
- Call it at the start of your test, before triggering actions
- Each test starts fresh - recordings don't persist between tests
- Use `toMatchObject()` for partial matching when payload has extra fields
When NOT to Use This
For read operations (GET) where you need to verify query parameters are sent correctly, **don't** use `recordRequests()`. Instead, use conditional overrides that return different data based on params, then verify the UI shows the expected data. See **testing-api-overrides** skill.
This approach is more robust because it tests actual user-facing behavior.
Related Skills
- **testing-with-api-mocks** - Auto-generated mocks and fixture basics
- **testing-api-overrides** - Conditional responses for testing filters/params (read operations)
Run any Model Context Protocol (MCP) server — securely, instantly, anywhere. ToolHive is the easiest way to discover, deploy, and manage MCP servers. Launch any MCP server in a locked-down container with just a few clicks.
Repo: stacklok/toolhive-studio
Other skills on toolhive-studio.
- /bug-fix-tdd
Reproduce and fix bugs using TDD. Use when analyzing a bug report, writing a regression test, or applying a minimal fix. Covers test placement, mock patterns, and the red-green-refactor workflow for automated bug fixing.
Open skill - /deep-links
Deep links in ToolHive Studio. Use when implementing, debugging, or asking about deep link features (toolhive-gui:// protocol), adding new deep link intents, understanding the deep link architecture, IPC model, or platform/packaging support.
Open skill - /devcontainer-dev
Spin up and interact with ToolHive Studio's containerized dev environment (Xvfb + noVNC + DinD). Use when running, testing, or debugging the app in isolation — locally, in a git worktree, or in GitHub Codespaces; when touching `.devcontainer/*`, `scripts/devcontainer-*.sh`, or
Open skill - /security-vuln-remediation
Remediate security vulnerabilities found by Grype or pnpm audit. Use when a security scan fails, a CVE needs fixing, or you need to analyze, upgrade, override, or ignore a vulnerable dependency.
Open skill - /skill-creator
Create new AI agent skills for Claude Code, Codex, and Cursor. Use when asked to create a skill, add a new agent capability, or set up a slash command.
Open skill - /skill-editor
REQUIRED for editing any skill file. Ensures changes sync to Claude, Codex, and Cursor. Never edit .claude/skills/ files directly - always use this skill.
Open skill

