DEV Community

Shannon Lal
Shannon Lal

Posted on

Unit Testing React Applications in a NX (NRWL) Monorepo with Vitest

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 }); 
Enter fullscreen mode Exit fullscreen mode

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", }, }, }, }); 
Enter fullscreen mode Exit fullscreen mode

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); }; 
Enter fullscreen mode Exit fullscreen mode
// 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(); }); }); 
Enter fullscreen mode Exit fullscreen mode

To validate that Vitest is working, we can run the test using the following command:

npx nx run react-app-ui:test 
Enter fullscreen mode Exit fullscreen mode

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> ); }; 
Enter fullscreen mode Exit fullscreen mode

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 }); }); 
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)