Vitest, by default, tries to be helpful by inheriting your Vite configuration for testing. This means your vite.config.js (or .ts) file becomes the foundation for your test environment, including things like aliases, plugins, and preprocessors. This can be a double-edged sword: convenient when it works, but a source of confusion when it doesn’t.

Let’s see this in action. Imagine you have a simple Vite project with a vite.config.js that sets up an alias for a utils directory:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@utils': '/src/utils',
    },
  },
});

And you have a test file that uses this alias:

// src/utils/math.js
export function add(a, b) {
  return a + b;
}

// src/components/Calculator.test.js
import { add } from '@utils/math'; // Using the alias

describe('Calculator', () => {
  it('should add two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });
});

When you run vitest, it reads vite.config.js. Because of the resolve.alias configuration, Vitest understands that @utils should resolve to /src/utils. The test passes seamlessly.

$ vitest
# ... (test output)

The primary problem Vitest solves by inheriting the Vite config is consistency. You’re not defining your project’s module resolution, transformations, or asset handling in two separate places. This reduces the cognitive load and the likelihood of "it works on my machine" scenarios between your development server and your test runner.

Internally, Vitest uses Vite’s loadConfigFromFile to find and parse your vite.config.js. It then merges this configuration with its own default testing options. Key Vite configuration areas that Vitest respects include:

  • resolve.alias: As seen above, this is crucial for mapping module paths.
  • plugins: Vite plugins that transform code (like Babel or SWC plugins, or custom ones) will often be applied to your test files as well, ensuring that your code is transformed consistently for both development and testing.
  • css.preprocessorOptions: If you use SASS or LESS, Vitest will pick up these settings.
  • build.target and build.polyfillModulePreload: These influence the JavaScript environment your tests run in.

The exact levers you control are essentially the same ones you control in your vite.config.js. If you need to add a new plugin to process specific file types during testing, you add it to your vite.config.js. If you need to change how certain modules are resolved, you adjust resolve.alias.

The magic behind this seamless integration is that Vitest is Vite. It uses Vite’s core machinery under the hood. When Vitest starts, it essentially spins up a Vite instance configured for testing. This instance handles module loading, transformation, and dependency pre-bundling, just like your development server, but with a specific focus on executing your test files.

One common point of confusion arises when you have very different configurations for development and testing. While Vitest can be configured to use a separate vitest.config.js, the default behavior is to use vite.config.js. If you find yourself needing vastly different plugin setups or resolution rules, it’s often a sign that your vite.config.js might be doing too much, or that you need to carefully manage conditional configurations within vite.config.js itself. For example, you could conditionally apply plugins based on an environment variable that Vitest sets.

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST;

export default defineConfig({
  plugins: [
    react(),
    // Example: A custom plugin that only runs during tests
    isTest && {
      name: 'my-test-plugin',
      transform(code, id) {
        if (id.endsWith('.js')) {
          return code.replace('console.log', 'console.log_test');
        }
      }
    }
  ].filter(Boolean), // Filter out falsy values from conditional plugins
  resolve: {
    alias: {
      '@utils': '/src/utils',
    },
  },
});

This allows you to keep a single configuration file while still tailoring aspects specifically for your testing environment.

If you encounter issues where your tests don’t behave like your development server, the first place to look is your vite.config.js and how Vitest is interpreting it. The next step is often understanding how to override or extend this inherited configuration specifically for Vitest, which is done via the test key within vite.config.js itself.

Want structured learning?

Take the full Vite course →