# Migrating from Jest to Vitest

I recently migrated from create-react-app (CRA) to ViteJS, and as part of that, I switched my test runner from Jest to [Vitest](https://vitest.dev/).

In this article, I go through all the steps I took as part of the migration, in the hope that it might help others who are going through the same process.

# Why switch?

I had originally planned to keep using Jest during the migration of CRA to ViteJS, but I kept running into issues, mainly because [Jest support for ES Modules is still experimental](https://jestjs.io/docs/ecmascript-modules). There is a Vite plugin called [vite-jest](https://github.com/sodatea/vite-jest) but it's still very much a work in progress.

Vitest is also pretty early in its development stages, but I felt that it was stable enough to give it a try, and I'm sure glad I did. It has many advantages but one thing I really like about it compared to other test runners is that it shares the same config file and plugins as Vite itself, so there's only one single pipeline to worry about.

# Migration steps

Here are all of the steps I took to migrate from Jest to Vitest.

**Note: These steps are unique to the way Jest was installed within CRA, so they may vary if you installed and configured Jest manually.**

Let's get started!

## 1\. Install dependencies

We need to install 3 dependencies:

* [Vitest](https://vitest.dev/): Our test runner.
    
* [jsdom](https://github.com/jsdom/jsdom): To mimic the browser while running tests.
    
* [c8](https://github.com/bcoe/c8): To generate our code coverage reports.
    

To install these dev dependencies, run the following command:

`npm install --save-dev vitest jsdom c8`

## 2\. Update vite.config.ts

As I mentioned previously, the beauty of using Vitest is that it integrates seamlessly with Vite, so all we have to do is update the `vite.config.ts` file.

You'll also need to add a reference to the Vitest types using a [triple slash command](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) at the top of your config file.

```plaintext
/// <reference types="vitest" />
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    // ...
  },
})
```

You may have different requirements, but here's what mine ended up looking like:

```plaintext
/// <reference types="vitest" />
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.ts',
    coverage: {
      reporter: ['text', 'html'],
      exclude: [
        'node_modules/',
        'src/setupTests.ts',
      ],
    },
  },
});
```

[Here's the full list of config options](https://vitest.dev/config/#deps), but I'll briefly explain each one I used:

* `globals`: Setting this to `true` allows you to reference the APIs globally (describe, expect, it, should etc.), just like Jest.
    
* `environment`: This is where you choose what browser-like environment you want to use.
    
* `setupFiles`: This is the path to the setup files which run before each test file. In CRA, this file (setupFiles.ts) is included by default, so I left it as is.
    
* `coverage`: This is the configuration I use for the [c8](https://github.com/bcoe/c8) reporter. I also specify the folders that I exclude from the report.
    

## 3\. Convert tests

Vitest has been designed with a Jest compatible API (describe, expect, it, should etc.), so migrating tests was an absolute breeze. Unless you mock modules or methods, you probably won't need to do anything here.

I was making use of Jest method mocking, but all I had to was change `jest.fn()` to `vi.fn()`. You'll have to import `vi` in your test if you want to do the same: `import { vi } from 'vitest';`

## 4\. Update package.json

Let's update the `package.json` scripts to reference `vitest` instead of `react-scripts`.

```plaintext
 "scripts": {
    ...
    "test": "vitest watch",
    "test:no-watch": "vitest run",
    "test:coverage": "vitest run --coverage"
  },
```

You'll notice I also added a new entry to run code coverage and provided a way to run tests without a watcher, this comes in handy when running tests in a CI/CD pipeline.

We can `npm uninstall @types/jest` since we won't need it anymore. I have kept `@testing-library/jest-dom` around though as it's required by React Testing Library.

Finally, I removed any "jest" configuration I had in this file:

```plaintext
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,ts,tsx}",
      "!/node_modules/",
    ]
  },
```

## 5\. Update tsconfig.json

To get TypeScript working with the global APIs, add `vitest/globals` to the types field in your `tsconfig.json` file.

```plaintext
"types": ["vitest/globals", .....]
```

You may also run into an error like this:

```plaintext
../node_modules/@types/jest/index.d.ts:34:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: test, describe, it, expect, beforeAll, afterAll, beforeEach,
```

At the time of writing this article, it still seems to be an [open issue](https://github.com/testing-library/jest-dom/issues/427). However, a workaround I found is to add `"skipLibCheck": true,` to your `tsconfig.json` file.

## 6\. Run tests

Hopefully the migration worked for you and all that's left to do now is to run `npm test`. 🎉

## Final thoughts

Overall I'm extremely happy with Vitest, they've really made the migration so easy. I'm even more impressed with their docs given it's still pretty new, especially the number of [examples](https://vitest.dev/guide/#examples) they have. If you found this article helpful or have suggestions, please leave a comment below. 🙂

## Want to see more?

I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on [BlueSky](https://bsky.app/profile/cathalmacdonnacha.com). 🦋

Bye for now 👋
