prefer-spy-on
Added in v0.9.4Configuration
Rule Details
This rule requires rs.spyOn() instead of assigning rs.fn() to an object property. An assignment replaces the property for good: rs.restoreAllMocks() and the restoreMocks option cannot bring the original back, so the mock leaks into every later test that uses the object. A spy records the original method and restores it.
The rule reports a plain = assignment to a dotted or bracketed property when the assigned value is an rs.fn() call, including one followed by further configuration such as .mockReturnValue() and one wrapped in parentheses or a TypeScript as, satisfies or !. rs.fn() is recognized through rs and rstest, renamed imports from @rstest/core or rstack/test, namespace imports, require, and import.meta.rstest.rs; a local variable named rs is a different object and is not reported. The rule does not require type information.
Compound assignments such as obj.method ??= rs.fn() are not reported, because they install the mock only conditionally. Assignments to private fields are not reported either, because rs.spyOn() cannot reach them.
Incorrect
Correct
Autofix
The autofix rewrites object.method = rs.fn(implementation) to rs.spyOn(object, 'method').mockImplementation(implementation), calling spyOn on the same rs, rstest, alias or namespace that fn was called on. Configuration chained after rs.fn() stays attached to the spy. When rs.fn() has no implementation, or is given undefined or void 0, the fix passes () => {}: a spy without an implementation calls the original method, while rs.fn() returned undefined. When the chain sets .mockImplementation() right after rs.fn(), the implementation passed to rs.fn() is dropped. A bracketed key that is a comma expression is wrapped in parentheses, and a semicolon is inserted when the rewritten statement starts with ( or a <Type> assertion after a statement that has none.
The rewritten code differs from the assignment in four cases, which the fix does not detect:
rs.spyOn()throws when the property does not exist or is not a function, so an assignment that created a new property fails after the fix.mockReset(),rs.resetAllMocks()and theresetMocksoption return a spy to the original method, wherers.fn(implementation)returned toimplementation.- An implementation that is
undefinedonly at run time, such asrs.fn(implementation)with an unsetimplementation, is passed to the spy as is, so the spy calls the original method. - The spy is typed from the original method. In TypeScript,
() => {}does not type-check against a method whose return type excludesundefined, such asDate.now; give the spy an implementation that returns a valid value.
No autofix is offered when the rewrite would delete a comment, when rs.fn() is given more than one argument or a spread argument, when the fix would drop an argument whose evaluation can have an effect, such as rs.fn(createImplementation()).mockImplementation(other) or rs.fn(void setup()), or when the property is read from super.