React.js Vite Unit Testing (Vitest, Husky, lint-staged, ESLint, Prettier)

Share with a friend:

Creating a React application with Vite has now become the preferred way rather than using create-react-app. Vite provides a much faster and simpler development environment, however unlike create-react-app, it does not come with the packages needed for unit testing React applications out of the box.

In this article, we will explore how to set up unit testing for a production-ready React + Typescript Vite application. We’ll set up testing with the lightweight and fast Vitest testing framework and the React Testing Library. We’ll also configure linting and formatting with ESLint and Prettier to catch issues early. And we’ll use Husky and lint-staged to automatically run tests and linting on git commits before code is pushed. By the end, you’ll have a solid testing setup that catches bugs, prevents regressions, and helps enforce code quality standards while developing and merging code changes.

Vitest vs Jest

Jest is a widely used testing framework for JavaScript. It offers features like built-in support for mocking and snapshot testing making it a comprehensive choice for JavaScript testing. On the other hand, Vitest is a JavaScript unit testing framework designed to complement Vite. Vitest focuses on speed and simplicity, with tests running significantly faster than Jest in some cases. It also offers compatibility with most of the Jest API, making it a viable alternative to Jest.

For small to medium sized projects, Vitest’s simplicity often makes it preferable to Jest which can be overkill. But for larger codebases with more complex test needs, features like snapshot testing or Jest’s rich ecosystem may make it a better choice.

Why use Typescript?

TypeScript offers several advantages over JavaScript, primarily due to its strong typing system. By providing static typing, TypeScript allows developers to catch errors during development rather than at runtime, enhancing code quality and reducing bugs. Learn more.

Setting Up Vitest

Assuming you already have your React Vite project setup (npm create vite@latest).

The first step is to install Vitest to run our unit tests. We can install it with:

npm install -D vitest

Then update your package.json file by adding a script to run vitest:

  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview",
    "test": "vitest",
}

Now we can write our first test file. Create src/components/Example/Example.test.ts:

import { describe, expect, it } from 'vitest';

describe('Example', () => {
  it('adds 1 + 2 to equal 3', () => {
    expect(1 + 2).toBe(3); 
  });
});

And run the tests:

npm run test

Vitest will detect and run the tests automatically. It’s fast and focused on simplicity, so it’s a great starting point for testing React applications.

If your project has a need for Coverage Reporting, see here for additional setup instructions.

Setting up Vitest with React Testing Library

In order to test the UI of your React components, you need a way to interact with the DOM. This is where React Testing Library comes into play.

Install React Testing Library and supporting packages:

npm i -D jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event

Update your Vite configuration file (vite.config.ts):

/// <reference types="vitest" />
/// <reference types="vite/client" />

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
    // you might want to disable it, if you don't have tests that rely on CSS
    // since parsing CSS is slow
    css: true,
  },
})

As you may have noticed, we specified a setup file (./src/test/setup.ts) in the config. Let’s now create that file at the location we specified.

Create file src/test/setup.ts and add:

import '@testing-library/jest-dom'

Now you should be able to successfully test the UI of your React application. For example:

import { test } from 'vitest';
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders welcome message', () => {
  render(<App />);
  expect(screen.getByText('Learn React')).toBeInTheDocument();
});

Creating a Custom Render for App Providers

There is a good chance that your React application utilizes the Context API and so your app relies on providers. For each of your unit tests, you would have to wrap the component you are trying to render with those providers. Since this is not very efficient, we can create a custom render function with the necessary providers.

Create file utils/test-utils.tsx and add the following:

import { cleanup, render } from '@testing-library/react'
import { afterEach } from 'vitest'

afterEach(() => {
  cleanup()
})

const AppProviders = ({children}: {children: React.ReactNode}) => {
  return (
    <ThemeProvider theme="light">
        {children}
    </ThemeProvider>
  )
}

function customRender(ui: React.ReactElement, options = {}) {
  return render(ui, {
    wrapper: AppProviders,
    ...options,
  })
}

export * from '@testing-library/react'
export { default as userEvent } from '@testing-library/user-event'
// override render export
export { customRender as render }

The code snippet above has an example <ThemeProvider> to demonstrate how you might add your own. Modify that part of the code as needed.

Now, that you have your custom render function in test-utils.tsx, ensure that you are importing that one when necessary and not the default render function from React Testing Library.

If you are interested in seeing examples of how you can set up Vitest with other tools/frameworks such as MSW, GraphQL, or Next.js, visit here.

Configuring ESLint and Prettier

If you have been following the tutorial up to this point you have already successfully set up unit testing with Vitest and React Testing Library for your React app. However, if you a collaborating with others for your project, you may want to utilize static analysis testing tools to ensure proper coding standards and syntax are maintained in your project at all times.

To maintain code quality and consistency, we’ll use ESLint for linting and Prettier for formatting.

ESLint would already be installed if you created a React + Typescript Vite project. You want to also install eslint-plugin-react to add React-specific linting rules:

npm install eslint-plugin-react --save-dev

Then add plugin:react/recommended and plugin:react/jsx-runtime to the extends list of your ESLint config file:

module.exports = {
  ...
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
    'plugin:react/recommended',
    'plugin:react/jsx-runtime',
  ],
  ...
}

Now, let’s install Prettier:

npm install --save-dev --save-exact prettier

Since we are using ESLint with Prettier, we need to also install eslint-config-prettier so that they don’t conflict with each other:

npm install -D eslint-config-prettier

Now add eslint-config-prettier to the extends list of your ESLint config file:

module.exports = {
  ...
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
    'plugin:react/recommended',
    'plugin:react/jsx-runtime',
    'eslint-config-prettier'
  ],
  ...
}

Create a file called .prettierrc at the root of your project. As you might have guessed this is Prettier’s configuration file. You may specify the following rules or specify your own (visit here to see options):

{
  semi: true,
  singleQuote: true,
}

As stated earlier Prettier is used to format your files based on the rules you specify. You can configure your favorite code editor to format your files on save. This setup is beyond the scope of this article, see here for more information. Regardless, in the following section, we will set up Husky which we can utilize to create a pre-commit hook so that our formatting and linting is done before each commit.

Setting Up Husky and lint-staged

To make ESLint, Prettier and testing run automatically before commits, we can use Husky and lint-staged.

First install them:

npm install --save-dev husky lint-staged
npx husky install
npm pkg set scripts.prepare="husky install"
npx husky add .husky/pre-commit "npx lint-staged"

Then create a lint-staged block in package.json and configure lint-staged with scripts to run on staged files:

{
  ...
  "scripts": {
    ...
    // add script to run unit tests without watch mode
    "test:staged": "vitest --run",
  },
  {
    "lint-staged": {
      "**/*.{js,jsx,ts,tsx}": [
        "npm run lint",
        "prettier --write --ignore-unknown",
        "npm run test:staged"
      ]
    }
  }
}

Now ESLint, Prettier, and tests will automatically run before any commits. This helps catch issues early before they are merged into the main or prod branch.

Conclusion

Setting up Vitest, ESLint, Prettier, Husky and lint-staged provides a solid foundation for unit testing React apps. You get automatic testing on commits, linting enforced on the way in, and consistent code formatting.

The tools are lightweight and easy to configure. They work well together to catch bugs and prevent regressions while developing React code. Give this setup a try on your next project – it will save you time debugging and maintaining high-quality code.

Share with a friend:

Rajae Robinson

Rajae Robinson is a young Software Developer with over 3 years of work experience building websites and mobile apps. He has extensive experience with React.js and Next.js.

Recent Posts