{"record":{"id":"20c63cb6793ec1b0","repo":"oxc-project/oxc","slug":"prefer-tohavebeencalled-over-tohavebeencalled","errorCode":null,"errorMessage":"Prefer `toHaveBeenCalled()` over `toHaveBeenCalledTimes(0)`","messagePattern":"Prefer `toHaveBeenCalled\\(\\)` over `toHaveBeenCalledTimes\\(0\\)`","errorType":"validation","errorClass":"OxcDiagnostic","httpStatus":null,"severity":"warning","filePath":"crates/oxc_linter/src/rules/jest/prefer_to_have_been_called.rs","lineNumber":15,"sourceCode":"use crate::{\n    context::LintContext,\n    rule::Rule,\n    utils::{ParsedExpectFnCall, PossibleJestNode, parse_expect_jest_fn_call},\n};\nuse oxc_ast::{\n    AstKind,\n    ast::{CallExpression, Expression},\n};\nuse oxc_diagnostics::OxcDiagnostic;\nuse oxc_macros::declare_oxc_lint;\nuse oxc_span::Span;\n\nfn prefer_to_have_been_called_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"Prefer `toHaveBeenCalled()` over `toHaveBeenCalledTimes(0)`\")\n        .with_help(\"Use `toHaveBeenCalled()` to check if function was called, or `not.toHaveBeenCalled()` to check if it wasn't called\")\n        .with_label(span)\n}\n\n#[derive(Debug, Default, Clone)]\npub struct PreferToHaveBeenCalled;\n\ndeclare_oxc_lint!(\n    /// ### What it does\n    ///\n    /// Suggests using `toHaveBeenCalled()` or `not.toHaveBeenCalled()` over `toHaveBeenCalledTimes(0)` or `toBeCalledTimes(0)`.\n    ///\n    /// ### Why is this bad?\n    ///\n    /// `toHaveBeenCalled()` is more explicit and readable than `toHaveBeenCalledTimes(0)`.\n    ///\n    /// ### Examples\n    ///","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called.rs#L1-L33","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nexpect(logger.warn).toHaveBeenCalledTimes(0);\n\n// after\nexpect(logger.warn).not.toHaveBeenCalled();","handlingStrategy":"validation","validationCode":"// find zero-times matchers before the rule does\nconst { execSync } = require('node:child_process');\nconsole.log(execSync(\"rg -n 'to(Have|Be)CalledTimes\\\\(\\\\s*0\\\\s*\\\\)' tests/\", { encoding: 'utf8' }));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["lint","jest","testing","matchers","style","oxlint"],"backgroundTag":"jest-matcher-style","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}