Skip to content

Mock Verification

Mock verification lets you assert that your mocks were called the expected number of times and with the expected parameters. This is essential for integration testing where you need to verify that your code is making the correct API calls.

mockd tracks every request that matches a mock:

  • Call count - How many times the mock was called
  • Invocation history - Details of each call (method, path, headers, body)
  • Timestamps - When the mock was first and last called
Terminal window
# 1. Start mockd with a mock
mockd serve --config mocks.json
# 2. Run your tests (which call the mock)
npm test
# 3. Verify the mock was called
curl http://localhost:4290/mocks/my-mock-id/verify
# 4. Reset for the next test
curl -X DELETE http://localhost:4290/verify
Terminal window
GET /mocks/{id}/verify

Response:

{
"mockId": "get-users",
"callCount": 5,
"firstCalledAt": "2024-01-15T10:30:00Z",
"lastCalledAt": "2024-01-15T10:35:00Z"
}
Terminal window
POST /mocks/{id}/verify
Content-Type: application/json
{
"atLeast": 1,
"atMost": 10
}
FieldDescription
atLeastMinimum expected calls
atMostMaximum expected calls
exactlyExact expected calls

Success Response (200):

{
"success": true,
"message": "Mock called 5 times, expected at least 1"
}

Failure Response (409):

{
"success": false,
"message": "Mock called 0 times, expected at least 1"
}
Terminal window
GET /mocks/{id}/invocations

Response:

{
"invocations": [
{
"timestamp": "2024-01-15T10:30:00Z",
"method": "GET",
"path": "/api/users",
"headers": {
"Authorization": "Bearer token123",
"User-Agent": "my-app/1.0"
},
"query": {
"page": "1",
"limit": "10"
},
"body": ""
},
{
"timestamp": "2024-01-15T10:31:00Z",
"method": "GET",
"path": "/api/users",
"headers": {
"Authorization": "Bearer token123"
},
"query": {
"page": "2"
},
"body": ""
}
],
"count": 2
}
Terminal window
# Reset specific mock
DELETE /mocks/{id}/invocations
# Reset all mocks
DELETE /verify

Reset verification state before each test to ensure isolation:

beforeEach(async () => {
await fetch('http://localhost:4290/verify', { method: 'DELETE' });
});
test('fetches users on load', async () => {
// Run your code
await loadUsers();
// Verify the mock was called
const res = await fetch('http://localhost:4290/mocks/get-users/verify');
const data = await res.json();
expect(data.callCount).toBe(1);
});
test('retries on failure', async () => {
// Run code that should retry 3 times
await fetchWithRetry('/api/users');
// Assert exactly 3 calls
const res = await fetch('http://localhost:4290/mocks/get-users/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ exactly: 3 })
});
const data = await res.json();
expect(data.success).toBe(true);
});
test('sends correct authorization header', async () => {
await fetchUsers('my-token');
const res = await fetch('http://localhost:4290/mocks/get-users/invocations');
const data = await res.json();
expect(data.invocations[0].headers['Authorization']).toBe('Bearer my-token');
});
test('sends correct payload', async () => {
await createUser({ name: 'Alice', email: 'alice@example.com' });
const res = await fetch('http://localhost:4290/mocks/create-user/invocations');
const data = await res.json();
const body = JSON.parse(data.invocations[0].body);
expect(body.name).toBe('Alice');
expect(body.email).toBe('alice@example.com');
});
const ADMIN_URL = 'http://localhost:4290';
describe('User API', () => {
beforeEach(async () => {
await fetch(`${ADMIN_URL}/verify`, { method: 'DELETE' });
});
test('fetches users with pagination', async () => {
const users = await fetchUsers({ page: 2, limit: 20 });
// Verify mock was called
const verify = await fetch(`${ADMIN_URL}/mocks/get-users/verify`);
const { callCount } = await verify.json();
expect(callCount).toBe(1);
// Verify query parameters
const invocations = await fetch(`${ADMIN_URL}/mocks/get-users/invocations`);
const { invocations: calls } = await invocations.json();
expect(calls[0].query.page).toBe('2');
expect(calls[0].query.limit).toBe('20');
});
});
import requests
import pytest
ADMIN_URL = 'http://localhost:4290'
@pytest.fixture(autouse=True)
def reset_verification():
requests.delete(f'{ADMIN_URL}/verify')
yield
def test_fetches_users_with_auth():
# Run code under test
fetch_users(token='secret123')
# Verify mock was called
res = requests.get(f'{ADMIN_URL}/mocks/get-users/verify')
assert res.json()['callCount'] == 1
# Verify authorization header
res = requests.get(f'{ADMIN_URL}/mocks/get-users/invocations')
invocations = res.json()['invocations']
assert invocations[0]['headers']['Authorization'] == 'Bearer secret123'
def test_creates_user_with_correct_payload():
# Run code under test
create_user(name='Bob', email='bob@example.com')
# Verify request body
res = requests.get(f'{ADMIN_URL}/mocks/create-user/invocations')
body = json.loads(res.json()['invocations'][0]['body'])
assert body['name'] == 'Bob'
assert body['email'] == 'bob@example.com'
func TestFetchUsers(t *testing.T) {
adminURL := "http://localhost:4290"
// Reset before test
req, _ := http.NewRequest("DELETE", adminURL+"/verify", nil)
http.DefaultClient.Do(req)
// Run code under test
FetchUsers()
// Verify
resp, _ := http.Get(adminURL + "/mocks/get-users/verify")
var result struct {
CallCount int `json:"callCount"`
}
json.NewDecoder(resp.Body).Decode(&result)
if result.CallCount != 1 {
t.Errorf("expected 1 call, got %d", result.CallCount)
}
}
beforeEach(() => fetch('http://localhost:4290/verify', { method: 'DELETE' }));
mocks:
- id: get-users-paginated # Not just "mock-1"
name: Get Users with Pagination
// Verify it was called
expect(callCount).toBe(1);
// Verify it was called correctly
expect(invocations[0].headers['Authorization']).toBeDefined();
// Strict: fail if called more or less
{ exactly: 1 }
// Flexible: just ensure it was called
{ atLeast: 1 }

If running tests in parallel, use unique mock IDs or separate mockd instances to avoid verification conflicts.

FeaturemockdWireMockMockoon
Call count tracking✅ Free✅ Free
Invocation history✅ Free✅ Free
Assertion API✅ Free✅ Free
Request body capture✅ Free✅ Paid