Skip to content

Commit f287f46

Browse files
vitest: clarify important default behavior difference for spies (#34)
* docs: clarify important default behavior difference for spies - Changed "almost the same" to "is similar to" for spy concept comparison - Added new section explaining critical difference in default behavior: * Jasmine: spyOn() returns undefined by default (stubs the method) * Vitest: vi.spyOn() executes original implementation by default - This is crucial information for migration from Jasmine to Vitest * docs: add section about cleaning up spies * docs: add context about Angular team's decision on spy behavior
1 parent 5c95804 commit f287f46

File tree

1 file changed

+52
-1
lines changed

1 file changed

+52
-1
lines changed

blog/2025-11-migrate-to-vitest/README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ Because of this, most projects will probably not need additional changes here.
361361

362362
### Spies and mocks
363363

364-
The spying concept works almost the same as in Jasmine, but it is provided through the [`vi` object](https://vitest.dev/api/vi.html#vi-spyon):
364+
The spying concept is similar to Jasmine and is provided through the [`vi` object](https://vitest.dev/api/vi.html#vi-spyon):
365365

366366
```ts
367367
// Jasmine
@@ -393,6 +393,57 @@ const onItem = jasmine.createSpy('onItem').and.returnValue(true);
393393
const onItem = vi.fn().mockName('onItem').mockReturnValue(true);
394394
```
395395

396+
#### Important difference: Default behavior of spies
397+
398+
In Jasmine, a spy returns `undefined` by default if no specific return value has been configured.
399+
In Vitest, however, the **original implementation is executed** unless you explicitly set a mock value:
400+
401+
```ts
402+
const book = { rating: 3 };
403+
404+
// Jasmine
405+
const spy = spyOn(service, 'rateUp');
406+
const result = service.rateUp(book);
407+
// result = undefined (Spy stubbed the method)
408+
409+
// Vitest
410+
const spy = vi.spyOn(service, 'rateUp');
411+
const result = service.rateUp(book);
412+
// result = { rating: 4 } (Original method is called!)
413+
```
414+
415+
This difference is especially important when migrating existing Jasmine tests to Vitest.
416+
If you need the original Jasmine behavior (i.e., returning `undefined`), you must explicitly use `.mockReturnValue(undefined)`.
417+
418+
#### Cleaning up spies
419+
420+
Angular TestBed creates a new test environment before each test.
421+
This means services are also re-instantiated. Spies on services therefore disappear automatically between tests.
422+
423+
However, spies on **global objects** remain active:
424+
425+
```ts
426+
vi.spyOn(Math, 'random').mockReturnValue(0.5);
427+
vi.spyOn(console, 'log');
428+
```
429+
430+
Without explicit cleanup, such spies would "leak" into subsequent tests and cause unexpected behavior (test pollution).
431+
432+
**If** you mock global objects, you should clean up spies in `afterEach()`:
433+
434+
```ts
435+
import { afterEach, vi } from 'vitest';
436+
437+
afterEach(() => {
438+
vi.restoreAllMocks();
439+
});
440+
```
441+
442+
Alternatively, you can set the option `test.restoreMocks: true` in `vitest.config.ts`, and Vitest will handle the cleanup automatically.
443+
444+
Unfortunately, this setting is not the default.
445+
The Angular team has [deliberately chosen the standard Vitest behavior](https://github.com/angular/angular-cli/issues/30478), forgoing maximum Jasmine compatibility.
446+
396447
### Asynchrony without Zone.js using Vitest timers
397448

398449
Since Angular 21, unit tests run zoneless by default.

0 commit comments

Comments
 (0)