unit-test-implementation
Best practices for implementing unit tests in this TypeScript monorepo. Use when writing new tests, refactoring existing tests, or fixing failing tests. Covers mocking strategies, test organization, helper functions, and assertion patterns.
Install
mkdir -p .claude/skills/unit-test-implementation && curl -L -o skill.zip "https://mcp.directory/api/skills/download/3606" && unzip -o skill.zip -d .claude/skills/unit-test-implementation && rm skill.zipInstalls to .claude/skills/unit-test-implementation
About this skill
Unit Test Implementation Guide
Mocking Dependencies
Use jest-mock-extended for External Dependencies
Use jest-mock-extended for dependencies that are external to the unit under test:
import { mock, mockDeep, mockFn, type MockProxy } from "jest-mock-extended";
let httpClient: MockProxy<HttpClient>;
let database: MockProxy<Database>;
beforeEach(() => {
httpClient = mock<HttpClient>();
database = mockDeep<Database>(); // Use mockDeep when mocking nested properties
});
NEVER Pass Complex Objects as mock() Props
jest-mock-extended's mock<T>(props) deep-processes any objects passed as initial properties. When those objects contain class instances with internal state (like Fr, EthAddress, AztecAddress, GasFees, Buffer, etc.), this causes O(2^n) exponential slowdown across tests — each test doubles the time of the previous one.
// ❌ NEVER: Passing complex domain objects as mock props
// This causes exponential test slowdown (1s → 2s → 4s → 8s → ...)
const constants = { chainId: new Fr(1), coinbase: EthAddress.random(), gasFees: GasFees.empty() };
beforeEach(() => {
builder = mock<CheckpointBuilder>({ checkpointNumber, constants });
});
// ✅ GOOD: Create mock without props, then set properties directly
beforeEach(() => {
builder = mock<CheckpointBuilder>();
Object.defineProperty(builder, 'checkpointNumber', { value: checkpointNumber });
Object.defineProperty(builder, 'constants', { value: constants });
});
Simple primitives (strings, numbers, booleans) and arrow functions are safe to pass as props. The issue is specifically with class instances that have complex prototypes.
When to Use Real Instances vs Mocks
Mock external dependencies that are:
- Difficult to set up and not to relevant to the behavior being tested
- External services or APIs
Use real instances for dependencies that are:
- Tightly coupled to the subject (e.g., a config object, a small utility class)
- Pure functions or simple data transformers
- Part of the same module and tested together
When in doubt, ask the user which dependencies should be mocked and which should use real instances. The decision depends on what behavior you're trying to test.
Prefer mockReturnValueOnce Over Complex mockImplementation
// ❌ Bad: Counter-based implementation
let callCount = 0;
mock.fetch.mockImplementation(() => {
callCount++;
return callCount === 1 ? responseA : responseB;
});
// ✅ Good: Clear sequence of return values
mock.fetch
.mockReturnValueOnce(responseA)
.mockReturnValueOnce(responseB)
.mockReturnValue(defaultResponse);
When Mock Behavior Becomes Too Complex, Create a Mock Class
If mocking requires complex state management (tracking multiple calls, conditional responses based on arguments, etc.), create a proper fake implementation instead:
/**
* A fake implementation for testing. Does NOT use jest mocks internally.
* Implements the same interface as the real class.
*/
export class MockCache {
private store = new Map<string, Item>();
// Track calls for assertions
public getCalls: string[] = [];
public setCalls: Array<{ key: string; value: Item }> = [];
async get(key: string): Promise<Item | undefined> {
this.getCalls.push(key);
return this.store.get(key);
}
async set(key: string, value: Item): Promise<void> {
this.setCalls.push({ key, value });
this.store.set(key, value);
}
/** Seed data for testing */
seed(entries: Array<[string, Item]>): this {
entries.forEach(([k, v]) => this.store.set(k, v));
return this;
}
/** Reset for reuse */
reset(): void {
this.store.clear();
this.getCalls = [];
this.setCalls = [];
}
}
Exposing Internals for Testing
Use a Derived Test Class
When you need to access or modify internal state, create a test subclass that exposes protected members:
Base class (production code):
class MyService {
protected config: Config;
constructor(config: Config) {
this.config = config;
}
protected async waitUntilReady(): Promise<void> {
await sleep(1000);
}
async execute(): Promise<Result> {
await this.waitUntilReady();
// ... implementation
}
}
Test class (test file):
class TestMyService extends MyService {
/** Override to skip delays in tests */
protected override async waitUntilReady(): Promise<void> {
return Promise.resolve();
}
/** Expose config for modification */
public updateConfig(partial: Partial<Config>): void {
this.config = { ...this.config, ...partial };
}
/** Expose config for assertions */
public getConfig(): Config {
return this.config;
}
}
Note: This requires the base class to use protected (not private) for members you need to access. Feel free to modify the base class for this when writing its unit tests.
Test Organization
Move Setup Logic to beforeEach
describe("MyFeature", () => {
let subject: TestMyService;
let dependency: MockProxy<Dependency>;
beforeEach(() => {
dependency = mock<Dependency>();
subject = new TestMyService(dependency);
});
// Tests modify subject via update methods, not direct mutation
});
Use Nested beforeEach for Variations
describe("with caching enabled", () => {
beforeEach(() => {
subject.updateConfig({ cacheEnabled: true });
});
it("returns cached results", async () => {
/* ... */
});
});
describe("with caching disabled", () => {
beforeEach(() => {
subject.updateConfig({ cacheEnabled: false });
});
it("always fetches fresh data", async () => {
/* ... */
});
});
Avoid Direct Object Mutation
// ❌ Bad: Direct mutation
config.maxRetries = 5;
// ✅ Good: Use update methods
subject.updateConfig({ maxRetries: 5 });
Helper Functions for Readability
Extract Repeated Setup Patterns
When you find yourself repeating the same 3-4 setup calls across multiple tests, extract them:
// ❌ Bad: Repeated in every test
it("test 1", async () => {
const items = await Promise.all([createItem(1), createItem(2)]);
mockSource.getItems.mockResolvedValue(items);
mockValidator.validate.mockResolvedValue(true);
// ... actual test
});
it("test 2", async () => {
const items = await Promise.all([createItem(1), createItem(2)]);
mockSource.getItems.mockResolvedValue(items);
mockValidator.validate.mockResolvedValue(true);
// ... actual test
});
// ✅ Good: Extracted helper
async function setupValidItems(count: number) {
const items = await Promise.all(times(count, (i) => createItem(i)));
mockSource.getItems.mockResolvedValue(items);
mockValidator.validate.mockResolvedValue(true);
return items;
}
it("test 1", async () => {
const items = await setupValidItems(2);
// ... actual test
});
Extract Repeated Assertion Patterns
When the same assertion sequence appears in multiple tests:
// ❌ Bad: Repeated in every test
it("test 1", async () => {
await subject.publish();
expect(publisher.enqueue).toHaveBeenCalledTimes(1);
expect(publisher.enqueue).toHaveBeenCalledWith(
expect.objectContaining({ type: "proposal" }),
expect.any(Date)
);
expect(publisher.send).toHaveBeenCalled();
});
// ✅ Good: Extracted helper
const expectPublished = () => {
expect(publisher.enqueue).toHaveBeenCalledTimes(1);
expect(publisher.enqueue).toHaveBeenCalledWith(
expect.objectContaining({ type: "proposal" }),
expect.any(Date)
);
expect(publisher.send).toHaveBeenCalled();
};
it("test 1", async () => {
await subject.publish();
expectPublished();
});
Note: Only extract when the pattern repeats across multiple tests. A single complex assertion block doesn't need extraction.
Helper Location
- Suite-specific helpers: Define inside the describe block
- Package-shared helpers: Move to
src/test/utils.ts - Cross-package helpers: Consider if they belong in a shared testing package
Assertions
Be Specific
// ❌ Too generic - doesn't verify actual behavior
expect(mock.save).toHaveBeenCalledWith(expect.anything());
// ✅ Specific - verifies the important parts
expect(mock.save).toHaveBeenCalledWith(
expect.objectContaining({
id: expectedId,
status: "active",
})
);
Use Mock Class Properties for Assertions
When using custom mock classes, assert on tracked properties:
expect(mockCache.getCalls).toEqual(["key1", "key2"]);
expect(mockCache.setCalls).toHaveLength(1);
expect(mockCache.setCalls[0]).toEqual({ key: "key1", value: expectedValue });
Reusable Test Suites
For testing multiple implementations of the same interface (e.g., in-memory vs persistent storage), create a shared test suite:
// cache_test_suite.ts
export function describeCacheImplementation(
name: string,
createCache: () => Cache
) {
describe(name, () => {
let cache: Cache;
beforeEach(() => {
cache = createCache();
});
it("stores and retrieves values", async () => {
await cache.set("key", "value");
expect(await cache.get("key")).toBe("value");
});
it("returns undefined for missing keys", async () => {
expect(await cache.get("missing")).toBeUndefined();
});
});
}
// memory_cache.test.ts
describeCacheImplementation("MemoryCache", () => new MemoryCache());
// redis_cache.test.ts
describeCacheImplementation("RedisCache", () => new RedisCache(mockClient));
Testing Async Operations
Use Promise.allSettled for Multiple Outcomes
it("handles mixed success and failure", async () => {
const results = await Promise.allSettled([
subject.processValid(),
subject.processInvalid(),
]);
expect(results).toEqual([
{ status: "fulfilled", value: expectedValue },
{
status: "rejected",
reason: expect.obj
---
*Content truncated.*
More by AztecProtocol
View all skills by AztecProtocol →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversBoost productivity with Task Master: an AI-powered tool for project management and agile development workflows, integrat
pg-aiguide — Version-aware PostgreSQL docs and best practices tailored for AI coding assistants. Improve queries, migrat
Supercharge AI platforms with Azure MCP Server for seamless Azure API Management and resource automation. Public Preview
Discover AntV Visualization Libraries for smart documentation, code examples, and best practices in g2, g6, l7, x6, f2,
Analyze your Cursor Chat History for coding insights, development patterns, and best practices with powerful search and
Access clean code rules and best practices on-demand from GitHub with Agent Rules—no local files needed, supports many f
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.