Unit Testing Guide
Good Tests: The FIRST Principles
| Principle | Meaning |
|---|---|
| Fast | Run in milliseconds; no sleep, no I/O |
| Independent | No shared state between tests; order doesn't matter |
| Repeatable | Same result every time; no randomness, no time dependencies |
| Self-validating | Pass or fail automatically โ no manual inspection needed |
| Timely | Written at the same time as production code (TDD) or immediately after |
Test Structure: AAA Pattern
// JavaScript (Vitest / Jest) โ AAA pattern
import { describe, it, expect, beforeEach } from 'vitest';
import { ShoppingCart } from './cart';
describe('ShoppingCart', () => {
let cart;
beforeEach(() => {
cart = new ShoppingCart(); // fresh state per test
});
it('calculates total with no items', () => {
// Arrange โ (done in beforeEach)
// Act
const total = cart.total();
// Assert
expect(total).toBe(0);
});
it('calculates total with multiple items including discount', () => {
// Arrange
cart.add({ id: 1, price: 100, qty: 2 });
cart.add({ id: 2, price: 50, qty: 1 });
cart.applyDiscount(10); // 10% discount
// Act
const total = cart.total();
// Assert
expect(total).toBe(225); // (200 + 50) * 0.9
});
it('throws when adding item with negative price', () => {
expect(() => cart.add({ id: 1, price: -5, qty: 1 }))
.toThrow('Price must be non-negative');
});
});
Go Unit Tests
// Go โ table-driven tests (idiomatic)
package calculator_test
import (
"testing"
"github.com/myapp/calculator"
)
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive numbers", 2, 3, 5},
{"negative + positive", -2, 5, 3},
{"both negative", -3, -4, -7},
{"zero values", 0, 0, 0},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := calculator.Add(tc.a, tc.b)
if got != tc.want {
t.Errorf("Add(%d, %d) = %d, want %d", tc.a, tc.b, got, tc.want)
}
})
}
}
// Using testify for cleaner assertions
func TestDivide(t *testing.T) {
result, err := calculator.Divide(10, 2)
require.NoError(t, err)
assert.Equal(t, 5.0, result)
_, err = calculator.Divide(10, 0)
assert.ErrorIs(t, err, calculator.ErrDivisionByZero)
}