jestjs/jest · error · Error

${methodName} has already been spied upon

Error message

${methodName} has already been spied upon

What it means

Thrown by `SpyRegistry.spyOn` in spyRegistry.ts:107 when the method is already a jasmine spy (`isSpy` returns true) and `respy` is not enabled. jasmine2 forbids double-spying by default to avoid losing the original reference and producing confusing call counts.

Source

Thrown at packages/jest-jasmine2/src/jasmine/spyRegistry.ts:107

          getErrorMsg(
            `could not find an object to spy upon for ${methodName}()`,
          ),
        );
      }

      if (methodName === void 0) {
        throw new Error(getErrorMsg('No method name supplied'));
      }

      if (obj[methodName] === void 0) {
        throw new Error(getErrorMsg(`${methodName}() method does not exist`));
      }

      if (obj[methodName] && isSpy(obj[methodName])) {
        if (this.respy) {
          return obj[methodName];
        } else {
          throw new Error(
            getErrorMsg(`${methodName} has already been spied upon`),
          );
        }
      }

      let descriptor;
      try {
        descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
      } catch {
        // IE 8 doesn't support `definePropery` on non-DOM nodes
      }

      if (descriptor && !(descriptor.writable || descriptor.set)) {
        throw new Error(
          getErrorMsg(
            `${methodName} is not declared writable or has no setter`,
          ),
        );

View on GitHub (pinned to f49721c78e)

Solutions

  1. Spy once — remove the duplicate `spyOn` call.
  2. If you genuinely need to re-spy, enable respy via `jasmine.getEnv().allowRespy(true)` (or `spyOnProperty` equivalent) before the second spy.
  3. Ensure tests are isolated so the previous spy is restored before the next spy attempt.

Example fix

// before
beforeEach(() => spyOn(db, 'query'));
it('x', () => { spyOn(db, 'query'); /* throws */ });
// after
beforeEach(() => spyOn(db, 'query'));
it('x', () => { db.query.mockImplementation(() => 1); });
Defensive patterns

Strategy: validation

Validate before calling

if (obj[methodName] && obj[methodName].and instanceof Object) { /* already a spy, skip or respy */ }
else spyOn(obj, methodName);

Type guard

const isSpy = (v: any): boolean =>
  v != null && v.and != null && typeof v.and === 'object' && v.calls != null;

Prevention

When it happens

Trigger: Calling `spyOn(obj, 'm')` twice in the same test, or in a `beforeEach` that runs for a test which also spies; sharing the object across tests without letting jasmine restore it.

Common situations: A shared `beforeEach` spies and an individual test spies again; helper that spies unconditionally and is called from multiple setup hooks; tests not isolated because the object is module-level and spies accumulate.

Related errors


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