Help us improve
Share bugs, ideas, or general feedback.
From js-ts
Guides writing high-quality Jest unit tests for JavaScript and TypeScript using AAA/USE patterns, mocks/stubs, async testing, TDD, and avoiding flakiness. Use for writing tests, debugging failures, or refactoring.
npx claudepluginhub el-feo/ai-context --plugin js-tsHow this skill is triggered — by the user, by Claude, or both
Slash command
/js-ts:javascript-unit-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
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).
Provides Jest testing patterns for unit tests, mocks, spies, snapshots, setup/teardown, and matchers including equality, truthiness, numbers, strings, and arrays.
Provides strategies for writing maintainable unit, integration, and E2E tests using AAA pattern, mocking, test naming, and organization. Useful for TDD and test infrastructure.
Provides unit testing patterns for isolated business logic: AAA structure, parametrized tests (test.each, @pytest.mark.parametrize), fixture scoping, MSW/VCR mocking, factories (FactoryBoy, faker-js). Covers Vitest, Jest, pytest.
Share bugs, ideas, or general feedback.
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.