Skip to content
ToolShedby Wasim Shaikh

Jasmine to Vitest Converter

Paste an Angular Jasmine spec and get the Vitest version: spyOn, createSpyObj, and.returnValue, toBeTrue, expectAsync, done callbacks and fakeAsync, using the official Angular CLI migration rules.

Examples:
Vitest spec
Loading the converter…
Options

The same options as the Angular CLI’s refactor-jasmine-vitest schematic.

From Karma and Jasmine to Vitest

Angular 21 made Vitest the default test runner for new projects. Most of a Jasmine spec already works in Vitest: describe, it, beforeEach, TestBed and matchers like toBe and toEqual stay the same. What has to change is everything Jasmine-specific: spies, a handful of matchers, expectAsync, done callbacks and the fake clock. Paste a spec above and the converter rewrites those parts, leaves the rest exactly as you wrote it, and lists anything you need to check by hand.

Jasmine to Vitest Cheat Sheet

JasmineVitest
spyOn(obj, 'm')vi.spyOn(obj, 'm').mockReturnValue(undefined as never)
.and.returnValue(x) / .and.callFake(fn).mockReturnValue(x) / .mockImplementation(fn)
.and.resolveTo(x) / .and.rejectWith(e).mockResolvedValue(x) / .mockRejectedValue(e)
.and.callThrough()removed: vi.spyOn calls through already
jasmine.createSpy('name')vi.fn().mockName('name')
jasmine.createSpyObj('S', ['a', 'b']){ a: vi.fn(), b: vi.fn() }
spy.calls.count() / argsFor(i)vi.mocked(spy).mock.calls.length / calls[i]
spy.calls.mostRecent().argsvi.mocked(spy).mock.lastCall
spy.calls.reset()spy.mockClear()
toBeTrue() / toHaveSize(n)toBe(true) / toHaveLength(n)
toHaveBeenCalledOnceWith(…)toHaveBeenCalledExactlyOnceWith(…)
jasmine.any(T) / objectContaining(…)expect.any(T) / expect.objectContaining(…)
expect(x).withContext('msg')expect(x, 'msg')
await expectAsync(p).toBeResolvedTo(x)await expect(p).resolves.toEqual(x)
it('…', (done) => { …; done(); })it('…', async () => { … })
fit / xit / fdescribe / xdescribeit.only / it.skip / describe.only / describe.skip
jasmine.clock().install() / tick(ms)vi.useFakeTimers() / vi.advanceTimersByTime(ms)
fakeAsync(() => { tick(ms); })async () => { await vi.advanceTimersByTimeAsync(ms); }
jasmine.SpyObj<T> / jasmine.SpyMockedObject<T> / Mock

Migrating a Whole Project

The converter handles the code in your spec files. The project setup is a separate step, and the Angular CLI does most of it for you.

  1. 1

    Switch the test builder

    Angular CLI 22 ships an optional migration that moves the test target to @angular/build:unit-test, carries over your coverage and browser settings, adds vitest, swaps the Jasmine types for vitest/globals in tsconfig.spec.json, and deletes karma.conf.js when nothing in it needs keeping. It needs the application build system.

    ng update @angular/cli --name migrate-karma-to-vitest
  2. 2

    Install a DOM

    Component tests need a DOM. The builder uses happy-dom if it is installed, otherwise jsdom.

    npm install --save-dev jsdom
  3. 3

    Convert the spec files

    Paste a spec above to review one file, or run the official schematic on the whole project. Both apply the same rules and leave TODO: vitest-migration comments where you need to decide.

    ng g @schematics/angular:refactor-jasmine-vitest
  4. 4

    Remove Karma

    Delete karma.conf.js and src/test.ts if they are still there, then uninstall the Karma and Jasmine packages.

    npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core @types/jasmine
  5. 5

    Run the tests

    Vitest runs in watch mode locally and once in CI.

    ng test

Frequently Asked Questions

Does Angular use Vitest instead of Karma now?
For new projects, yes: Vitest has been the default since Angular 21. Existing projects keep Karma, which is still supported, until you migrate them. That takes two steps: switch the test builder with ng update @angular/cli --name migrate-karma-to-vitest, then convert the Jasmine code in your spec files.
Should I use this converter or the refactor-jasmine-vitest schematic?
For a whole project, run ng g @schematics/angular:refactor-jasmine-vitest. This converter is for one spec at a time: reviewing what will change, converting a file the schematic skipped, or learning the Vitest equivalents. It follows the schematic’s rules but only rewrites the expressions it converts, so your formatting and comments stay exactly as they were. The schematic reprints the whole file.
Why does spyOn become vi.spyOn(…).mockReturnValue(undefined as never)?
A Jasmine spy replaces the method and returns undefined until you give it a strategy, but vi.spyOn calls the real method. The added mockReturnValue keeps the Jasmine behavior, and "as never" stops TypeScript complaining when the real method returns something. If the test does not depend on the stub, you can delete it. When .and.returnValue() or another strategy is chained, that decides the behavior and nothing is added.
How are done callbacks converted?
Vitest does not support done callbacks. When done() is called at the end of the test or in a .then(), the test becomes async and the promise is awaited. When done() is called somewhere else, such as inside a subscribe() or setTimeout() callback, the test returns a new Promise and done resolves it, so the test still ends when done() is called. done.fail becomes reject.
What happens to fakeAsync, tick and waitForAsync?
By default fakeAsync and tick are kept, which works if you add zone.js/plugins/vitest-patch to the test polyfills in angular.json. Turn on "Convert fakeAsync to fake timers" to rewrite them as async tests that use vi.useFakeTimers() and await vi.advanceTimersByTimeAsync(). The usual waitForAsync(() => TestBed…compileComponents()) setup is always rewritten to async/await.
Which Jasmine features have no Vitest equivalent?
jasmine.addMatchers, custom equality testers, mapContaining, setContaining, toHaveSpyInteractions, toBePending, spyOnAllFunctions and jasmine.clock().withMock() need a manual rewrite. The converter leaves the code as it was and adds a TODO comment with a link to the Vitest docs.
Do I need to import describe, it and expect from vitest?
Not with Angular’s test builder, which enables Vitest globals. Turn on "Add vitest imports" only if your own Vitest config disables globals. Types such as Mock and MockedObject are always imported, because they are not globals.
Is my code uploaded anywhere?
No. The conversion runs entirely in your browser.

Sources & Official Resources

The conversion rules follow the Angular CLI's refactor-jasmine-vitest schematic and Angular's Vitest migration guide. Last reviewed against Angular CLI 22.2 with Vitest 4 and 5.

Upgrading Angular too? The Angular Upgrade Planner lists every step between two versions, and the Control Flow Converter rewrites *ngIf and *ngFor. More in Angular tools.