Skip to content

New rule: no-unsafe-vi-mock-factory-refs #913

Description

@iankernohan

Summary

Vitest hoists vi.mock() calls to the top of the file before any module-level variable initializers run. This means a variable initialized with vi.fn() at module scope will be undefined when a vi.mock() factory callback executes, causing flaky tests that silently pass most of the time but occasionally fail depending on execution order.

The existing hoisted-apis-on-top rule enforces that vi.mock() is placed at the top level of the file, but does not catch references to non hoisted variables inside factory callbacks.

The correct fix is to wrap the variable in vi.hoisted(), which hoists the initializer alongside the vi.mock() call.

Invalid

// mockFoo is undefined when the factory runs
const mockFoo = vi.fn();
vi.mock('./foo', () => ({ foo: mockFoo }));

// chained calls are equally unsafe
const mockBar = vi.fn().mockReturnValue('x');
vi.mock('./bar', () => ({ bar: mockBar }));

Valid

// wrapped in vi.hoisted() — safe to reference in the factory
const mockFoo = vi.hoisted(() => vi.fn());
vi.mock('./foo', () => ({ foo: mockFoo }));

// inline declaration inside the factory is also fine
vi.mock('./bar', () => ({ bar: vi.fn() }));

Notes

  • I have a working implementation ready and am happy to open a PR if this is something the maintainers would like to add.
  • The rule would not flag vi.fn() variables that are only used outside of vi.mock() factory callbacks, nor variables already wrapped in vi.hoisted().
  • Real-world motivation: We encountered this pattern repeatedly in a large monorepo where tests would pass locally but fail intermittently in CI. The root cause was often the same: vi.fn() variables referenced in vi.mock() factory callbacks without vi.hoisted(). A lint rule would have caught every instance at author time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions