facebook/relay · error · Error

This version of `act` requires Jest's timer mocks (i.e. jest

Error message

This version of `act` requires Jest's timer mocks (i.e. jest.useFakeTimers).

What it means

After confirming the mock Scheduler, internal act() also asserts that Jest's timer mocks are installed: setTimeout._isMockFunction must be true. act() flushes work using timer-mocked setTimeout, so with real timers (jest.useFakeTimers not called) it cannot guarantee synchronous flushing and throws.

Source

Thrown at packages/react-relay/jest-react/internalAct.js:46

// This doesn't require a return value neither from the handler nor the
// then function.
interface Thenable<out R> {
  then<U>(
    onFulfill: (value: R) => void | Thenable<U> | U,
    onReject: (error: unknown) => void | Thenable<U> | U,
  ): void | Thenable<U>;
}

let actingUpdatesScopeDepth = 0;

function act<T>(scope: () => Thenable<T> | T): Thenable<T> {
  if (Scheduler.unstable_flushAllWithoutAsserting === undefined) {
    throw Error(
      'This version of `act` requires a special mock build of Scheduler.',
    );
  }
  if (setTimeout._isMockFunction !== true) {
    throw Error(
      "This version of `act` requires Jest's timer mocks " +
        '(i.e. jest.useFakeTimers).',
    );
  }

  const previousIsActEnvironment = global.IS_REACT_ACT_ENVIRONMENT;
  const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
  actingUpdatesScopeDepth++;
  if (__DEV__ && actingUpdatesScopeDepth === 1) {
    // Because this is not the "real" `act`, we set this to `false` so React
    // knows not to fire `act` warnings.
    global.IS_REACT_ACT_ENVIRONMENT = false;
  }

  const unwind = () => {
    if (__DEV__ && actingUpdatesScopeDepth === 1) {
      global.IS_REACT_ACT_ENVIRONMENT = previousIsActEnvironment;
    }

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Call jest.useFakeTimers() at the top of the test (or in setupFilesAfterAll) before using Relay test APIs
  2. If using modern fake timers, verify jest-react's expected version compatibility or configure legacy fake timers (jest.useFakeTimers('legacy'))
  3. Ensure all suites using RelayTestingEnvironment share a setup file that installs fake timers
  4. Check that no other mocking library replaces global setTimeout without setting _isMockFunction

Example fix

// before
it('renders', () => {
  const env = createMockEnvironment();
  TestRenderer.create(<Component />);
});
// after
it('renders', () => {
  jest.useFakeTimers();
  const env = createMockEnvironment();
  TestRenderer.create(<Component />);
});
Defensive patterns

Strategy: try-catch

Validate before calling

// at top of test file
if ((global.setTimeout as any)._isMockFunction !== true) {
  jest.useFakeTimers();
}

Try / catch

try {
  env.execute({...}).toPromise();
  jest.runAllTimers();
} catch (e) {
  if (String(e.message).includes('timer mocks')) {
    jest.useFakeTimers();
  }
  throw e;
}

Prevention

When it happens

Trigger: Invoking RelayTestingEnvironment APIs (testRender, resolve, etc.) inside a test that runs with real timers — no jest.useFakeTimers() call, or fake timers enabled only after act() was first invoked, or using a test runner whose fake timers don't set setTimeout._isMockFunction (e.g. some sinon/lolex configurations).

Common situations: Migrating tests to modern fake-timers APIs where the underscore mock flag differs, suites mixing vi.useFakeTimers (vitest) or sinon fake timers with jest-react, or forgetting fake timers in setupFilesAfterEach for a specific test file.

Related errors


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/d8b8c1c040925d90. Report an issue: GitHub.