Testing Vite React JS 18 app using Vitest and Testing Library

Here in the blog you will learn how to work with Vitest.

Before starting, you should know that there are many Vitest example in this link for different libraries.

First, initialize a fresh Vite project using this guide Vite js. Vite is a build tool designed to greatly enhance the frontend development experience.

Vite incorporates a native unit testing framework (Vitest) which is built on top of it, boasts a range of rich and modern features. Vitest is a kind of test runner which finds the test and run them.

What you only need after that is to install the Vitest as a dev dependency.

npm install -D vitest

Later in this article you will find we will install more dependencies, and testing library to test specifically React and React components. Right now, the Vitest as the unit testing framework is enough for having a quick progress. But don't forget that we need a testing library as well.

To organize your project and make the concerns separated, you should make a __test__ directory for putting your test files there. I recommend using underscore _ in the directory name to not have a conflict with the other directory name in the future.

In the src directory of Vite js project you can write your first test file like sum.js. Then in the __test__ directory write your first test file like sample.test.js

import { expect, test, it } from 'vitest'
import { sum } from '../sum'

it('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})

The syntaxes are the same as the syntax of Jest library Jest document.. And Vitest has the very similar API, look at the doc Vitest.

As you notices I have imported both test and it from vitest. and I can use them interchangeably.

like

it('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})

or

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})

Now is the time to run your test. To run this you have two options.

You can use the vitest binary in your npm scripts, or run it directly with npx vitest.

If you want to use the vitest binary in your npm scripts, what you should do is adding this to package.json file:

{
  "scripts": {
    ...
    "test": "vitest"
  }
}

and then run npm run test, yarn test, or pnpm test, depending on your package manager.

The second option is to run directly npx vitest.

In order to integrate VScode with Vitest check this page Official VScode Vitest extension

This is the link to the screenshots of the extension visit Vites extension repo

Debug

Look at the "tick" on the left-side column.

Also look at the triangle on the left-side column which you can run your test just by a click on.

describe

We can group some tests together (using describe() method) when they are can be described by one statement or explanation.

Suppose the Math.js is:

export const add = (a, b) => {
  return a + b
}

export const multiply = (a, b) => {
  return a * b
}

export const subtract = (a, b) => {
  return a - b
}

Then our test could be:

import { expect, test, it, describe } from 'vitest'
import { sum } from '../sum'
import { add, multiply, subtract } from '../Math.js'

it('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
  expect(Math.sqrt(36)).toBe(6)
})

describe('apply our custom functions in Match.js', () => {
  it('sums 1 & 2 using my own function', () => {
    expect(add(1, 2)).toBe(3)
  })

  it('multiplies 1 & 2 using my own function', () => {
    expect(multiply(1, 2)).toBe(2)
  })

  it('subtracts 1 & 2 using my own function', () => {
    expect(subtract(1, 2)).toBe(-1)
  })
})

Till now we have just used the toBe() as a matcher on expect() method. As there are some APIs in the Jest library jest, you can find the similar APIs and matchers on expect() method, in the document of Vitest.

Let's apply toContain() matcher as follow and run it.

describe('Strings tests', () => {
  it('contains a string', () => {
    expect('This is a string').toContain('is a string')
  })
})

Features

By having a look at this page in the Vitest document, you will see that it supports, jsdom, jest-compatible Snapshot and some other important libraries.

Vitest UI

Now is the time to see what is Vitest UI. Vitest also has a dev server under the hood when running the tests. This allows Vitest to provide a beautiful UI to view and interact with your tests.

You'll need to install it with:

npm i -D @vitest/ui

and run it like:

npx vitest --ui

or modify the package.json like:

  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview",
    "test": "vitest",
    "test:ui": "vitest --ui"
  },

and then run:

npm run test:ui

a new tab of browser will be available you can visit the Vitest UI at http://localhost:51204/**vitest**/

Then you can visit the Vitest UI at http://localhost:51204/**vitest**/

Testing a react component

Step 1:

First we need to install a library as the React testing utilities Testing Library.

You need to run this:

npm install -D @testing-library/react

Step 2:

Then we need a Dom environment for running our tests. JSDOM is for this purpose.

and run the following command to install jsdom:

npm install -D jsdom

Step 3:

Then run this:

npm install -D @testing-library/jest-dom

It is required later to extend the expect() method from Vitest to include the assertion methods in matchers from this package. See Step 4.

In the future, for configuring test in a Vite project, you may want to install all the dependencies together once, in one command like:

npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event

Step 4:

Since we are in a Vite project, we need to instruct the Vite configuration to sync with Vitest. This can be possible by just editing the vite.config.js, otherwise we need to make a vitest.config.ts and write some configuration there.

Before:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

After:

/// <reference types="vitest" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './setupTest.js',
  },
})

To know why we use triple slash /// directive, have a look at the Vitest document.

Make a setupTest.js file and put this code there.

import { expect } from 'vitest'
import matchers from '@testing-library/jest-dom/matchers'

expect.extend(matchers)

chrome extension:

Next you need to install this chrome extension, Testing Playground

Migrating from Jest to Vitest

Given Jest's massive adoption, Vitest provides a compatible API that allows you to use it as a drop-in replacement in most projects, read more here.

Migrating your application's tests from Jest to Vitest is a seamless process. The two testing frameworks share similar APIs, which means significant alterations to your existing code are generally unnecessary when transitioning to Vitest.

First, we need to uninstall Jest from our project and then install Vitest, accompanied by all required dependencies, by executing the following commands:

npm uninstall jest --save-dev
npm install vitest --save-dev

and add Vitest to the package.json:

"scripts": {
  "test": "vitest",
}

Testing Library

As you learn above for testing React components we need the Testing Library.

You need to learn about different concepts. Query and Role are the important ones. Let's first see what is Query.

Testing Library methods are called Queries which gives the ability to find elements on the page. There are several types of queries under these three main categories:

  • get
  • find
  • query

It is highly recommended to read the Testing Library official document to know how they works.

Queries introduction:

get is a query method to find element and will return the matching node for the query.

find it returns a promise and resolves when an element is for the given query.

query if no elements match for the query then it returns null which is a handy tools to assert an element is not present in the DOM.

Type of query

Depending on what we are looking for, single or multiple Elements we have two different types.

getBy... or getAllBy..., for more information have a look at the query types.

Role

There different kinds of queries but there some that reflect the experience of visual/mouse users as well as those that use assistive technologies.

One of these kind of queries is getByRole. Read about Role here. Most of the time, it will be used with the name option like so: getByRole('button', {name: /submit/i}). This can query every element that is exposed in the accessibility tree.

You can learn the list of roles here:

Role in summary:

let's see an example:

const element = screen.getByRole('banner')

The getByRole query in JavaScript testing, particularly when using libraries like React Testing Library, is a function that allows you to select elements based on their ARIA roles. ARIA (Accessible Rich Internet Applications) roles provide a way to indicate what the purpose of an element is (like a button, link, dialog, etc.) to assistive technologies like screen readers.

getByRole is looking for an element that has an ARIA role of banner. The banner role typically represents a section of a page that contains site-oriented content, like a logo or a site identifier. This is a part of making web content more accessible to users with disabilities.

The getByRole query is part of a family of queries provided by testing libraries like React Testing Library that encourage good accessibility practices. By selecting elements based on their role, rather than test-specific attributes or CSS selectors, you ensure that your tests also validate the accessibility aspects of your components.

If there's no element with the specified role, getByRole will throw an error, which makes it useful for asserting that an element is present in the document. This is different from queryByRole, which returns null instead of throwing an error when no elements are found, and findByRole, which returns a promise and is useful for asynchronous operations.

testing react hook

https://github.com/testing-library/react-hooks-testing-library

https://react-hooks-testing-library.com/

Note:

the test file name in a typescript project should be: name.test.ts

the chrome extension

The extension you already installed above is very useful to see what should you write in your test specifically when you are not sure about the Role of the html tag.

Be careful userEvent.click() is a promise function

Also In setuping test, it seems there is no need to this extension. in the new versions. to access all the api in this list: https://github.com/testing-library/jest-dom

import { expect } from 'vitest'
import matchers from '@testing-library/jest-dom/matchers'

// expect.extend(matchers);

Also look at this: which is referenced in inspire of jest-dom github link above

https://github.com/testing-library/dom-testing-library