oxc-project/oxc · warning

Suggest using `jest.spyOn()` or `vi.spyOn()`.

Error message

Suggest using `jest.spyOn()` or `vi.spyOn()`.

What it means

This is the oxlint `prefer-spy-on` rule (jest/vitest plugin), with an autofix. Overwriting a property with `jest.fn()` (`Date.now = jest.fn()`) replaces the original and makes you responsible for restoring it — easy to forget, causing cross-test leakage. `jest.spyOn()`/`vi.spyOn()` records calls while keeping the original implementation and can be restored centrally via `jest.restoreAllMocks()`, `mockRestore()`, or the `restoreMocks` config. The fixer rewrites the assignment to the spyOn form, preserving any `mockImplementation` chain.

Source

Thrown at crates/oxc_linter/src/rules/shared/jest_vitest/prefer_spy_on.rs:21

    ast::{
        Argument, AssignmentExpression, CallExpression, Expression, MemberExpression,
        SimpleAssignmentTarget,
    },
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_semantic::AstNode;
use oxc_span::Span;

use crate::{
    context::LintContext,
    fixer::RuleFixer,
    utils::{
        KnownMemberExpressionProperty, PossibleJestNode, get_node_name, parse_general_jest_fn_call,
    },
};

fn use_jest_spy_on(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Suggest using `jest.spyOn()` or `vi.spyOn()`.").with_label(span)
}

pub const DOCUMENTATION: &str = r"### What it does

When mocking a function by overwriting a property you have to manually restore
the original implementation when cleaning up. When using `jest.spyOn()` Jest
keeps track of changes, and they can be restored with `jest.restoreAllMocks()`,
`mockFn.mockRestore()` or by setting `restoreMocks` to `true` in the Jest
config.

Note: The mock created by `jest.spyOn()` still behaves the same as the original
function. The original function can be overwritten with
`mockFn.mockImplementation()` or by some of the
[other mock functions](https://jestjs.io/docs/en/mock-function-api).

### Why is this bad?

Directly overwriting properties with mock functions can lead to cleanup issues

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Apply the autofix or rewrite manually: `Date.now = jest.fn(() => 10)` → `jest.spyOn(Date, 'now').mockImplementation(() => 10)`.
  2. Enable `restoreMocks: true` in jest config (or call `jest.restoreAllMocks()` in afterEach) so spies are restored automatically.
  3. For computed properties keep the computed form: `jest.spyOn(obj, key)`.

Example fix

// before
Date.now = jest.fn(() => 10);

// after
jest.spyOn(Date, 'now').mockImplementation(() => 10);
Defensive patterns

Strategy: validation

Validate before calling

// .oxlintrc.json
{ "rules": { "jest/prefer-spy-on": "error" } }

npx oxlint --fix tests/  # autofix rewrites assignments to spyOn

// jest.config.js
module.exports = { restoreMocks: true };

Prevention

When it happens

Trigger: An AssignmentExpression whose left side is a member expression (`obj.prop = ...` or computed `obj[key] = ...`) and whose right side is a `jest.fn()`/`vi.fn()` call — including member chains like `obj.prop = jest.fn().mockImplementation(() => 10)` — detected via `parse_general_jest_fn_call` with first member `fn`.

Common situations: Mocking global/static methods (`Date.now`, `Math.random`, `window.fetch`) by direct assignment; stubbing imported module methods on namespace objects; teams hitting flaky tests because a previous test's overwritten property leaked into later ones.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/e4edd2f8de5b5876. Report an issue: GitHub.