oxc-project/oxc · warning · OxcDiagnostic
Prefer `toHaveBeenCalled()` over `toHaveBeenCalledTimes(0)`
Error message
Prefer `toHaveBeenCalled()` over `toHaveBeenCalledTimes(0)`
What it means
This is oxlint's 'jest/prefer-to-have-been-called' diagnostic. When an expect(...).toHaveBeenCalledTimes(0) (or toBeCalledTimes(0)) matcher is found on a parsed expect call, the rule asks for the intent-revealing forms: toHaveBeenCalled() for 'was called', not.toHaveBeenCalled() for 'was not called'. The zero-times form is a double negative that reviewers must decode.
Source
Thrown at crates/oxc_linter/src/rules/jest/prefer_to_have_been_called.rs:15
use crate::{
context::LintContext,
rule::Rule,
utils::{ParsedExpectFnCall, PossibleJestNode, parse_expect_jest_fn_call},
};
use oxc_ast::{
AstKind,
ast::{CallExpression, Expression},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
fn prefer_to_have_been_called_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Prefer `toHaveBeenCalled()` over `toHaveBeenCalledTimes(0)`")
.with_help("Use `toHaveBeenCalled()` to check if function was called, or `not.toHaveBeenCalled()` to check if it wasn't called")
.with_label(span)
}
#[derive(Debug, Default, Clone)]
pub struct PreferToHaveBeenCalled;
declare_oxc_lint!(
/// ### What it does
///
/// Suggests using `toHaveBeenCalled()` or `not.toHaveBeenCalled()` over `toHaveBeenCalledTimes(0)` or `toBeCalledTimes(0)`.
///
/// ### Why is this bad?
///
/// `toHaveBeenCalled()` is more explicit and readable than `toHaveBeenCalledTimes(0)`.
///
/// ### Examples
///View on GitHub (pinned to e1e7af627c)
Solutions
- For 'never called', negate the matcher: expect(fn).not.toHaveBeenCalled().
- For asserting calls with counts > 0, keep toHaveBeenCalledTimes(n) with n >= 1, or use toHaveBeenCalled() when the exact count does not matter.
- Bulk-migrate with search and replace for toHaveBeenCalledTimes(0) → not.toHaveBeenCalled().
- Disable the rule in .oxlintrc.json if the parameterized style is accepted team-wide.
Example fix
// before expect(logger.warn).toHaveBeenCalledTimes(0); // after expect(logger.warn).not.toHaveBeenCalled();
Defensive patterns
Strategy: validation
Validate before calling
// find zero-times matchers before the rule does
const { execSync } = require('node:child_process');
console.log(execSync("rg -n 'to(Have|Be)CalledTimes\\(\\s*0\\s*\\)' tests/", { encoding: 'utf8' })); Prevention
- Write 'never called' assertions as expect(fn).not.toHaveBeenCalled().
- Reserve toHaveBeenCalledTimes(n) for n >= 1 where the exact count matters.
- Codemod existing tests once when enabling the rule to avoid piecemeal fixes.
When it happens
Trigger: Enable the rule and lint a file containing expect(fn).toHaveBeenCalledTimes(0) or the toBeCalledTimes(0) alias — the matcher name is matched with its argument literal 0 during parse_expect_jest_fn_call processing. The diagnostic fires on the matcher span.
Common situations: Tests asserting 'this spy was never invoked' written quickly as toHaveBeenCalledTimes(0), and AI- or template-generated tests that default to the parameterized matcher. Enabling recommended Jest presets surfaces them during CI lint passes.
Related errors
- Suggest using the built-in comparison matchers
- Enforce `test` and `it` usage conventions
- Use `{preferred_node_name}` instead.
- Prefer mock resolved/rejected shorthands for promises.
- Require a message for {matcher_name:?}.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/20c63cb6793ec1b0.
Report an issue: GitHub.