Vitest Module Mock Unit Test

Isolate a module under test with Vitest vi.mock hoisting, typed mock functions, and restore between tests so suites stay order-independent.

TypescriptNodeCli Tooling

Code

snippet
import { describe, it, expect, vi, beforeEach } from 'vitest';

const rateClient = {
  async getRate(currency: string): Promise<number> {
    throw new Error('real network call in unit test');
  },
};

vi.mock('./rate-client', () => ({
  rateClient: { getRate: vi.fn() },
}));

import { priceInUsd } from './pricing';
import { rateClient } from './rate-client';

vi.mocked(rateClient.getRate).mockResolvedValue(7.2);

describe('priceInUsd', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(rateClient.getRate).mockResolvedValue(7.2);
  });

  it('converts amounts using the fetched rate', async () => {
    vi.mocked(rateClient.getRate).mockResolvedValue(7.2);
    const result = await priceInUsd(100, 'eur');
    expect(result).toBeCloseTo(720);
    expect(rateClient.getRate).toHaveBeenCalledWith('eur');
  });

  it('rejects when the rate service fails', async () => {
    vi.mocked(rateClient.getRate).mockRejectedValue(new Error('fx down'));
    await expect(priceInUsd(100, 'eur')).rejects.toThrow('fx down');
  });

  it('caches repeat lookups within one call', async () => {
    await priceInUsd(50, 'eur');
    await priceInUsd(60, 'eur');
    expect(rateClient.getRate).toHaveBeenCalledTimes(2);
  });
});