FrontendAtlas
Interview Questions
Home>JavaScript interview questions> JavaScript coding challenges >Create a Spy Function (Test Double)

Create a Spy Function (Test Double)

intermediate
By FrontendAtlas Editorial · Updated Feb 5, 2026
Implement createSpy(impl?) to produce a callable spy that records every call (args + this) and optionally delegates to an implementation. Spies are a core unit-testing tool to verify behavior at boundaries (network, storage, analytics) without needing a full mocking library.

Arguments

  • impl: Function (optional) — Optional implementation to call. The spy must preserve the caller's this value.

Returns

Function — A spy function with metadata: calls, thisValues, callCount(), lastCall(), reset().
Examples
const spy = createSpy((a, b) => a + b);
spy(1, 2); // => 3
spy.calls; // => [[1, 2]]
spy.callCount(); // => 1

const obj = { x: 2, fn: createSpy(function (n) { return this.x + n; }) };
obj.fn(3); // => 5 (this preserved)
obj.fn.thisValues[0] === obj; // true

Solution

Overview

A spy is just a function with extra state. Record calls in arrays, and if an implementation is provided, delegate to it using apply() so this binding is preserved.

1

Approach: Closure + attached metadata

Steps:

1) Create a function spy(...args).

2) Push args into spy.calls and this into spy.thisValues.

3) If impl exists, return impl.apply(this, args).

4) Attach helpers: callCount(), lastCall(), reset().

Complexity: O(1) per call and O(k) memory for k calls.

export default function createSpy(impl) {
  function spy(...args) {
    spy.calls.push(args);
    spy.thisValues.push(this);
    if (typeof impl === 'function') {
      return impl.apply(this, args);
    }
    return undefined;
  }

  spy.calls = [];
  spy.thisValues = [];
  spy.callCount = () => spy.calls.length;
  spy.lastCall = () => (spy.calls.length ? spy.calls[spy.calls.length - 1] : null);
  spy.reset = () => {
    spy.calls.length = 0;
    spy.thisValues.length = 0;
  };

  return spy;
}
export type SpyFn<TArgs extends any[] = any[], TResult = any> = ((...args: TArgs) => TResult) & {
  calls: TArgs[];
  thisValues: any[];
  callCount: () => number;
  lastCall: () => TArgs | null;
  reset: () => void;
};

export default function createSpy<TArgs extends any[] = any[], TResult = any>(
  impl?: (this: any, ...args: TArgs) => TResult
): SpyFn<TArgs, TResult> {
  const spy = function (this: any, ...args: TArgs) {
    (spy as any).calls.push(args);
    (spy as any).thisValues.push(this);
    if (typeof impl === 'function') {
      return impl.apply(this, args);
    }
    return undefined as any as TResult;
  } as SpyFn<TArgs, TResult>;

  spy.calls = [] as TArgs[];
  spy.thisValues = [] as any[];
  spy.callCount = () => spy.calls.length;
  spy.lastCall = () => (spy.calls.length ? spy.calls[spy.calls.length - 1] : null);
  spy.reset = () => {
    spy.calls.length = 0;
    spy.thisValues.length = 0;
  };

  return spy;
}

Notes & Pitfalls

Pitfalls
  • Calling impl(...args) loses the caller's this; use impl.apply(this, args).
  • Forgetting to reset between tests causes order-dependent failures.
  • Asserting too much about call order can make tests brittle; prefer behavior-based assertions.
Edge cases
  • No implementation: spy still records calls and returns undefined.
  • Zero calls: lastCall() should return null.
  • The spy may be used as a method; thisValues should reflect the receiver.
Techniques
  • Closure to keep state private.
  • Attach metadata onto the function object.
  • Preserve this via apply().

Resources

  • MDN – Function.prototype.apply

Similar questions

Create a Deferred Promise (For Async Tests)intermediateRun With a Performance Budget (Sync or Async)intermediateMeasure Function Duration (Profiling Wrapper)intermediate

Guides

Frontend interview preparation guideGuideFrontend coding interview questions and prep guideBlueprintJavaScript Problems That Actually Show UpBlueprintBuild Great UI in 60 MinutesBlueprint

Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.

Open frontend interview questionsBrowse JavaScript interview questionsOpen Essential 60Open Machine Coding HubOpen Frontend Coding ChallengesOpen System DesignOpen Interview Prep GuideOpen System Design BlueprintOpen Framework Prep PathsOpen Study PlansOpen JavaScript mastery study planOpen Company PrepOpen JavaScript Framework Prep Guide
↗Incidents hub
← Prev←3 / 88