tap-test
Installation
SKILL.md
Tap Test - Real API Integration Test Generator
Generate production-grade HTTP-level integration/e2e tests that simulate real client behavior against a live API + real database.
What is a Tap Test?
A "tap test" taps into your API exactly like a real client would:
- Real HTTP requests (not function calls)
- Real database (not mocks)
- Real server (lightweight Fastify instance)
- Real responses captured via EventEmitter or HTTP endpoints
- Real cleanup after each test
Pattern
1. beforeAll: cleanup DB → seed test data → start Fastify server
2. beforeEach: reset test users + clear captured responses
3. test: POST to API → wait for responses → verify via HTTP state endpoint + DB queries
4. afterAll: cleanup DB → stop server
Architecture
Test File
├── Fastify test server (port 3999)
│ ├── POST /api/send → simulate incoming message
│ ├── GET /api/state/:id → read user/session state
│ ├── GET /api/chat/:id → read conversation history
│ └── DELETE /api/user/:id → reset user for next test
├── EventEmitter capture → collect outgoing responses
├── Direct DB queries → verify transactions, records
└── Cleanup helpers → isolated test data (prefix/marker pattern)
Step-by-Step Process
1. Explore the project
- Find the main message processing function (router, handler, engine)
- Find the gateway/provider layer (WhatsApp, Slack, etc.)
- Find existing test setup (cleanup, seed functions)
- Identify the DB client and tables
2. Create test data isolation
- Use a phone prefix or unique marker for test data (e.g.,
97259900%) - Use a category/area marker for content data (e.g.,
area='test') - Create
cleanupTestData()that deletes ALL test data across all tables - Create
seedTestData()that inserts minimal required test content
3. Build the test server
import Fastify from 'fastify';
const app = Fastify({ logger: false });
const TEST_PORT = 3999;
// Capture responses via provider's EventEmitter
const captured: Response[] = [];
provider.onResponse((r) => captured.push(r));
// POST endpoint - simulate incoming messages
app.post('/api/send', async (req, reply) => {
const msg = provider.parseWebhook(req.body);
await engine.handleMessage(msg);
return { success: true };
});
// GET endpoint - read state
app.get('/api/state/:phone', async (req) => {
const user = await getUser(req.params.phone);
const session = await getSession(user.id);
return { user, session };
});
// DELETE endpoint - reset user
app.delete('/api/user/:phone', async (req) => {
// cascade delete all user data
});
await app.listen({ port: TEST_PORT, host: '127.0.0.1' });
4. Write HTTP helper functions
async function sendText(phone: string, text: string) {
return fetch(`${BASE}/api/send`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, type: 'text', text }),
});
}
async function waitForResponses(n: number, timeout = 15000) {
const start = Date.now();
while (captured.length < n && Date.now() - start < timeout) {
await new Promise(r => setTimeout(r, 100));
}
return captured;
}
async function getState(phone: string) {
const res = await fetch(`${BASE}/api/state/${phone}`);
return res.json();
}
5. Write test cases
Each test should:
clearCaptured()before sending- Send via HTTP (not direct function call)
waitForResponses(n)for async processing- Verify response content
- Verify state via HTTP GET
- Verify DB records via direct Supabase queries
6. Test categories to cover
- Happy path flows (full user journey start to finish)
- Error handling (invalid input, missing data)
- State transitions (verify session state after each step)
- DB verification (transactions, records created)
- Multi-user concurrent (parallel requests)
- Chat history (conversation recorded correctly)
Key Principles
- No mocks for core logic - the whole point is testing the real stack
- Mock only external services (WhatsApp API, LLM) via provider pattern
- Isolated test data - unique prefixes, cleanup before AND after
- Sequential execution - use
singleFork: truein vitest for DB consistency - Generous timeouts - real DB operations take time (10-30s per test)
- Wait, don't assume - use
waitForResponses()pattern for async processing
Vitest Config
// vitest.config.ts
export default defineConfig({
test: {
pool: 'forks',
poolOptions: { forks: { singleFork: true } },
env: { WHATSAPP_PROVIDER: 'mock' }, // or equivalent
},
});
Example Test
it('should onboard new user via HTTP', async () => {
clearCaptured();
await sendText(PHONE, 'hello');
const responses = await waitForResponses(1);
expect(responses[0].content).toContain('Welcome');
const state = await getState(PHONE);
expect(state.session.state).toBe('onboarding');
expect(state.user).not.toBeNull();
});
Weekly Installs
2
Repository
aviz85/tap-test-skillGitHub Stars
8
First Seen
Mar 15, 2026
Security Audits
Installed on
amp2
cline2
opencode2
cursor2
kimi-cli2
codex2