Effortless Testing Setup for React with Vite, TypeScript, Jest, and React Testing Library
Tags: webdev, javascript, tutorial, react
Published: 2024-01-28 09:00:55

Recently, I have been learning to write some tests in React using **Jest** and **RTL(React Testing Library)**, which has been a fun and eye-opening adventure so far. Setting up testing in a Nextjs app which I used to practise testing was not too complicated, and I managed to get things working. I recently wanted to start writing some tests for a project I am working on which is a React+Vitejs, and Typescript application. This proved challenging to set up, resulting in me looking through a few outdated articles here and there, pulling a hair or 2, and almost crying about why the gods of testing were against me. Thankfully, I finally resolved my errors and had my tests running smoothly.

This post therefore serves as an easy-to-follow, updated guide on how to set up testing with Jest and React-testing-library in your Vite + Typescript application.

## Challenges faced

While setting up the test in my Vite project, the 2 major problems that are commonly faced are:

- Configuring Jest to handle SVG images
- Handling path aliases (absolute imports) with Jest

In this post, we solve the above problems and set up testing in our application. Without wasting much time, let's jump right in.

## Setting up a Vite project

Let's get started by creating a simple Vite project. Run the following command to create a React application using Vite: `npm create vite@latest` . Follow the instructions and select the necessary dependencies


![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6gdoup8ruo79plzhxndw.png)

Now, cd into your project directory and run npm install to install all dependencies.

## Installing Jest and React testing library

Now let's install Jest and RTL and other related dependencies:

```
npm install -D jest @testing-library/react ts-jest @types/jest ts-node @testing-library/jest-dom jest-environment-jsdom @testing-library/user-event
```
and wait for the packages to finish installing.

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo9e6apycla2bt2dm1gz.png)
Great, now if you open your package.json file, you should see all of these packages as dev dependencies.

Now create a _jest.setup.ts_ file with the following code :

```
import "@testing-library/jest-dom";
```
Also, create a _jest.config.js_ file with the following configuration code

```
export default {
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.tsx?$": "ts-jest",
  },
  setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
};
```
## Handling Styles and SVG's

Now we need 2 more packages for our test to run correctly, the first package is **identity-obj-proxy** which will help jest transform files with the extension .css, .less, .sass, or .scss. When a file with any of these extensions is imported into your code during testing, Jest will substitute the import with the identity-obj-proxy module. This is a common technique for mocking stylesheets in Jest tests.

The second package we need is **jest-transformer-svg**. When an SVG file is imported into your code during testing, Jest will substitute the import with the jest-transformer-svg module. This suggests that there might be a custom transformer (jest-transformer-svg) configured for transforming SVG files during the testing process.

To install this package, run:
`npm install -D identity-obj-proxy jest-transformer-svg`

After successfully installing, modify your _jest.config.js_ file to look like so:

```
export default {
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.tsx?$": "ts-jest",
  },

  moduleNameMapper: {
    "\\.(css|less|sass|scss)$": "identity-obj-proxy",
    "^.+\\.svg$": "jest-transformer-svg",
  },

  setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
};
```
Great!!, we have done a lot of configuration, let's write a test and see if things work correctly. Firstly, head over to your package.json and add the following script : `"test": "jest"`

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v50kah90gt48gx27yl7c.png)

Let's create a simple component that renders a list of users, with some tests for that component. Create a components folder in the src directory and create users folder with a _Users.tsx_ file and a _Users.spec.tsx_ file.

the _Users.tsx_ component looks like this

```
const Users = () => {
  return (
    <div>
      <h1>Users</h1>
      <ul>
        <li>name 1</li>
        <li>name 2</li>
      </ul>
    </div>
  );
};

export default Users;
```
Our test for this component in the _Users.spec.tsx_ file looks like this

```
import { render, screen } from "@testing-library/react";
import Users from "./Users";

describe("User", () => {
  test("renders heading", async () => {
    render(<Users />);
    expect(screen.getByRole("heading", { name: "Users" })).toBeInTheDocument();
  });

  test("renders a list of users", async () => {
    render(<Users />);
    const users = await screen.findAllByRole("listitem");
    expect(users).toHaveLength(2);
  });
});
```
We have a simple component with some test setup. let's try to run our test using the `npm run test` command.

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6tis7rxfd41hjd766igk.png)

Our test passes..in some cases, this will fail because of the following Typescript error:

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39ervmfpxp8dazjfejgq.png)
We fix this by including our jest.setup.ts file in our _tsconfig.json_ file like so:

```
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src", "./jest.setup.ts"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

```
Specifically, we add _./jest.setup.ts_ as an array value of the include property. Our Typescript error now disappears and our test runs smoothly.


## Handling absolute imports

When setting up our applications, some of our applications might be making use of absolutes imports for clean and readable import statements. However, using absolute imports with Jest will need tiny adjustments, Firstly let's set up absolute import or path aliases in our Vite app and then make it work with Jest. To do that, we need to install the following package

`npm install -D vite-tsconfig-paths`

After successful installation, we need to modify the _vite.config.ts_ file and the _tsconfig.json_ file. add the following code as part of the _**compilerOptions**_ property

```
//absolute import
    "baseUrl": "./src",
    "paths": {
      "@/*": ["./*"]
    }
```
Our _tsconfig.json_ file looks like this

```
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    //absolute import
    "baseUrl": "./src",
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["src", "./jest.setup.ts"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```
We also modify our _vite.config.ts_ file

```
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), tsconfigPaths()],
});
```
In your _jest.config.ts_ file, add `"^@/(.*)$": "/src/$1"` to the _**moduleNameMapper**_ property.

```
export default {
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.tsx?$": "ts-jest",
  },

  moduleNameMapper: {
    "\\.(css|less|sass|scss)$": "identity-obj-proxy",
    "^.+\\.svg$": "jest-transformer-svg",
    "^@/(.*)$": "<rootDir>/src/$1",
  },

  setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
};
```
Amazing!! now you can import components like so:

```
import Counter from "@/components/counter-two/Counter-two";
```
using the @ path alias and use them in your test.

## Getting test coverage report

Most often when writing tests, we need to know the test coverage. Code coverage reports provide insights into how much of your code is covered by tests. We need a command to Instruct Jest to generate code coverage reports after running the tests. Add the following command to your script in the _package.json_ file

```
"coverage": "npm test --coverage --watchAll --collectCoverageFrom='src/components/**/*.{ts,tsx}'",
```
We then run `npm run coverage` . This runs Jest in watch mode and provides the coverage report after every test run.

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4gmgejd5tydyjv4lrqni.png)

## Conclusion
Setting up tests in React+Vite and Typescript applications is not that straightforward, as we often face a lot of challenges while setting it up. This post serves as an updated, step-by-step, easy-to-follow guide on setting up tests in your React+Vite and Typescript applications.

Code: [Github link](https://github.com/teyim/Vite-test-setup)