Simple React and Vite setup with unit testing
The official React docs recommend using a framework with React, but sometimes you don't need all the bells and whistles that come with that.
Vite is the recommended alternative and it even provides a command line tool to scaffold a basic React project.
So why do you need a guide for this? While that setup gets you off the ground, it doesn't have any support for unit testing.
In this walkthrough we'll improve on the built-in Vite and React boilerplate to add support for vitest, React Testing Library and msw
for mocking API requests.
If you just want to get coding, you can jump straight to the Github repository at https://github.com/reactpractice-dev/basic-vite-react-boilerplate.
Step 1 - Create the Vite boilerplate
To get started, we'll use the Vite command line tool to scaffold a React project:
npm create vite@latest
You can run npm install
and npm run dev
to check everything works as expected.
Step 2 - Add Vitest and React Testing Library
I've found the best walkthrough on how to set this up is the one written by Robin Wieruch.
First, we'll install vitest
, jsdom
and React Testing Library:
npm install -D vitest
npm install -D @testing-library/react jsdom @testing-library/jest-dom @testing-library/user-event
Next, we'll need to update Vite configuration with a new test
section, that describes the details on how to run the tests.
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
// support `describe`, `test` etc. globally,
// so you don't need to import them every time
globals: true,
// run tests in jsdom environment
environment: "jsdom",
// global test setup
setupFiles: "./tests/setup.js",
},
});
Notice we're referencing a setup.js
file as a global test setup - this is where we extend the expects
statement with useful helpers from RTL (like expect(...).toBeInTheDocument
) and make sure we cleanup after each test.
import { expect, afterEach } from "vitest";
import { cleanup } from "@testing-library/react";
import * as matchers from "@testing-library/jest-dom/matchers";
expect.extend(matchers);
afterEach(() => {
cleanup();
});
Then, we can add the test
command to the package.json
file, so we can run npm run test
to run the test suite.
// package.json
...
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "vitest"
},
...
This is enough to get the tests to work, however you'll notice in your IDE that Eslint complains about the global describe
and it
statements in the test. To fix this, we need to enable the vites-globals
Eslint plugin.
npm install -D eslint-plugin-vitest-globals
// .eslintrc.cjs
module.exports = {
root: true,
env: {
browser: true,
es2020: true,
"vitest-globals/env": true // <--- add new env
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
"plugin:vitest-globals/recommended", // <---- add new plugin
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
Step 3 - Add support for msw
msw is a library that allows mocking HTTP requests. This is very useful in tests, as it allows us to not call any backend directly, but instead use dummy responses, tailored to the specifics of the test.
To get a feeling of what we're trying to achieve, I created a simple RandomQuote
component that loads a quote from https://api.quotable.io/quotes/random
and displays it:
import { useEffect, useState } from "react";
const RandomQuote = () => {
const [quote, setQuote] = useState();
useEffect(() => {
fetch("https://api.quotable.io/quotes/random")
.then(function (response) {
return response.json();
})
.then(function (data) {
setQuote(data[0].content);
});
}, []);
return <div>{quote}</div>;
};
export default RandomQuote;
A simple test for this component would look like this:
it("renders a quote", async () => {
render(<RandomQuote />);
expect(
await screen.findByText(/If you accept the expectations of others/)
).toBeInTheDocument();
});
So how do we skip calling the backend in the test? By adding a beforeEach
statement that mocks the backend call and returns a dummy response instead:
beforeEach(() => {
server.use(
http.get("https://api.quotable.io/quotes/random", () => {
return HttpResponse.json([
{
_id: "2i4ILvPHXsgJ",
content:
"If you accept the expectations of others, especially negative ones, then you never will change the outcome.",
author: "Michael Jordan",
tags: ["Change", "Wisdom"],
authorSlug: "michael-jordan",
length: 107,
dateAdded: "2019-08-16",
dateModified: "2023-04-14",
},
]);
})
);
});
This way, the test always returns the same data so we can assert for the exact quote we provided.
This way of testing is very useful, as we can also check for error responses, for edge cases with the data and so on. So let's get msw
set up!
First step is to install it:
npm install -D msw msw/node
Then, we'll create a new file under the tests
folder where we'll define our mock server:
// tests/mock-api-server.js
import { setupServer } from "msw/node";
const server = setupServer();
export default server;
Finally, we need to configure the mock server to listen for requests before every test by updating the global setup file:
// tests/setup.js
import { expect, afterEach } from "vitest";
import { cleanup } from "@testing-library/react";
import * as matchers from "@testing-library/jest-dom/matchers";
import server from "./mock-api-server";
expect.extend(matchers);
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
cleanup();
});
afterAll(() => server.close());
And that's it - you can now use mocks in your tests!
Conclusion
This is the most basic React and Vite setup that I keep coming back to. What about you? Is there anything else you usually include?
Share your thoughts in the comments below!
And if you want to use this setup in your own projects, here's the completed code on Github: https://github.com/reactpractice-dev/basic-vite-react-boilerplate
Member discussion