Skip to content

sfcc-test/no-stale-runtime-reference ​

Reports captured active runtime instances that beforeEach() replaces before a test or cleanup hook uses them.

Default behavior ​

  • ⚪ Off in both the cartridge recommended config and the sfcc-test preset. Enable it explicitly, normally as a 🟡 warning.
  • ➖ No auto-fix or editor suggestion: choosing a fresh getter or shared binding changes runtime ownership.

What it checks ​

A const initialized directly from getSfccRuntime() or getSfccTestRuntime() at file or inline suite scope captures the currently active instance. If a beforeEach() in the same suite or an ancestor calls resetSfccRuntime(), tests use a different active instance. Operations on the captured runtime no longer configure the runtime used by cartridge imports.

The rule reports reads of that binding directly inside inline test() / it() callbacks and afterEach() callbacks. Imported aliases, namespace imports, unshadowed Vitest globals, and describe.each() suites are supported. Cleanup or reset hooks in sibling suites do not affect the check.

Examples ​

Invalid
js
import { getSfccRuntime, resetSfccRuntime } from "@commerce-klaus/vitest-sfcc/runtime"
import { beforeEach, test } from "vitest"

const runtime = getSfccRuntime()
beforeEach(() => resetSfccRuntime())

test("uses a site mock", async () => {
  runtime.mock("dw/system/Site", siteMock) 
  await import("../cartridges/app_sample/cartridge/scripts/subject.js")
})
Valid
js
import { getSfccRuntime, resetSfccRuntime } from "@commerce-klaus/vitest-sfcc/runtime"
import { afterEach, beforeEach, test } from "vitest"

beforeEach(() => resetSfccRuntime())
afterEach(() => getSfccRuntime().reset())

test("uses a site mock", async () => {
  const runtime = getSfccRuntime()
  runtime.mock("dw/system/Site", siteMock)
  await import("../cartridges/app_sample/cartridge/scripts/subject.js")
})

Alternatively, assign runtime = resetSfccRuntime() in beforeEach() to a shared let runtime binding. Independent createSfccTestRuntime() instances are not active-runtime captures and are not reported.

Calling runtime.reset() clears that instance's state without replacing it. This alone does not make an existing reference stale.

Configuration ​

eslint.config.js
js
import { createSfccTestConfig } from "@commerce-klaus/eslint-config-sfcc"

export default [
  ...createSfccTestConfig(),
  {
    files: ["**/*.{test,spec}.{js,ts}"],
    rules: { "sfcc-test/no-stale-runtime-reference": "warn" },
  },
]

Static analysis boundaries ​

This deliberately checks a narrow, statically recognizable lifecycle pattern. The capture must be a direct const declaration at file or inline suite scope; the replacement hook must be registered directly in that scope or an ancestor. Reset calls must be direct hook expressions or statements, including direct assignments and variable initializers. Conditional resets and resets after control-flow statements are not analyzed.

Named callbacks, helper calls, nested deferred functions, mutable captures, destructuring, factories wrapped in other expressions, and runtime replacement hidden in setup files are not followed. Unusual code that explicitly reactivates an old instance needs manual review; this rule does not model arbitrary execution.

Oxlint support ​

Fully supported through @commerce-klaus/eslint-config-sfcc/oxlint/sfcc-test. Register the test plugin and enable the same rule in a test-file override. The same scope checks and conservative lifecycle boundaries apply in both tools.

Released under the MIT License.