# Mock Functions and Spies — Jest, Vitest & Testing Library

Source: https://www.skillbyai.com/en/javascript-testing/m-fns

> jest.fn / vi.fn and spyOn.

## Record calls and control return values

A **mock function** (`jest.fn()` / `vi.fn()`) records every call, its arguments and its return values, and lets you set behaviour with `mockReturnValue`, `mockResolvedValue`, `mockRejectedValue` or `mockImplementation`. Use it for callbacks and injected dependencies, then assert with `toHaveBeenCalled`, `toHaveBeenCalledWith` and `toHaveBeenCalledTimes`. A **spy** (`jest.spyOn(obj, 'method')` / `vi.spyOn`) wraps an existing method on a real object: by default it still calls the original, but you can override it. Know the reset family: `mockClear` clears recorded calls, `mockReset` also removes implementations, and `mockRestore` puts the original method back for spies (exact semantics have shifted between versions, so check the docs for yours).

## Replace the parts you do not want to test

Mocks stand in for collaborators so a test can focus on one piece of behaviour, but every mock is an assumption about the real thing.

![Three ideas: mock functions and spies, module mocks, network mocks with MSW.](assets/figures/javascript-testing/section-4-map.svg) — Figure 4.1 — From single functions, to whole modules, to the network boundary.

## A callback mock and a console spy

Vitest; swap vi for jest in Jest.

```typescript
import { afterEach, expect, it, vi } from 'vitest';
import { retry } from './retry';

afterEach(() => vi.restoreAllMocks());

it('retries until the operation succeeds', async () => {
  const op = vi
    .fn<() => Promise<string>>()
    .mockRejectedValueOnce(new Error('network'))
    .mockResolvedValueOnce('ok');

  await expect(retry(op, { attempts: 3 })).resolves.toBe('ok');
  expect(op).toHaveBeenCalledTimes(2);
});

it('warns when it gives up', async () => {
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
  const op = vi.fn().mockRejectedValue(new Error('down'));

  await expect(retry(op, { attempts: 2 })).rejects.toThrow('down');
  expect(warn).toHaveBeenCalledWith(expect.stringContaining('gave up after 2'));
});
```

## Prefer injecting dependencies

When a function receives its collaborators as arguments, a plain `vi.fn()` is enough and no module mocking is needed. Hard-to-mock code is often a hint to pass dependencies in.

**Quiz:** What does vi.spyOn(console, "warn") do by default?

- [x] Wraps console.warn so calls are recorded while the original still runs
- [ ] Deletes console.warn
- [ ] Silences all console output globally forever
- [ ] Turns warnings into test failures

*Answer:* Wraps console.warn so calls are recorded while the original still runs. Override with mockImplementation if you want it silent.
