Setting Up Vitest in a Vite React TypeScript App with Import Aliases

ReactWeb DevelopmentTypeScriptTestingViteVitest

October 12, 2025

vitest-banner

Introduction

Vitest is a fast, Vite-native unit testing framework that's ideal for modern JavaScript and TypeScript projects. When combined with a Vite React app, it allows seamless testing without the overhead of tools like Jest. This guide builds on a basic setup by explaining not just how to configure Vitest with import aliases, but why each step is necessary. Import aliases (e.g., @/components) improve code readability and maintainability by avoiding relative path hell, especially in larger projects. We'll cover creating the app, adding aliases, using them, and integrating Vitest for reliable testing.

1. Creating a Vite React App

Start by bootstrapping your project with Vite, which is a modern build tool focused on speed and simplicity.

Command:

pnpm create vite

Why do we need to do this?
Vite provides a lightweight, fast development server and build process optimized for React and TypeScript. Unlike Create React App, Vite uses native ES modules for instant hot module replacement (HMR) and faster cold starts. Using pnpm as the package manager ensures faster installations and disk space efficiency through a shared store for dependencies, which is crucial for reproducible builds in team environments.

Follow the prompts to select React and TypeScript templates. This sets up a basic structure with src/ as the root for your application code.

2. Adding Import Aliases

Import aliases allow you to use absolute paths like

@/components/Button
instead of relative ones like
../../components/Button
This is configured in TypeScript config files and Vite's config.

Update tsconfig.json

Add baseUrl and paths:

{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["./src/*"] },
    "resolveJsonModule": true,
    "esModuleInterop": true
  }
}

Why?
baseUrl: "." sets the root directory for absolute imports, making paths relative to the project root. paths maps @/ to ./src/, enabling TypeScript to resolve imports correctly during compilation and IDE autocompletion. This prevents path errors in larger apps, improves refactorability (e.g., moving files without updating imports), and enhances code cleanliness. resolveJsonModule and esModuleInterop handle JSON imports and CommonJS compatibility, which are common in modern setups.

Update vite.config.ts

Add alias resolution:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import path from 'path';

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});

Why?
Vite needs to know how to resolve these aliases at runtime and during bundling. Without this, the dev server or build process would fail on alias imports. path.resolve ensures the alias points accurately to ./src, aligning Vite's resolver with TypeScript's for consistent behavior across development and production.

Update tsconfig.app.json

Add baseUrl and paths:

{
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2022",
    "useDefineForClassFields": true,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "types": ["vite/client"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "erasableSyntaxOnly": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"]
}

Why?
This file is specific to the app's TypeScript compilation. Duplicating baseUrl and paths here ensures the app's code (under src/) uses aliases correctly, while keeping node-specific configs separate. It enables bundler-mode resolution, which Vite relies on, and strict linting rules to catch errors early, promoting robust code.

3. Using Alias Imports

Place components like Button.tsx under src/components/.

Example Import:

import Button from '@/components/Button';

Why?
Aliases eliminate brittle relative paths (e.g., ../../../), reducing errors when restructuring folders. This is essential for scalability— in a growing app, it saves time on maintenance and makes code more intuitive for collaborators. All files must be under src/ for the alias to resolve properly, as that's the mapped root.

Setting Up Vitest

Vitest integrates natively with Vite, sharing its config for faster tests.

1. Install Vitest and Necessary Packages

Command:

pnpm add -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom

Why?

  • vitest: The core testing framework, chosen for its speed (up to 10x faster than Jest) and Vite compatibility.
  • @testing-library/react: Provides utilities to test React components as users interact with them, encouraging accessible, user-focused tests.
  • @testing-library/jest-dom: Adds custom matchers like **toBeInTheDocument()**for DOM assertions.
  • @testing-library/user-event: Simulates realistic user interactions (e.g., clicks, typing).
  • jsdom: A DOM emulator for running browser-like tests in Node.js.
    These are dev dependencies (-D) since they're only needed for testing, keeping production bundles lean.

2. Create a Simple Test Script

Create src/demo.test.ts:

import { expect, test } from "vitest";

const sum = (a: number, b: number) => a + b;

test("adds 1 + 2 to equal 3", () => {
  expect(sum(1, 2)).toBe(3);
});

Add to package.json:

"scripts": {
  "test": "vitest"
}

Why?
This verifies the setup with a basic arithmetic test. Naming files with .test.ts or .test.tsx ensures Vitest auto-discovers them. The test script runs all such files, allowing continuous testing. Explaining failures (e.g., changing + to - shows expected vs. actual in green/red) helps debug why tests fail, teaching the importance of assertions for code reliability.

Run with:

pnpm run test

If it fails (e.g., due to a code mistake), Vitest highlights discrepancies, emphasizing why tests catch regressions early.

3. Testing Button Components

Recommended folder structure: Create tests under src/, mirroring app structure (e.g., tests/components/Button.test.tsx).

Example Test (Button.test.tsx):

import Button from '@/components/ui/button';
import { render, screen } from '@testing-library/react';

describe('Button component', () => {
  describe('renders button with children', () => {
    test('renders button with text', () => {
      render(<Button>Simple Button</Button>);
      const button = screen.getByRole('button');
      expect(button).toBeInTheDocument();
      expect(button).toHaveTextContent(/Simple Button/i);
    });
  })
});

Why?
Mirroring structure keeps tests organized and co-located with code, making maintenance easier. Using describe and test groups related assertions for readability. Testing via roles (e.g., getByRole('button')) ensures accessibility compliance. This verifies the component renders correctly, catching UI bugs before deployment.

Fixing Common Errors

If you see "Cannot find module '@/components/ui/button'", configure Vitest to recognize aliases.

Create/Update vitest.config.ts

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./vitest.setup.ts'],
    coverage: {
      provider: 'v8',
    },
  },
  resolve: {
    alias: {
      '@': '/src',
    },
  },
});

Why?
Vitest needs its own config to inherit Vite's resolver for aliases. environment: 'jsdom' simulates a browser. globals: true allows using test, expect without imports. setupFiles runs global setups. coverage with V8 tracks test coverage for quality metrics.

Create vitest.setup.ts

import '@testing-library/jest-dom';

Why?
This imports matchers globally, making them available in all tests without repetition, streamlining the testing workflow.

Update tsconfig.json

Add types:

"types": ["vitest/globals", "@testing-library/jest-dom"]

Why?
This provides TypeScript types for Vitest globals and Jest-DOM matchers, enabling IDE autocompletion and type safety in tests.

Create vitest.d.ts

/// <reference types="vitest/globals" />
import '@testing-library/jest-dom';

Why?
This declaration file ensures TypeScript recognizes Vitest and testing library types project-wide, resolving type errors during compilation.

After these fixes, re-run

pnpm run test
Your tests should pass, confirming a fully integrated setup.

Conclusion

By following this guide, you've set up a robust testing environment with Vitest in your Vite React TypeScript app. Aliases keep imports clean, and tests ensure code reliability. For visual references, refer to the original document's screenshots (e.g., image1.png for test outputs). Experiment with more complex tests to build confidence in your app!