Use a Package Alias in Vitest

Learn how to use Vitest aliases in vitest.config to use packages from other locations.

VitestTesting

In this mini-tutorial, you’ll learn:

  1. How to figure out that your failing tests are due to different versions of the same package.
  2. How to force Vitest to use the package you want with vitest.config.ts.

This is useful when you have a package.json for your source code, and a separate one for your tests. I often do this for local Firebase tests.firebase-admin clashes are common, and this resolves them.

Step 1 — Spot the Package Mismatch

Spotting this in your tests varies from package to package. For example, in firebase-admin using admin.firestore.FieldValue.increment(..) with a dependency injected db gives it away. You get an error like the following:

Error: Value for argument "dataOrField" is not a valid Firestore document. 
Couldn't serialize object of type "NumericIncrementTransform"

This occurs because your test is using your root firebase-admin to create the db. Therefore, in the function you’re testing, this db instance expects your FieldValue class to come from its source code. But, if you’re importing admin in your function to get the admin.firestore.FieldValue Vitest will use Node resolution to find it.

The first place Node resolution checks is the node_modules directory closest to the importing file in the directory structure. For me, this means looking in the node_modules in the functions directory:

📁 ./
├── 📁 node_modules/...
├── 📁 test/
│   ├── 📄 my-test.test.ts
├── 📁 functions/
│   ├── 📁 node_modules/...
│   ├── 📁 src/
│   │   ├── 📁 aDir/
│   │   │   ├── 📄 tested-file.ts

The file I’m testing is tested-file.ts and the closest node_modules is in /functions not in the root.

How To Be Sure This Is the Issue

If you’re having a similar weird issue you don’t expect to see, you can double-check it’s a package mismatch with the following:

typescript
console.log("Path is:", require.resolve("<your-package-name>"));

Use the command above in your test file and in your source code file. require.resolve will return the resolved path to the package that the require statement will import. If the paths don’t match, it’s probably a package mismatch issue.

Step 2 — Force Use Your Source Code’s Package

To prevent the mismatch, you need to ensure that your tests use the same package version as your source code. Add a file named vitest.config.ts to the root of your project:

bash
touch vitest.config.ts

Inside it, paste:

typescript
import { defineConfig } from "vitest/config";

For example, in my case:

javascript
import { defineConfig } from "vitest/config";
import path from "path";

export default defineConfig({
  resolve: {
    alias: {
      "firebase-admin": path.resolve(
        __dirname,
        "functions/node_modules/firebase-admin"
      ),
    },
  },
});

This makes Vitest search for firebase-admin in the path you provide to the alias. In my case <my-root-dir>/functions/node_modules/firebase-admin. With an alias pointing at the correct package version, your mismatch issue should be fixed.

Step 3 — Rerun Your Tests

If everything is working and the weird error is gone. Congratulations! You’ve solved the issue. If it’s still there, you may have another problem on your hands, but first, double-check the package mismatch is gone with:

typescript
console.log("Path is:", require.resolve("<your-package-name>"));

Paste it inside your test code and source code, and make sure the output matches. If it does, time search elsewhere for your problem.

Final Words

That’s it! No more wrestling mismatched types! Using a Vitest alias is a simple way to pick the package version you need. I lost hours chasing this—hope it saves you the headache.

Happy testing, and may your increments never break!