How to migrate from Jest to Vitest without headaches!
Tags: vitest, jest, testing, javascript
Published: 2023-12-17 20:45:14

## Introduction

[Vitest](https://vitest.dev) is a testing library that is API-compatible with Jest. The main advantages over [Jest](https://jestjs.io) are:

- Vitest is compatible out-of-the-box with [TypeScript](https://www.typescriptlang.org)! No need to deal with `ts-jest` or other complex setup.
- Vitest is fast! We have seen a improvement of around 50% on your main module!

After a few months with both Vitest and Jest in our team, we have decided to give a push and migrate the remaining projects to Vitest only. This will help us to be more efficient on all the projects (it's not always easy to switch between Jest testing and Vitest testing depending on the project!).

## The issue

For a majority of the projects, we have a small number of test files (between 1 and 10). So it's a quick win to migrate from one testing framework to another. In each test file a couple of modification is needed:

- import the `describe`, `it`,`expect` keywords from `vitest` as it's not global by default
- adjust the test if not passing

But for one project, this method seems too complex. The project is a NextJs application:

- 82 test files
- 257 tests in total

It's pretty obvious that a single Pull Request will be not a good idea, and it may take too much time to work on this task. In the team, we are not using the [Trunk-Based development](https://trunkbaseddevelopment.com) methodology, but we try to keep our modifications small:

- not too much code modified each time
- not too much time between starting creating a feature branch and the merge of the feature (about a day)

For us, it's a good way to keep the development flow and not struggle too much with merging issues. So switching 250 tests in a day doesn't seems to be a realistic objective, even for an experimented developer! Can we find a alternative path?

## The baby step idea

As usual when an issue seems too complex, a good approach is to split the problem. Here the best solution we found is to split our migration in multiple _smaller_ steps. But splitting test environment doesn't seems to be easy, right? Okay let's describe the methodology!

### First step: init the Vitest setup

The first step is to install Vitest without removing Jest. So in the project let's start by running:

```
yarn add vitest --dev
```

Create a new `vite.config.ts` file with the following:

```typescript
import { fileURLToPath, URL } from 'url';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    setupFiles: './vitest-setup.ts',
    environment: 'jsdom',
    // TODO: remove include prop after complete Vitest migration
    include: ['tests-vitest/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
    coverage: {
      reporter: ['lcov', 'text'],
    },
    outputFile: 'coverage/sonar-report.xml',
  },
  resolve: {
    alias: [{ find: '@/', replacement: fileURLToPath(new URL('./', import.meta.url))
      },
});
```

And a new `vitest-setup.ts` with:

```typescript
import "@testing-library/jest-dom/vitest";
```

(to be able to use [jest-dom](https://github.com/testing-library/jest-dom) !)

The idea is too keep all old Jest test in the `test/` folder, and move slowly the tests in a new `tests-vitest/` folder. We need to set the `include` prop in the configuration to run only the tests in the specific folder. We are also configuring some coverage with Sonar.

Here is the architecture of the project:

```
web/
	components/.       # React components
	pages/             # Still using the pages router of NextJs!
	tests/             # Jest tests
	test-vitest/       # Vitest tests
```

in `package.json`, we are adding a new command to run Vitest tests:

```json
"scripts": {
	...
	"test": "jest",
    "test:vitest": "vitest",
    ...
}
```

So we can run Jest tests with `yarn test` and Vitest tests with `yarn test:vitest`!

A final modification, we need to tweak the Jest configuration in `jest.config.js` to search tests only in the `tests/` folder:

```javascript
module.exports = {
	...
	testMatch: ['<rootDir>/tests/**/?(*.)+(spec|test).[jt]s?(x)'],
	...
}
```

And we can drop a few first tests in `tests-vitest` to be sure it's working! The first PR goal was about 4 tests files migrated, so it was a pretty small PR that we were able to validate in a day.

Finally don't forget to adjust your CI to run **both** test suite in order to still have a complete coverage.

### Second step: increase the Vitest coverage

Then slowly move a couple of test files from `tests/` to `tests-vitest/` and adjust tests to make then pass. Again the same strategy:

- add the imports for `describe`, `it`, `expect`, or `vi`
- play a bit with the test
- and _voilà_!

It took us around **11 Pull Requests** to move all **82 test files and 257 tests**. Only one test have been declared too complicated and we decided (for now) to disable it, we will try to re-enable it later on.

### Third step: remove Jest!

Finally it's time to remove all Jest dependencies! This is the most fun part of the project because you remove a lot dependencies! 🤓 Do not forget to remove also all configuration files (`jest.config.js`, `jest.setup.js`...) and we decided to move back all Vitest tests in `tests/` in this specific PR (adjust `include` in `vitest.config.ts`!).

## Conclusion

The task seems at first sight impossible when you want to apply the same rules. But you are free to decide and create new way to work a specific problem! Here instead of dealing with a monster PR with maybe 100 or more modified files, it was much more easy to split the issue and have a temporary setup during the migration time. It took us around 2 weeks to complete the migration (around one PR per day during the migration process!)