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
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.
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"
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.
Our test passes..in some cases, this will fail because of the following Typescript error:
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.
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
Top comments (12)
Thank for the article! Guys if you're getting an error:
"Cannot use JSX unless the '--jsx' flag is provided" then I hope this might help you(worked for me):
I am using Vite + React + TypeScript + Jest. I deleted tsconfig.app.json (comes out of the box) and tsconfig.node.json. So it's only tsconfig.json(code below). And it solved my problem with imports and jsx.
Great post, it's definitely not straightforward to setup unit testing for a React + Vite project. I recently wrote a similar article on this as well; it covers how to setup vitest, RTL, Husky, ESLint & Prettier.
Thanks Rajae. Will definitely checkout your article.
I have fixed my issue by adding and pinning the versions of jest-environment and jest-config to the same version as jest.
"identity-obj-proxy": "^3.0.0",
"jest": "^27.5.1",
"jest-environment-jsdom": "^27.5.1",
I get this error when i try to run tests :(
don't know how to fix this, i've already tried everything :/
try to remove offensive words should help!
This was really helpful for me. Thanks!
Thank you, it's working. I got ts error, but after adding "esModuleInterop": true in tsconfig.json it went away.
no fluffs and straight to the point, cheers mate!
Thanks! this was a huge time saver
I am happy you found it useful Isaac. :)
it is really good! Saved a lot of time. Thank you so much