TypeScript Test Skeletons¶
Starter shapes for consumer-focused test suites following the playbook's conventions. These use Vitest syntax (describe/it/expect/vi.fn) and focus on the testing pattern, not a specific framework. Mock infra boundaries; assert consumer-visible behavior.
1) HTTP Handler Test¶
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Replace with your handler and service types
import type { OrderService } from '../src/services/order-service';
import { createOrderHandler } from '../src/handlers/create-order';
describe('POST /orders', () => {
let service: OrderService;
let handler: ReturnType<typeof createOrderHandler>;
beforeEach(() => {
service = {
create: vi.fn(),
getById: vi.fn(),
} as unknown as OrderService;
handler = createOrderHandler(service);
});
it('returns 201 with created order on valid input', async () => {
const order = { id: 'ord_1', status: 'pending' };
vi.mocked(service.create).mockResolvedValue(order);
const res = await handler({
body: { accountId: 'acct_1', amountCents: 500 },
});
expect(res.status).toBe(201);
expect(res.body).toEqual(order);
expect(service.create).toHaveBeenCalledOnce();
});
it('returns 400 on invalid input', async () => {
const res = await handler({ body: {} });
expect(res.status).toBe(400);
expect(service.create).not.toHaveBeenCalled();
});
it('returns 500 when service throws', async () => {
vi.mocked(service.create).mockRejectedValue(new Error('db down'));
const res = await handler({
body: { accountId: 'acct_1', amountCents: 500 },
});
expect(res.status).toBe(500);
});
});
2) gRPC Handler Test¶
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { AccountService } from '../src/services/account-service';
import { createGetAccountHandler } from '../src/handlers/get-account';
describe('GetAccount RPC', () => {
let service: AccountService;
let handler: ReturnType<typeof createGetAccountHandler>;
beforeEach(() => {
service = {
getById: vi.fn(),
} as unknown as AccountService;
handler = createGetAccountHandler(service);
});
it('returns account for valid id', async () => {
const account = { id: 'acct_1', name: 'Acme' };
vi.mocked(service.getById).mockResolvedValue(account);
const res = await handler({ id: 'acct_1' });
expect(res.value).toEqual(account);
});
it('returns NOT_FOUND for unknown id', async () => {
vi.mocked(service.getById).mockResolvedValue(null);
const res = await handler({ id: 'acct_unknown' });
expect(res.error?.code).toBe('NOT_FOUND');
});
it('returns INTERNAL on service failure', async () => {
vi.mocked(service.getById).mockRejectedValue(new Error('timeout'));
const res = await handler({ id: 'acct_1' });
expect(res.error?.code).toBe('INTERNAL');
});
});
3) Event / Message Consumer Test¶
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { OrderRepo } from '../src/repos/order-repo';
import { createOrderEventConsumer } from '../src/consumers/order-events';
describe('OrderEvent consumer', () => {
let repo: OrderRepo;
let consume: ReturnType<typeof createOrderEventConsumer>;
beforeEach(() => {
repo = {
upsert: vi.fn(),
markProcessed: vi.fn(),
} as unknown as OrderRepo;
consume = createOrderEventConsumer(repo);
});
it('processes a valid OrderCreated event', async () => {
const event = {
type: 'OrderCreated',
payload: { orderId: 'ord_1', amountCents: 1000 },
};
await consume(event);
expect(repo.upsert).toHaveBeenCalledWith(
expect.objectContaining({ orderId: 'ord_1' }),
);
});
it('skips unknown event types without error', async () => {
await consume({ type: 'UnknownEvent', payload: {} });
expect(repo.upsert).not.toHaveBeenCalled();
});
it('rejects event with missing required fields', async () => {
const event = { type: 'OrderCreated', payload: {} };
await expect(consume(event)).rejects.toThrow();
});
it('handles duplicate delivery idempotently', async () => {
vi.mocked(repo.markProcessed).mockResolvedValueOnce(false); // already seen
const event = {
type: 'OrderCreated',
payload: { orderId: 'ord_1', amountCents: 1000 },
};
await consume(event);
expect(repo.upsert).not.toHaveBeenCalled();
});
});
4) Job / Scheduler Test¶
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import type { CleanupService } from '../src/services/cleanup-service';
import { createCleanupJob } from '../src/jobs/cleanup';
describe('CleanupJob', () => {
let service: CleanupService;
beforeEach(() => {
vi.useFakeTimers();
service = {
purgeExpired: vi.fn(),
} as unknown as CleanupService;
});
afterEach(() => {
vi.useRealTimers();
});
it('runs purge on each interval tick', async () => {
vi.mocked(service.purgeExpired).mockResolvedValue({ deleted: 5 });
const stop = createCleanupJob(service, { intervalMs: 60_000 });
await vi.advanceTimersByTimeAsync(60_000);
expect(service.purgeExpired).toHaveBeenCalledOnce();
await vi.advanceTimersByTimeAsync(60_000);
expect(service.purgeExpired).toHaveBeenCalledTimes(2);
stop();
});
it('continues running after a failed tick', async () => {
vi.mocked(service.purgeExpired)
.mockRejectedValueOnce(new Error('db timeout'))
.mockResolvedValueOnce({ deleted: 3 });
const stop = createCleanupJob(service, { intervalMs: 60_000 });
await vi.advanceTimersByTimeAsync(60_000); // first tick -- fails
await vi.advanceTimersByTimeAsync(60_000); // second tick -- succeeds
expect(service.purgeExpired).toHaveBeenCalledTimes(2);
stop();
});
it('stops cleanly when stop is called', async () => {
const stop = createCleanupJob(service, { intervalMs: 60_000 });
stop();
await vi.advanceTimersByTimeAsync(120_000);
expect(service.purgeExpired).not.toHaveBeenCalled();
});
});
5) Cache Adapter Test¶
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createCacheAdapter } from '../src/adapters/cache';
describe('CacheAdapter', () => {
let backend: { get: ReturnType<typeof vi.fn>; set: ReturnType<typeof vi.fn>; del: ReturnType<typeof vi.fn> };
let cache: ReturnType<typeof createCacheAdapter>;
beforeEach(() => {
backend = {
get: vi.fn(),
set: vi.fn(),
del: vi.fn(),
};
cache = createCacheAdapter(backend);
});
it('returns cached value on hit', async () => {
backend.get.mockResolvedValue(JSON.stringify({ id: 'item_1' }));
const result = await cache.get('item_1');
expect(result).toEqual({ id: 'item_1' });
expect(backend.get).toHaveBeenCalledWith('item_1');
});
it('returns null on miss', async () => {
backend.get.mockResolvedValue(null);
const result = await cache.get('item_missing');
expect(result).toBeNull();
});
it('writes value and makes it retrievable', async () => {
await cache.set('item_2', { id: 'item_2' });
expect(backend.set).toHaveBeenCalledWith(
'item_2',
JSON.stringify({ id: 'item_2' }),
expect.any(Object), // TTL options
);
});
it('invalidates a cached key', async () => {
await cache.invalidate('item_1');
expect(backend.del).toHaveBeenCalledWith('item_1');
});
it('treats backend errors as cache miss', async () => {
backend.get.mockRejectedValue(new Error('redis down'));
const result = await cache.get('item_1');
expect(result).toBeNull();
});
});
Verification Checklist¶
- Each skeleton covers success, expected-error, and unexpected-error paths.
- Infra is mocked; no real network or disk I/O in unit tests.
- Tests assert consumer-visible outputs and side effects, not internal implementation.
- Fake timers are restored in
afterEachto avoid test pollution.