Lesson 08.1: Setting Up Unit Testing
Duration: 30 minutes | Difficulty: Intermediate
Learning Objectives
By the end of this lesson, you will be able to:
- Choose between Jest and Vitest
- Install and configure your test runner
- Write your first passing test
- Run tests from the command line
Choosing a Test Framework
Two popular options dominate JavaScript testing:
| Feature | Jest | Vitest |
|---|---|---|
| Speed | Good | Excellent (2-5x faster) |
| ES Modules | Config needed | Native support |
| TypeScript | Config needed | Native support |
| Watch mode | Yes | Yes (HMR-powered) |
| Community | Massive | Growing fast |
| Best for | Established projects | New projects, Vite users |
Recommendation: Use Vitest for new projects. Use Jest if your team already knows it.
Installation: Vitest (Recommended)
Step 1: Install Dependencies
# Navigate to your project
cd my-project
# Install Vitest
npm install -D vitest
Step 2: Add Test Script
// package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
Step 3: Create Config (Optional)
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true, // Use describe/it/expect without import
environment: 'node', // or 'jsdom' for browser code
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
threshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
}
}
});
Step 4: Verify Installation
npm test
# You should see:
# No test files found, exiting with code 0
Installation: Jest (Alternative)
Step 1: Install Dependencies
# For JavaScript projects
npm install -D jest
# For TypeScript projects
npm install -D jest ts-jest @types/jest
Step 2: Add Test Script
// package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
Step 3: Create Config for TypeScript
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
Your First Test
Step 1: Create a Function to Test
// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
Step 2: Create a Test File
// tests/utils/math.test.ts (or src/utils/math.test.ts)
import { describe, it, expect } from 'vitest'; // Vitest
// import { describe, it, expect } from '@jest/globals'; // Jest
import { add, multiply } from '../../src/utils/math';
describe('math utilities', () => {
describe('add', () => {
it('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(add(-1, 5)).toBe(4);
});
it('should handle zero', () => {
expect(add(0, 0)).toBe(0);
});
});
describe('multiply', () => {
it('should multiply two numbers', () => {
expect(multiply(3, 4)).toBe(12);
});
it('should return zero when multiplied by zero', () => {
expect(multiply(5, 0)).toBe(0);
});
});
});
Step 3: Run Tests
npm test
# Expected output:
# ✓ math utilities > add > should add two positive numbers
# ✓ math utilities > add > should handle negative numbers
# ✓ math utilities > add > should handle zero
# ✓ math utilities > multiply > should multiply two numbers
# ✓ math utilities > multiply > should return zero when multiplied by zero
#
# Test Files 1 passed
# Tests 5 passed
Project Structure
Organize your tests in one of these patterns:
Pattern 1: Colocated Tests (Recommended)
src/
├── utils/
│ ├── math.ts
│ └── math.test.ts ← Test next to code
├── services/
│ ├── user.ts
│ └── user.test.ts
└── index.ts
Advantages: Easy to find tests, encourages testing.
Pattern 2: Separate Tests Directory
src/
├── utils/
│ └── math.ts
├── services/
│ └── user.ts
└── index.ts
tests/
├── utils/
│ └── math.test.ts ← Mirrors src structure
└── services/
└── user.test.ts
Advantages: Clean separation, easier to exclude from builds.
Watch Mode
Run tests automatically when files change:
# Vitest (default watch mode)
npm test
# Jest watch mode
npm test -- --watch
Watch mode shortcuts:
a- Run all testsf- Run only failed testsp- Filter by filenamet- Filter by test nameq- Quit
Common Setup Issues
Issue 1: "Cannot find module"
# Error: Cannot find module './math'
Fix: Check import paths and file extensions:
// ❌ Wrong (might fail)
import { add } from './math';
// ✅ Correct (explicit)
import { add } from './math.js'; // or configure moduleResolution
Issue 2: "Unexpected token 'import'"
Fix: Enable ES modules or use CommonJS:
// package.json - Enable ES modules
{
"type": "module"
}
Or configure your test runner for ES modules.
Issue 3: TypeScript Errors
Fix: Install type definitions:
npm install -D @types/jest # For Jest
# Vitest includes its own types
SpecWeave Integration
When using SpecWeave, tests are defined in tasks.md:
## T-003: Implement math utilities
**Status**: [ ] pending
**Satisfies ACs**: AC-US1-02
### Test Plan
- `add(2, 3)` → returns `5`
- `add(-1, 5)` → returns `4`
- `multiply(3, 4)` → returns `12`
SpecWeave's /sw:do generates test files from these plans.
Key Takeaways
- Choose Vitest for new projects — faster, better TypeScript support
- Choose Jest for existing projects — huge community, battle-tested
- Colocate tests with code — easier to find and maintain
- Use watch mode during development — immediate feedback
- Start with simple tests —
add(2, 3)before complex logic
Practice Exercise
- Create a new project with
npm init -y - Install Vitest:
npm install -D vitest - Create
src/utils/string.tswith acapitalizefunction - Create
tests/utils/string.test.tswith tests - Run
npm testand verify all tests pass
Next Lesson
You've set up your testing environment. Now let's write effective unit tests!