jestjs/jest · error · Error

${expectedArgument} must be a positive integer

Error message

${expectedArgument} must be a positive integer

What it means

Thrown by `toHaveBeenNthCalledWith` (spyMatchers.ts:950) when the `n` argument (the 1-based call index) is not a positive safe integer. The matcher indexes into `mock.calls[nth - 1]`, so values like 0, -1, 1.5, NaN, Infinity, or a string would produce invalid indexing. Jest uses `Number.isSafeInteger(nth) && nth >= 1` as the gate and renders the message with `expectedArgument = 'n'`, so the rendered message reads `n must be a positive integer`.

Source

Thrown at packages/expect/src/spyMatchers.ts:969

  [number, ...Array<unknown>]
> =>
  function (received: any, nth, ...expected): SyncExpectationResult {
    const expectedArgument = 'n';
    const options: MatcherHintOptions = {
      expectedColor: (arg: string) => arg,
      isNot: this.isNot,
      promise: this.promise,
      secondArgument: '...expected',
    };
    ensureMockOrSpy(
      received,
      'toHaveBeenNthCalledWith',
      expectedArgument,
      options,
    );

    if (!Number.isSafeInteger(nth) || nth < 1) {
      throw new Error(
        matcherErrorMessage(
          matcherHint(
            'toHaveBeenNthCalledWith',
            undefined,
            expectedArgument,
            options,
          ),
          `${expectedArgument} must be a positive integer`,
          printWithType(expectedArgument, nth, stringify),
        ),
      );
    }

    const receivedIsSpy = isSpy(received);
    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();

    const calls = receivedIsSpy
      ? received.calls.all().map((x: any) => x.args)

View on GitHub (pinned to f49721c78e)

Solutions

  1. Use 1-based indexing: the first call is `n = 1`, not 0.
  2. If deriving from `findIndex`, add 1: `.toHaveBeenNthCalledWith(idx + 1, ...)`.
  3. For the last call, prefer `toHaveBeenLastCalledWith(...)` to avoid index math.
  4. Validate `n` is a positive integer before the assertion if it is dynamic.

Example fix

// before
const idx = mock.calls.findIndex(c => c[0] === 'x');
expect(mock).toHaveBeenNthCalledWith(idx, 'x', payload);

// after
const idx = mock.calls.findIndex(c => c[0] === 'x');
expect(mock).toHaveBeenNthCalledWith(idx + 1, 'x', payload);
Defensive patterns

Strategy: validation

Validate before calling

if (!Number.isSafeInteger(n) || n < 1) {
  throw new Error(`n must be a positive integer, got ${n}`);
}
expect(fn).toHaveBeenNthCalledWith(n, ...args);

Type guard

const isPositiveInteger = (v: unknown): v is number =>
  typeof v === 'number' && Number.isSafeInteger(v) && v >= 1;

Try / catch

try {
  expect(fn).toHaveBeenNthCalledWith(n, ...args);
} catch (e) {
  if (e instanceof Error && /must be a positive integer/.test(e.message)) {
    console.error('n was', n, '(calls are 1-indexed)');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `expect(fn).toHaveBeenNthCalledWith(0, ...)` (zero is invalid — calls are 1-indexed), `.toHaveBeenNthCalledWith(-1, ...)`, `.toHaveBeenNthCalledWith(1.5, ...)`, `.toHaveBeenNthCalledWith('1', ...)` (string), `.toHaveBeenNthCalledWith(NaN, ...)`, or `.toHaveBeenNthCalledWith(calls.length, ...)` intending the last call (off-by-one — should be `calls.length` for the Nth, which is correct, but `calls.length - 1` would be off).

Common situations: Off-by-one errors assuming 0-based indexing; passing a computed index from `findIndex` (0-based) directly instead of adding 1; dynamic index from config that defaulted to 0; porting from a custom matcher that was 0-based.

Related errors


AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03). Data as JSON: /data/errors/c6bce63cfd6ce87c.json. Report an issue: GitHub.