From js-ts
Writing high-quality unit tests for JavaScript and TypeScript using Jest. Covers test structure (AAA pattern, USE naming), breaking dependencies (stubs, mocks, dependency injection), testing async code (promises, callbacks, timers), avoiding flaky tests, and test-driven development. Use when writing tests, debugging test failures, refactoring tests for maintainability, or questions about Jest, TDD, mocks, stubs, or test best practices.
npx claudepluginhub el-feo/ai-context --plugin js-tsThis skill uses the workspace's default tool permissions.
Expert guidance for writing maintainable, trustworthy unit tests in JavaScript and TypeScript using Jest. Based on "The Art of Unit Testing, Third Edition" by Roy Osherove with Vladimir Khorikov (Manning, 2024).
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
Expert guidance for writing maintainable, trustworthy unit tests in JavaScript and TypeScript using Jest. Based on "The Art of Unit Testing, Third Edition" by Roy Osherove with Vladimir Khorikov (Manning, 2024).
When helping users with unit testing:
A unit of work is all actions between an entry point (function/method we trigger) and one or more exit points (observable results).
Three types of exit points:
Must have:
Avoid:
test('sum with two numbers returns their sum', () => {
// Arrange - set up test data
const input = '1,2';
// Act - call the unit of work
const result = sum(input);
// Assert - verify the outcome
expect(result).toBe(3);
});
Format: **[U]**nit, **[S]**cenario, **[E]**xpectation
// Good examples
test('sum, with two valid numbers, returns their sum', () => { ... });
test('verify, with no uppercase letter, returns false', () => { ... });
test('save, during maintenance window, throws exception', () => { ... });
describe('Password Verifier', () => {
describe('one uppercase rule', () => {
test('given no uppercase, returns false', () => {
const verifier = makeVerifier([oneUpperCaseRule]);
expect(verifier.verify('abc')).toBe(false);
});
test('given one uppercase, returns true', () => {
const verifier = makeVerifier([oneUpperCaseRule]);
expect(verifier.verify('Abc')).toBe(true);
});
});
});
Use factory methods instead of beforeEach() to avoid scroll fatigue and keep tests self-contained.
test('processes input and returns result', () => {
const result = calculateTotal([10, 20, 30]);
expect(result).toBe(60);
});
test('adds item to cart and updates count', () => {
const cart = new ShoppingCart();
cart.addItem('apple');
expect(cart.itemCount()).toBe(1);
expect(cart.contains('apple')).toBe(true);
});
Only for testing third-party calls (exit points):
test('save calls logger with correct message', () => {
const mockLogger = { info: jest.fn() };
const repository = new Repository(mockLogger);
repository.save({ id: 1, name: 'test' });
expect(mockLogger.info).toHaveBeenCalledWith('Saved item 1');
});
Important: Use mocks only for exit points. Have one mock per test maximum. Most tests (95%+) should be return-value or state-based.
Break dependencies when code relies on:
1. Parameter Injection (Simplest)
// Before - time dependency baked in
const verify = (input) => {
const day = moment().day(); // Hard to test!
if (day === 0 || day === 6) throw Error("Weekend!");
};
// After - time injected
const verify = (input, currentDay) => {
if (currentDay === 0 || currentDay === 6) throw Error("Weekend!");
};
// Test with full control
test('on weekends, throws exception', () => {
expect(() => verify('input', 0)).toThrow("Weekend!");
});
2. Functional Injection
const verify = (logger) => (input) => {
logger.info('Verifying');
return input.length > 5;
};
// Test
test('verify logs attempt', () => {
const stubLogger = { info: jest.fn() };
const verifyFn = verify(stubLogger);
verifyFn('password');
});
3. Constructor Injection (OOP)
class Verifier {
constructor(private logger: ILogger) {}
verify(input: string): boolean {
this.logger.info('Verifying');
return true;
}
}
// Test
test('verify calls logger', () => {
const mockLogger = { info: jest.fn() };
const verifier = new Verifier(mockLogger);
verifier.verify('input');
expect(mockLogger.info).toHaveBeenCalled();
});
Stubs (incoming dependencies):
Mocks (outgoing dependencies):
// Stub - provides data IN
const stubDatabase = {
getUser: () => ({ id: 1, name: 'John' })
};
// Mock - verifies calls OUT
const mockLogger = {
info: jest.fn()
};
test('getUserName retrieves name from database and logs', () => {
const service = new UserService(stubDatabase, mockLogger);
const name = service.getUserName(1);
expect(name).toBe('John'); // Return value assertion
expect(mockLogger.info).toHaveBeenCalledWith('Retrieved user 1'); // Mock assertion
});
Extract pure logic from async operations:
// Before - everything mixed
const isWebsiteAlive = async () => {
const resp = await fetch('http://example.com');
if (!resp.ok) throw resp.statusText;
const text = await resp.text();
return text.includes('illustrative')
? { success: true }
: { success: false, status: 'missing text' };
};
// After - extract testable logic
const processFetchContent = (text) => {
return text.includes('illustrative')
? { success: true }
: { success: false, status: 'missing text' };
};
// Fast, synchronous unit test
test('with good content, returns success', () => {
const result = processFetchContent('illustrative');
expect(result.success).toBe(true);
});
Wrap async dependencies behind testable interfaces:
// network-adapter.js - wrapper for fetch
const fetchUrlText = async (url) => {
const resp = await fetch(url);
return resp.ok
? { ok: true, text: await resp.text() }
: { ok: false, text: resp.statusText };
};
// website-verifier.js - inject adapter
const isWebsiteAlive = async (network) => {
const result = await network.fetchUrlText('http://example.com');
if (!result.ok) throw result.text;
return result.text.includes('illustrative');
};
// Test with fake adapter (synchronous!)
test('with good content, returns true', async () => {
const fakeNetwork = {
fetchUrlText: () => ({ ok: true, text: 'illustrative' })
};
const result = await isWebsiteAlive(fakeNetwork);
expect(result).toBe(true);
});
test('calls callback after delay', () => {
jest.useFakeTimers();
const callback = jest.fn();
delayedGreeting(callback);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledWith('hello');
jest.useRealTimers();
});
Can you answer YES to all these?
If NO to any → Review the corresponding section in references/REFERENCE.md
For comprehensive code examples covering all patterns and scenarios, see references/EXAMPLES.md.