Skip to content

Writing Tests

A comprehensive guide to writing tests with rjest.

Test File Location

By default, rjest looks for test files matching:

  • **/*.test.ts
  • **/*.test.tsx
  • **/*.test.js
  • **/*.test.jsx
  • **/__tests__/**/*.ts

Basic Test Structure

Simple Test

test('adds 1 + 2 to equal 3', () => {
  expect(1 + 2).toBe(3);
});

Grouped Tests

describe('Calculator', () => {
  test('adds numbers', () => {
    expect(add(1, 2)).toBe(3);
  });

  test('subtracts numbers', () => {
    expect(subtract(5, 3)).toBe(2);
  });
});

Nested Groups

describe('User', () => {
  describe('validation', () => {
    test('requires email', () => {
      expect(() => validateUser({})).toThrow('Email required');
    });

    test('validates email format', () => {
      expect(() => validateUser({ email: 'invalid' })).toThrow('Invalid email');
    });
  });

  describe('creation', () => {
    test('creates user with defaults', () => {
      const user = createUser({ email: '[email protected]' });
      expect(user.role).toBe('user');
    });
  });
});

Setup and Teardown

Per-Test Setup

describe('Database', () => {
  let db;

  beforeEach(() => {
    db = new MockDatabase();
    db.seed(testData);
  });

  afterEach(() => {
    db.clear();
  });

  test('finds user by id', () => {
    const user = db.findById(1);
    expect(user.name).toBe('Alice');
  });
});

Per-Suite Setup

describe('API Tests', () => {
  let server;

  beforeAll(async () => {
    server = await startTestServer();
  });

  afterAll(async () => {
    await server.close();
  });

  test('GET /users returns list', async () => {
    const response = await fetch(`${server.url}/users`);
    expect(response.status).toBe(200);
  });
});

Testing Functions

Pure Functions

import { capitalize, isEven, add } from './utils';

describe('capitalize', () => {
  test('capitalizes first letter', () => {
    expect(capitalize('hello')).toBe('Hello');
  });

  test('handles empty string', () => {
    expect(capitalize('')).toBe('');
  });

  test('preserves rest of string', () => {
    expect(capitalize('hELLO')).toBe('HELLO');
  });
});

Functions with Side Effects

describe('Logger', () => {
  let consoleSpy;

  beforeEach(() => {
    consoleSpy = jest.spyOn(console, 'log').mockImplementation();
  });

  afterEach(() => {
    consoleSpy.mockRestore();
  });

  test('logs messages', () => {
    log('hello');
    expect(consoleSpy).toHaveBeenCalledWith('hello');
  });
});

Testing Classes

class Calculator {
  private value = 0;

  add(n: number) {
    this.value += n;
    return this;
  }

  subtract(n: number) {
    this.value -= n;
    return this;
  }

  getResult() {
    return this.value;
  }
}

describe('Calculator', () => {
  let calc: Calculator;

  beforeEach(() => {
    calc = new Calculator();
  });

  test('starts at zero', () => {
    expect(calc.getResult()).toBe(0);
  });

  test('adds numbers', () => {
    calc.add(5).add(3);
    expect(calc.getResult()).toBe(8);
  });

  test('chains operations', () => {
    calc.add(10).subtract(3).add(5);
    expect(calc.getResult()).toBe(12);
  });
});

Testing Error Handling

Synchronous Errors

function divide(a: number, b: number): number {
  if (b === 0) throw new Error('Cannot divide by zero');
  return a / b;
}

describe('divide', () => {
  test('throws on division by zero', () => {
    expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
  });

  test('throws Error instance', () => {
    expect(() => divide(10, 0)).toThrow(Error);
  });

  test('throws matching pattern', () => {
    expect(() => divide(10, 0)).toThrow(/divide by zero/);
  });
});

Async Errors

async function fetchUser(id: string) {
  if (!id) throw new Error('ID required');
  // ... fetch logic
}

describe('fetchUser', () => {
  test('rejects without ID', async () => {
    await expect(fetchUser('')).rejects.toThrow('ID required');
  });
});

Testing Edge Cases

describe('isEven', () => {
  // Normal cases
  test('returns true for even numbers', () => {
    expect(isEven(2)).toBe(true);
    expect(isEven(4)).toBe(true);
    expect(isEven(100)).toBe(true);
  });

  test('returns false for odd numbers', () => {
    expect(isEven(1)).toBe(false);
    expect(isEven(3)).toBe(false);
  });

  // Edge cases
  test('handles zero', () => {
    expect(isEven(0)).toBe(true);
  });

  test('handles negative numbers', () => {
    expect(isEven(-2)).toBe(true);
    expect(isEven(-3)).toBe(false);
  });

  test('handles floating point', () => {
    expect(isEven(2.5)).toBe(false);
  });

  test('handles special values', () => {
    expect(isEven(NaN)).toBe(false);
    expect(isEven(Infinity)).toBe(false);
  });
});

Skipping and Focusing Tests

Skip Tests

test.skip('not ready yet', () => {
  // This test won't run
});

describe.skip('incomplete feature', () => {
  // All tests in this block are skipped
});

Focus Tests

test.only('debug this test', () => {
  // Only this test runs
});

describe.only('focus on this suite', () => {
  // Only tests in this block run
});

Todo Tests

test.todo('implement validation');
test.todo('add error handling');

Best Practices

1. One Assertion Per Test (Usually)

// Good - clear what failed
test('user has correct name', () => {
  expect(user.name).toBe('Alice');
});

test('user has correct email', () => {
  expect(user.email).toBe('[email protected]');
});

// Also OK - related assertions
test('creates valid user', () => {
  const user = createUser({ name: 'Alice' });
  expect(user.id).toBeDefined();
  expect(user.createdAt).toBeInstanceOf(Date);
});

2. Descriptive Test Names

// Good
test('returns empty array when no users match filter', () => {});
test('throws ValidationError when email is invalid', () => {});

// Bad
test('filter works', () => {});
test('error case', () => {});

3. Arrange-Act-Assert

test('adds item to cart', () => {
  // Arrange
  const cart = new Cart();
  const item = { id: 1, name: 'Widget', price: 10 };

  // Act
  cart.addItem(item);

  // Assert
  expect(cart.items).toContainEqual(item);
  expect(cart.total).toBe(10);
});

4. Test Behavior, Not Implementation

// Good - tests behavior
test('removes item from cart', () => {
  cart.addItem(item);
  cart.removeItem(item.id);
  expect(cart.items).not.toContainEqual(item);
});

// Bad - tests implementation details
test('removes item from internal array', () => {
  cart.addItem(item);
  cart.removeItem(item.id);
  expect(cart._items.length).toBe(0); // Don't test private state
});