| name | unit-testing |
| description | Unit testing patterns with Jest: test structure, mocking, spying, async tests, snapshot testing, and coverage thresholds. Use when writing or improving unit tests for JavaScript/Node.js code. Use when this capability is needed. |
Unit Testing with Jest
Context
Unit to test or testing problem: $ARGUMENTS
Test Anatomy (AAA Pattern)
describe('userService.calculateTier', () => {
it('returns gold tier for spend >= 1000', () => {
const user = { totalSpend: 1500, joinDate: new Date('2023-01-01') };
const tier = userService.calculateTier(user);
expect(tier).toBe('gold');
});
it('returns silver tier for spend between 500 and 999', () => {
const user = { totalSpend: 750, joinDate: new Date('2023-01-01') };
expect(userService.calculateTier(user)).toBe('silver');
});
it('returns bronze tier for spend < 500', () => {
const user = { totalSpend: 100, joinDate: new Date() };
expect(userService.calculateTier(user)).toBe('bronze');
});
});
Describe Block Organization
describe('CartService', () => {
describe('addItem', () => {
it('adds new item to empty cart');
it('increments quantity when item already in cart');
it('throws when quantity exceeds stock');
});
describe('removeItem', () => {
it('removes item from cart');
it('throws NotFoundError when item not in cart');
});
describe('calculateTotal', () => {
it('sums all items');
it('applies discount code correctly');
it('returns 0 for empty cart');
});
});
Mocking Modules
jest.mock('../repositories/userRepository.js');
import { userRepository } from '../repositories/userRepository.js';
describe('UserService.getById', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('returns mapped user when found', async () => {
const mockUser = { _id: '123', email: 'alice@example.com', role: 'user' };
userRepository.findById.mockResolvedValue(mockUser);
const result = await userService.getById('123');
expect(userRepository.findById).toHaveBeenCalledWith('123');
expect(result).toEqual({ id: '123', email: 'alice@example.com', role: 'user' });
});
it('throws NotFoundError when repository returns null', async () => {
userRepository..();
(userService.())
..({ : });
});
(, () => {
userRepository..( ());
(userService.())
..();
});
});
Spying (Partial Mocks)
import { emailService } from '../services/emailService.js';
describe('UserService.register', () => {
it('sends welcome email after successful registration', async () => {
const spy = jest.spyOn(emailService, 'sendWelcome')
.mockResolvedValue({ messageId: 'abc' });
await userService.register({ email: 'alice@example.com', password: 'Test1234!' });
expect(spy).toHaveBeenCalledWith(
expect.objectContaining({ email: 'alice@example.com' })
);
spy.mockRestore();
});
});
Async Testing
it('handles concurrent requests correctly', async () => {
const [result1, result2] = await Promise.all([
userService.getById('1'),
userService.getById('2'),
]);
expect(result1.id).toBe('1');
expect(result2.id).toBe('2');
});
it('throws on invalid input', async () => {
await expect(userService.getById('')).rejects.toThrow('ID is required');
try {
await userService.getById('');
fail('Expected error was not thrown');
} catch (err) {
expect(err.message).toBe('ID is required');
}
});
it(, () => {
jest.();
cache = ({ : });
cache.(, );
jest.();
(cache.()).();
jest.();
});
Parameterized Tests
describe('validateEmail', () => {
test.each([
['alice@example.com', true],
['alice+tag@example.co.uk', true],
['not-an-email', false],
['@nodomain.com', false],
['', false],
])('validateEmail(%s) returns %s', (email, expected) => {
expect(validateEmail(email)).toBe(expected);
});
});
test.each([
{ spend: 1500, expected: 'gold' },
{ spend: 750, expected: 'silver' },
{ spend: 100, expected: 'bronze' },
])('tier calculation: spend=$spend → $expected', ({ spend, expected }) => {
expect(userService.calculateTier({ totalSpend: spend })).toBe(expected);
});
Coverage Configuration
export default {
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!src/config/**',
'!src/migrations/**',
],
coverageThresholds: {
global: {
lines: 80,
branches: 75,
functions: 85,
statements: 80,
},
'./src/services/': {
lines: 90,
},
},
coverageReporters: ['text', 'html', 'lcov'],
};
Test Quality Anti-Patterns
it('sets this._cache[key] in the internal map', () => {
cache.set('key', 'val');
expect(cache._cache.has('key')).toBe(true);
});
it('returns value after setting', () => {
cache.set('key', 'val');
expect(cache.get('key')).toBe('val');
});
expect(result).toBeDefined();
expect(result).not.toBeNull();
expect(result).toEqual({ id: '123', email: 'alice@example.com' });
it('returns true');
it('returns true when user has verified email and active subscription');
Source: chavangorakh1999/sde-skills — distributed by TomeVault.