Mocking & Spies

In JavaScript testing, mocking and spies are techniques used to observe or control function behavior without relying on real implementations. They help isolate units of code and verify interactions.

Illustration of Mocking & Spies
Illustration of Mocking & Spies

💡 What Are Mocks & Spies?

Mocks replace real functions or modules with controlled implementations, often returning predefined values.

Spies wrap real functions to record calls and arguments without changing behavior (though some frameworks allow spies to alter behavior).

Why Use Mocking & Spies?

  • Isolate code from external dependencies (e.g., APIs, databases).
  • Control function outputs to test edge cases.
  • Verify that functions are called with expected arguments.
  • Improve test reliability and speed.

Core Concepts

Mocks vs Spies
FeatureMockSpy
PurposeReplace function implementationObserve function calls & arguments
BehaviorFake behavior (custom return values)Usually calls original function
Use CaseIsolate dependenciesVerify interactions
ModificationYesOptional

Common APIs (Jest Example)

  • jest.fn() — Creates a mock function that can track calls and set return values.
  • jest.spyOn(object, 'method') — Wraps an existing method to spy on it.
  • mockFn.mockReturnValue(value) — Sets a fixed return value for a mock function.
  • mockFn.mock.calls — Array of all calls to the mock function with arguments.
  • mockFn.mockClear() — Resets call count and arguments.

💡 Note on Frameworks

Jest is widely used and provides built-in mocking & spying utilities. Other libraries like Sinon.js also offer rich mocking/spying features.

📌 Deep Dive: Mock Function Example

JAVASCRIPT
const fetchData = jest.fn();
fetchData.mockReturnValue(Promise.resolve('data'));

test('fetchData called and returns data', async () => {
  const result = await fetchData();
  expect(fetchData).toHaveBeenCalled();
  expect(result).toBe('data');
});
Output
Passes if fetchData is called and returns 'data'

📌 Deep Dive: Spy on Method Example

JAVASCRIPT
const calculator = {
  add(a, b) {
    return a + b;
  }
};

test('spy on add method', () => {
  const spy = jest.spyOn(calculator, 'add');
  const result = calculator.add(2, 3);

  expect(spy).toHaveBeenCalledWith(2, 3);
  expect(result).toBe(5);

  spy.mockRestore();
});
Output
Passes if add called with 2, 3 and returns 5

⚠️ Avoid Over-Mocking

Excessive mocking can lead to brittle tests that don't reflect real-world behavior. Mock only external dependencies, not the code under test.

Best Practices

  • Mock external services, not your own functions unless needed.
  • Use spies to confirm side effects or method invocations.
  • Clear mocks/spies between tests to avoid state leaks.
  • Prefer explicit mock return values to increase test clarity.