Unit Testing React Applications in a NX (NRWL) Monorepo with Vitest
Shannon Lal
Posted on September 13, 2023
Testing is the foundation that enables sustainable growth in any software project. For React applications in a NX monorepo, comprehensive unit testing across all projects is essential for stability as the codebase expands. However, slow and flaky tests can grind development velocity to a crawl by bogging down commits in the CI.
Vitest is a blazing fast unit test runner with first class React support that brings testing joy back to your monorepo. It smartly executes tests in parallel for unrivaled speed while remaining a tiny dependency.
In this post, we’ll explore setting up Vitest in a NX workspace with support for Canvas and writing fast unit tests for React components. With Vitest and NX, you can implement comprehensive unit testing to gain confidence as your apps grow.
We’ll assume you have working knowledge of TypeScript, React, testing, and NX workspaces. Ready to unlock the power of speedy unit testing for your monorepo? Let’s dive in!
Project setup
The following is an example of the monorepo structure we use at Designstripe and what we will be using during this blog.
Workspace Name: react-nrwl
Application: react-app-ui — the main React application
Shared Library: common-ui — contains shared React components bundled with Vite
Application Structure
apps/
react-app-ui/
src/
pages/
components/
next.config.js
project.json
tsconfig.ts
tsconfig.spec.ts
test-setup.ts
vite.config.ts
libs/
common-ui/
src/
lib/
components/
IconAlert.tsx
index.ts
project.json
tsconfig.json
tsconfig.spec.json
vite.config.ts
nx.json
tailwind.config.js
tsconfig.base.json
package.json
Use NX Vite plugin to configure your application
First, if you haven’t already, install the NX Vite plugin. This plugin will allow you to generate Vite applications and libraries in your NX workspace.
npm install -D @nrwl/vite
Next, use the NX generator to add Vitest to your workspace. This will add the Vitest configuration, update the test
target in your project.json
and update the tsconfig.json
to include the Vitest types.
npx nx g @nrwl/vite:vitest --project=react-app-ui --testEnvironment=jsdom --uiFramework=react
File changes:
UPDATE apps/react-app-ui/project.json
CREATE apps/react-app-ui/vite.config.ts
CREATE apps/react-app-ui/tsconfig.spec.json
UPDATE apps/react-app-ui/tsconfig.json
Install testing dependencies for React, Vite and Canvas
npm install -D @testing-library/react @testing-library/jest-dom @vitejs/plugin-react vite-tsconfig-paths vitest-canvas-mock
That's a lot of dependencies, so let's break them down:
@testing-library/react: A library for testing React components. It provides utilities to simulate user interactions and query elements in a way that's more aligned with how users interact with your app.
@testing-library/jest-dom: A set of custom matchers that makes it easier to assert how your components should behave in the DOM.
@vitejs/plugin-react: The official Vite plugin to support React. This plugin enables features like Fast Refresh in a Vite + React development setup.
vite-tsconfig-paths: A Vite plugin that enables support for TypeScript's paths and baseUrl configuration options. This is useful when you want to set up custom path aliases in a TypeScript project using Vite.
vitest-canvas-mock: Mocks the canvas API when running unit tests with vitest.
Add a test setup file
We can optionally add a test setup file to our project. This file will be executed before each test file and can be used to configure the testing environment. In ours, we use it to mock the canvas API and some other global functions that are used in our tests. We also clean up the DOM after each test using the cleanup function from @testing-library/react.
import { afterEach } from "vitest";
import { cleanup } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
// Option for using mocking canvas testing
import "vitest-canvas-mock";
// runs a cleanup after each test case (e.g. clearing jsdom)
afterEach(() => {
cleanup();
});
// Add Default Functions
/* eslint-disable @typescript-eslint/no-empty-function */
const noop = () => {};
Object.defineProperty(window, "scrollTo", { value: noop, writable: true });
Update Vite Config to support test setup and reference libraries in MonoRepo
One of the challenges with setting up testing using Vitest is that Vite doesn't know how to resolve the paths to the libraries in the monorepo by default. To resolve this, we need to add a Vite plugin called vite-tsconfig-paths. This plugin will read the tsconfig.json file and resolve the paths to the libraries in the monorepo. We also make sure that the Vite React plugin is configured, and we add the test setup file to the Vite config.
/// <reference types="vitest" />
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import viteTsConfigPaths from "vite-tsconfig-paths";
import dts from "vite-plugin-dts";
import { join } from "path";
export default defineConfig({
plugins: [
// This is required to build the test files with SWC
react({ include: /\.(js|jsx|ts|tsx)$/, fastRefresh: false }),
viteTsConfigPaths({
root: "../../",
}),
],
test: {
globals: true,
cache: {
dir: "../../node_modules/.vitest",
},
environment: "jsdom",
deps: {
// Required for vitest-canvas-mock
inline: ["vitest-canvas-mock"],
},
include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
setupFiles: "./test-setup.ts",
// Required for vitest-canvas-mock
threads: false,
environmentOptions: {
jsdom: {
resources: "usable",
},
},
},
});
Create Unit Tests
Step 1. Create simple Typescript function and test
Here we create a simple function that we will test to validate that Vitest is working.
// processRequest.ts
export const processRequest = (request) => {
console.log("request", request);
};
// processRequest.spec.ts
// Note: Simple test to validate that vitest is working
import { processRequest } from "./processRequest";
describe("processRequest", () => {
it("Calls Function with process request", () => {
expect(processRequest).toBeDefined();
});
});
To validate that Vitest is working, we can run the test using the following command:
npx nx run react-app-ui:test
Step 2. Create simple React Component and test
Note: It references a component that is in a library in the mono repo
import { processRequest } from "./processRequest";
// Note this behind the scene references the canvas
import { IconAlert } from "@react-nrwl/common-ui";
interface ButtonProps {
label: string;
}
export const Button = ({ label }: ButtonProps) => {
const onClick = () => {
scrollTo({ top: 0, behavior: "smooth" });
processRequest(label);
};
return (
<div>
<IconAlert />
<button
data-testid="focusedButton"
onClick={onClick}
type="button"
className="bg-purple/100 flex px-4 py-3 rounded-[12px] text-12 leading-6 font-medium text-left text-purple/700 sm-down:w-[100%] duration-500 hover:bg-neutral-200 transition ease-in-out-expo"
>
{label}
</button>
</div>
);
};
Write a simple Spec for the Component:
import { render, screen, fireEvent } from "@testing-library/react";
import { vi, Mock } from "vitest";
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-ignore
global.console = { log: vi.fn() };
import { Button } from "./Button";
describe("Button", () => {
it("renders headline", () => {
const consoleSpy = vi.spyOn(global.console, "log");
const comp = render(<Button label={"Test Label"} />);
const button = screen.getByTestId("focusedButton");
expect(button).toBeDefined();
fireEvent.click(button);
expect(consoleSpy).toHaveBeenCalledTimes(1);
screen.debug();
expect(comp).toBeDefined();
// check if App components renders headline
});
});
Parting Thoughts
In this post, we explored configuring Vitest for unit testing React apps in NX monorepos. With the setup covered, you're equipped to implement comprehensive unit testing.
Testing is crucial as complexity grows. But slow, flaky tests stall velocity. Vitest delivers the speed and reliability needed to test React frequently.
Pair Vitest with NX workspaces for scale. Write unit tests early and often to prevent bugs. Testing with confidence and joy enables sustainable development.
We hope you found this guide helpful. Please let us know if you have any other questions—we're happy to help. And feel free to share your own React testing tips in the comments below!
Authors
Jeff Schofield
https://www.linkedin.com/in/jeff-schofield-76555b163/
https://twitter.com/JeffScript
Shannon Lal
https://www.linkedin.com/in/shannonlal/
https://twitter.com/shannondlal
Posted on September 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.