DEV Community

Sheikh Limon
Sheikh Limon

Posted on

Setting Up Vitest + React Testing Library

Let's configure a modern, professional testing environment for React — the same stack used by industry teams.

🎯 What We're Building

This setup combines:

  • Vite → Lightning-fast dev server and build tool
  • Vitest → Modern, Jest-compatible test runner with native ESM support
  • React Testing Library → User-centric UI testing that mimics real interactions
  • @testing-library/jest-dom → Readable, semantic assertions
  • jsdom → Lightweight browser environment for Node.js

🪜 Step 1: Install Dependencies

Run this in your project root:

npm install -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom
Enter fullscreen mode Exit fullscreen mode

What -D means: These are devDependencies — they're only needed during development, not in production.

🪜 Step 2: Configure Vitest

Create or update vite.config.ts in your project root:

/// <reference types="vitest" />
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true, // Enables 'test', 'expect' globally (no imports needed)
    environment: "jsdom", // Simulates browser DOM for React components
    setupFiles: "./src/setupTests.ts", // Runs before each test file
  },
});
Enter fullscreen mode Exit fullscreen mode

Why this matters:

  • globals: true → Write tests without repetitive imports
  • environment: "jsdom" → Your components render as if in a real browser
  • setupFiles → Configure test helpers once, use everywhere

🪜 Step 3: Create Test Setup File

Create src/setupTests.ts:

import "@testing-library/jest-dom";
Enter fullscreen mode Exit fullscreen mode

What this unlocks:

Now you can use human-readable assertions like:

expect(element).toBeInTheDocument();
expect(button).toBeDisabled();
expect(link).toHaveAttribute("href", "/home");
expect(heading).toHaveTextContent("Welcome");
Enter fullscreen mode Exit fullscreen mode

Instead of verbose, fragile alternatives like:

// ❌ Without jest-dom
expect(element).toBeTruthy();
expect(button.disabled).toBe(true);
Enter fullscreen mode Exit fullscreen mode

🪜 Step 4: Configure TypeScript

Update tsconfig.json to recognize Vitest and the DOM environment:

{
  "compilerOptions": {
    "types": ["vitest/globals", "vite/client", "@testing-library/jest-dom"],
    "jsx": "react-jsx",
    "module": "ESNext",
    "target": "ES2022",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}
Enter fullscreen mode Exit fullscreen mode

Key additions:

  • "vitest/globals" → TypeScript recognizes test, expect, describe
  • "@testing-library/jest-dom" → Autocomplete for custom matchers

🪜 Step 5: Configure ESLint for Testing

Install the ESLint plugins for React Testing Library and jest-dom:

npm install -D eslint-plugin-testing-library eslint-plugin-jest-dom
Enter fullscreen mode Exit fullscreen mode

Update your eslint.config.js (ESLint flat config):

import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
import { defineConfig, globalIgnores } from "eslint/config";
import eslintConfigPrettier from "eslint-config-prettier";
import testingLibrary from "eslint-plugin-testing-library";
import jestDom from "eslint-plugin-jest-dom";

export default defineConfig([
  globalIgnores(["dist"]),
  {
    files: ["**/*.{ts,tsx}"],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
      reactHooks.configs["recommended-latest"],
      reactRefresh.configs.vite,
      eslintConfigPrettier,
    ],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
  },
  // Testing Library + Jest DOM rules for test files only
  {
    files: [
      "**/__tests__/**/*.{js,jsx,ts,tsx}",
      "**/*.{test,spec}.{js,jsx,ts,tsx}",
    ],
    extends: [
      testingLibrary.configs["flat/react"],
      jestDom.configs["flat/recommended"],
    ],
  },
]);
Enter fullscreen mode Exit fullscreen mode

What this provides:

Best practice enforcement → Warns against anti-patterns like getByTestId

Async handling → Catches missing await on async queries

Accessible queries → Suggests using getByRole over less semantic queries

jest-dom matchers → Autocomplete and validation for custom assertions

Example linting feedback:

// ❌ ESLint will warn
const button = container.querySelector('button');

// ✅ ESLint suggests
const button = screen.getByRole('button', { name: /submit/i });
Enter fullscreen mode Exit fullscreen mode

🪜 Step 6: Add Test Script

In package.json:

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "test": "vitest",
  "test:ui": "vitest --ui" // optional UI runner
}
Enter fullscreen mode Exit fullscreen mode

Run tests with:

npm run test
Enter fullscreen mode Exit fullscreen mode

or start an interactive UI:

npm run test:ui
Enter fullscreen mode Exit fullscreen mode

🧠 Why This Setup Works

✅ Vitest understands modern ES Modules (no CommonJS issues)

✅ React Testing Library gives realistic user-focused queries

✅ JSDOM simulates the browser for rendering

✅ jest-dom improves readability of test results

✅ setupTests.ts ensures global test behavior is consistent

✅ Example: A Complete Test

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import App from "./App";

test("user can add a task", async () => {
  render(<App />);

  const input = screen.getByRole("textbox", { name: /add task/i });
  const button = screen.getByRole("button", { name: /add/i });

  await userEvent.type(input, "Learn TDD");
  await userEvent.click(button);

  expect(screen.getByText("Learn TDD")).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

When you run it, Vitest uses:

  • JSDOM to render your app
  • Testing Library to simulate user behavior
  • jest-dom to verify output

💡 Pro Tip

You can watch tests live as you code:

npx vitest --watch
Enter fullscreen mode Exit fullscreen mode

Vitest will re-run only the tests affected by the files you changed — perfect for iterative TDD development.

🏁 Final Thoughts

With this setup, you now have a professional-grade testing environment that mirrors what modern React teams use in production:

  • ⚡ Vite → fast bundling
  • 🧪 Vitest → Jest-compatible testing
  • 🧍‍♂️ React Testing Library → test like a real user
  • 🧱 TypeScript → safe and typed components
  • 🧭 TDD workflow → focused, confident coding

✅ "Write tests for behavior, not for implementation."

🎓 Remember

"The more your tests resemble the way your software is used, the more confidence they can give you."

— Kent C. Dodds, creator of React Testing Library

With this setup, you're not just testing code — you're verifying that real users can accomplish their goals.

Next steps: Start practicing TDD by writing a failing test first, then implementing just enough code to make it pass. 🎯

Top comments (0)