A failing test at 3 AM should tell you what's wrong without requiring a debugging session. "Expected true but got false" is not a helpful message.
// 😱 BAD: What failed? What was it checking? test('user validation', () => { const result = validateUser(userData); expect(result).toBe(true); }); // Failure output: // ✗ user validation // Expected: true // Received: false // 😱 BAD: Which field failed validation? test('form validation', () => { const form = { email: 'bad', age: 'seventeen', name: '' }; const errors = validateForm(form); expect(errors.length).toBe(0); }); // Failure output: // ✗ form validation // Expected: 0 // Received: 3
// 😱 BAD: What do these numbers mean? test('calculates total', () => { const result = calculateTotal(order); expect(result).toBe(157.45); }); // Failure output: // ✗ calculates total // Expected: 157.45 // Received: 162.38 // What changed? Tax? Shipping? Discount? Item prices?
// ✅ GOOD: Clear context in failure message test('user validation', () => { const result = validateUser(userData); expect(result.valid).toBe(true, `User validation failed: ${result.errors.join(', ')}` ); }); // Failure output: // ✗ user validation // User validation failed: email format invalid, age must be number // ✅ GOOD: Show what was being validated test('form validation', () => { const form = { email: 'bad', age: 'seventeen', name: '' }; const errors = validateForm(form); expect(errors).toEqual([], `Form validation failed: Input: ${JSON.stringify(form, null, 2)} Errors: ${JSON.stringify(errors, null, 2)}` ); });
// ✅ GOOD: Break down the calculation for clarity test('calculates order total correctly', () => { const order = createOrder({ items: [ { name: 'Widget', price: 50, quantity: 2 }, { name: 'Gadget', price: 30, quantity: 1 } ], discount: 'SAVE10', shipping: 'express' }); const result = calculateTotal(order); // Break down the expectation const expectedSubtotal = 130; // (50*2 + 30*1) const expectedDiscount = 13; // 10% off const expectedShipping = 15; // express shipping const expectedTax = 14.70; // 12% tax on (130-13) const expectedTotal = expectedSubtotal - expectedDiscount + expectedShipping + expectedTax; expect(result).toEqual({ subtotal: expectedSubtotal, discount: expectedDiscount, shipping: expectedShipping, tax: expectedTax, total: expectedTotal }, ` Order total calculation mismatch: Items: 2x Widget @ $50, 1x Gadget @ $30 Expected breakdown: Subtotal: $${expectedSubtotal} Discount (SAVE10): -$${expectedDiscount} Shipping (express): +$${expectedShipping} Tax (12%): +$${expectedTax} Total: $${expectedTotal} Actual: $${result.total} `); });
// Provide context about what was being tested function assertUserCanPurchase(user, product) { const canPurchase = checkPurchaseEligibility(user, product); expect(canPurchase).toBe(true, ` User ${user.email} cannot purchase ${product.name}: - User age: ${user.age} (required: ${product.minAge}) - User balance: $${user.balance} (price: $${product.price}) - User verified: ${user.verified} - Product available: ${product.inStock} `); } test('verified adult can purchase age-restricted item', () => { const user = createUser({ age: 21, balance: 100, verified: true }); const product = createProduct({ name: 'Wine', price: 25, minAge: 21 }); assertUserCanPurchase(user, product); });
// Create domain-specific matchers with good messages expect.extend({ toBeValidEmail(received) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const pass = emailRegex.test(received); return { pass, message: () => pass ? `Expected "${received}" not to be a valid email` : `Expected "${received}" to be a valid email address. Common issues: missing @, no domain, spaces` }; }, toBeWithinRange(received, min, max) { const pass = received >= min && received <= max; return { pass, message: () => pass ? `Expected ${received} not to be within range [${min}, ${max}]` : `Expected ${received} to be within range [${min}, ${max}] Value is ${received < min ? 'too low' : 'too high'} by ${ Math.abs(received - (received < min ? min : max)) }` }; } }); // Usage test('validates email format', () => { expect('not-an-email').toBeValidEmail(); // Failure: Expected "not-an-email" to be a valid email address. // Common issues: missing @, no domain, spaces }); test('age within valid range', () => { expect(150).toBeWithinRange(18, 120); // Failure: Expected 150 to be within range [18, 120] // Value is too high by 30 });
// Add context to snapshot tests test('renders error state correctly', () => { const errors = [ 'Email is required', 'Password too weak', 'Terms must be accepted' ]; const component = render(<Form errors={errors} />); expect(component).toMatchSnapshot(` Form with validation errors: - ${errors.length} errors shown - Fields: email, password, terms - State: initial submission attempt `); });
// Show both sides of the equation expect(result).toBe(expected, ` Input: ${JSON.stringify(input)} Expected: ${expected} Actual: ${result} `);
expect(user.canVote).toBe(true, ` User cannot vote: Age: ${user.age} (must be >= 18) Citizenship: ${user.citizenship} (must be valid) Registration: ${user.registered} (must be registered) `);
// Instead of multiple assertions expect(user.name).toBe('Alice'); expect(user.age).toBe(25); expect(user.role).toBe('admin'); // Use object comparison to see all issues expect(user).toEqual({ name: 'Alice', age: 25, role: 'admin' });
await expect(fetchUser(id)) .rejects .toThrow(`Failed to fetch user ${id}: Network timeout after 5s`); await expect(async () => { const user = await fetchUser(id); return user.status; }).rejects.toThrow(`User ${id} is inactive`);