Getting Started With Unit Testing In JS/TS And React

Wednesday, October 25, 2023 -  5 min read

Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently tested. The goal is to verify that each unit of code meets its design and behaves as expected. In simpler terms unit testing is isolating the smallest sets of code and testing them to verify that they behave as expected.

This post is available for JavaScript, TypeScript and React 

You can choose which one here by clicking on it or change it at code block.

And the code editor at the end is a live editor you can edit it and see the results of your tests in realtime.

What do you need to get started?

  • Test runner like Vitest or Jest (personally I prefer Vitest).
  • Project to test. I provided a live code editor at the end of the blog with starter files but you can start with any project.

Why Vitest, not Jest?

  • It's significantly faster than Jest.
  • Out-of-box ESM, TypeScript, and JSX support powered by esbuild.
  • In watch mode, it's smart and only reruns the related changes, just like HMR for tests!
  • If you know Jest you already know Vitest, it provides a Jest-compatible API that allows you to use it as a drop-in replacement in most projects. you only have to change a few things, and you're good to go. check the migration guide here

Installing Vitest in your project

yarn add -D vitest vite jsdom

shell

yarn add -D vitest vite jsdom vite-tsconfig-paths

shell

yarn add -D vitest vite jsdom vite-tsconfig-paths @vitejs/plugin-react @testing-library/react @testing-library/jest-dom

shell

Configuring Vitest

Make a file in the root of the project called vitest.config.js|ts with the following code

import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, }, })

JS

import { defineConfig } from "vitest/config"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ plugins: [tsconfigPaths()], });

TS

import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { environment: "jsdom", setupFiles: ["./tests/setupTests.ts"], globals: true, }, });

TS

@vitejs/plugin-react enables HMR to react in development. vite-tsconfig-paths makes vite use the paths defined in your tsconfig. environment: 'jsdom' by default the environment is node which means that any code meant for the browser can't run in the test, so we use jsdom which provides a browser-like environment to run the test in it.


Make file called tests/setupTests.ts with the following code

import "@testing-library/jest-dom/vitest"; import { afterEach } from "vitest"; import { cleanup } from "@testing-library/react"; afterEach(() => { cleanup(); });

TS

The @testing-library/jest-dom/vitest library provides a set of custom jest/vitest matchers that you can use to extend jest/vitest. These will make your tests more declarative, clear to read, and maintain. Check the docs here to see the added matchers

the after each cleanup configures Vitest and @testing-library/react to clean up after running every test so that each test can run on a clean slate

adding globals: true adds all vitest imports to every test file so we don't have to import in each file(jest like)

Add the following script to your package.json

"test": "vitest run",

json

Writing our first test

let's start with something simple create a file called with the following code

export const add = (a, b) => a + b;

JS

export const add = (a: number, b: number) => a + b;

TS

export const add = (a: number, b: number) => a + b;

TS

to test a unit is to check whether it matches something
eg: you expect 1 + 2 to equal 3. this is human language
in you write expect(1+2).toEqual(3)
there are a lot of matchers for different types like:

  • expect(function).toHaveBeenCalled()
  • expect(object).toHaveProperty(property)
  • expect(array).toContain(element)
  • expect(boolean).not.toBe(false)

to create a test file you must use the extension .spec.(ts/tsx/js/jsx) or .test.(ts/tsx/js/jsx) A spec comes from "specification" where you specify how code should behave, the extension doesn't change anything in how we write the tests it's just a preference.

Create a file in the same folder you created the add file called with the following code

import { add } from "./add"; test("add()", () => { expect(add(1, 2)).toBe(3); });

JS

import { add } from "./add"; test("add()", () => { expect(add(1, 2)).toBe(3); });

TS

import { add } from "./add"; test("add()", () => { expect(add(1, 2)).toBe(3); });

TS

congratulations! you just ran your first test successfully.