Backend api-testing
Write and run API tests with Vitest for endpoints, middleware, and integrations. Use when testing API functionality, request/response validation, or error handling.
install
source · Clone the upstream repo
git clone https://github.com/sgcarstrends/sgcarstrends
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/sgcarstrends/sgcarstrends "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/api-testing" ~/.claude/skills/sgcarstrends-backend-api-testing && rm -rf "$T"
manifest:
.claude/skills/api-testing/SKILL.mdsource content
API Testing Skill
Running Tests
pnpm test # Run all tests pnpm test:watch # Watch mode pnpm test:coverage # With coverage pnpm -F @sgcarstrends/web test # Run web tests pnpm -F @sgcarstrends/web test:watch # Watch mode for web
Test Structure
apps/web/__tests__/ ├── setup.ts # Test setup ├── helpers.ts # Test utilities ├── queries/ # Query tests ├── components/ # Component tests └── utils/ # Utility tests
Testing API Routes
Basic Endpoint Test
import { describe, it, expect } from "vitest"; describe("GET /api/health", () => { it("returns 200 OK", async () => { const res = await fetch("http://localhost:3000/api/health"); expect(res.status).toBe(200); expect(await res.json()).toEqual({ status: "ok" }); }); });
Testing with Query Params
describe("GET /api/cars", () => { it("filters by month", async () => { const res = await fetch("http://localhost:3000/api/cars?month=2024-01"); expect(res.status).toBe(200); }); it("returns 400 for invalid month", async () => { const res = await fetch("http://localhost:3000/api/cars?month=invalid"); expect(res.status).toBe(400); }); });
Testing POST Endpoints
describe("POST /api/posts", () => { it("creates a new post", async () => { const res = await fetch("http://localhost:3000/api/posts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: "Test", content: "Content" }), }); expect(res.status).toBe(201); }); });
Mocking
Mock Database
import { vi } from "vitest"; import { db } from "../src/config/database"; vi.spyOn(db.query.cars, "findMany").mockResolvedValue([ { make: "Toyota", model: "Corolla", number: 100 }, ]);
Mock External APIs
const mockFetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ records: [] }), }); global.fetch = mockFetch;
Mock Redis
vi.mock("@sgcarstrends/utils", () => ({ redis: { get: vi.fn(), set: vi.fn(), del: vi.fn(), }, }));
Test Helpers
// apps/web/__tests__/helpers.ts export const createAuthHeader = (token: string) => ({ Authorization: `Bearer ${token}`, }); export const seedDatabase = async (data: any[]) => { await db.insert(cars).values(data); }; export const clearDatabase = async () => { await db.delete(cars); };
Best Practices
- Isolate tests - Each test should be independent
- Test error cases - Test both happy and error paths
- Use mocks - Mock external dependencies
- Clean up - Reset state in afterEach
- Descriptive names - Clear test descriptions
- Coverage goals - Aim for 80%+ coverage
Coverage Configuration
// vitest.config.ts export default defineConfig({ test: { coverage: { provider: "v8", thresholds: { lines: 80, functions: 80, branches: 80 }, }, }, });
References
- Vitest: https://vitest.dev
- Next.js Testing: https://nextjs.org/docs/app/building-your-application/testing