Back to Blog
Tutorial2025-01-15

JavaScript Testing Fundamentals

Learn JavaScript testing with Vitest/Jest: unit tests, integration tests, mocking, and test-driven development.

Why Test?

Tests catch bugs before they reach users, make refactoring safe, and serve as living documentation for your code.

Setting Up Vitest

``bash npm install -D vitest `

`javascript // vitest.config.js import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true } }); `

Writing Your First Test

`javascript // math.js export function add(a, b) { return a + b; } export function multiply(a, b) { return a * b; }

// math.test.js import { describe, it, expect } from 'vitest'; import { add, multiply } from './math';

describe('Math functions', () => { it('should add two numbers', () => { expect(add(2, 3)).toBe(5); expect(add(-1, 1)).toBe(0); expect(add(0, 0)).toBe(0); });

it('should multiply two numbers', () => { expect(multiply(3, 4)).toBe(12); expect(multiply(0, 5)).toBe(0); }); }); `

Common Matchers

`javascript // Equality expect(value).toBe(42); // Strict equality expect(obj).toEqual({ a: 1 }); // Deep equality expect(value).toBeNull(); expect(value).toBeDefined(); expect(value).toBeTruthy();

// Numbers expect(value).toBeGreaterThan(3); expect(value).toBeLessThanOrEqual(10); expect(0.1 + 0.2).toBeCloseTo(0.3);

// Strings expect(str).toMatch(/regex/); expect(str).toContain('substring');

// Arrays expect(arr).toContain(item); expect(arr).toHaveLength(3);

// Errors expect(() => riskyFn()).toThrow(); expect(() => riskyFn()).toThrow('specific message'); `

Mocking

`javascript import { vi, describe, it, expect } from 'vitest';

// Mock a function const mockFetch = vi.fn(); mockFetch.mockResolvedValue({ json: () => ({ id: 1, name: 'Alice' }) });

// Mock a module vi.mock('./api', () => ({ fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }) }));

// Spy on existing function const consoleSpy = vi.spyOn(console, 'log'); myFunction(); expect(consoleSpy).toHaveBeenCalledWith('expected output'); `

Testing Async Code

`javascript it('should fetch user data', async () => { const user = await fetchUser(1); expect(user.name).toBe('Alice'); });

it('should reject with error for invalid ID', async () => { await expect(fetchUser(-1)).rejects.toThrow('Invalid ID'); }); `

Test-Driven Development (TDD)

1. Red: Write a failing test 2. Green: Write minimum code to pass 3. Refactor: Improve code while keeping tests green

`javascript // Step 1: Write test first it('should return fizz for multiples of 3', () => { expect(fizzBuzz(3)).toBe('Fizz'); expect(fizzBuzz(9)).toBe('Fizz'); });

// Step 2: Implement function fizzBuzz(n) { if (n % 15 === 0) return 'FizzBuzz'; if (n % 3 === 0) return 'Fizz'; if (n % 5 === 0) return 'Buzz'; return String(n); } ``

Best Practices

1. Test behavior, not implementation details 2. Keep tests independent — each test should run in isolation 3. Use descriptive test names that explain what and why 4. Follow AAA pattern: Arrange, Act, Assert 5. Don't mock everything — only external dependencies 6. Aim for meaningful coverage, not 100%