In this article, I’ll be taking a different approach to show you how to run tests on your React app with Vitest and React testing library. This article answers the frequently asked question about using Vitest and React testing library. Before starting, you must ensure that you have a basic knowledge in writing code in React and TypeScript. At the end of this tutorial, I’m sure you’ll have a better understanding of how Vitest and React testing library works.
Table of Content
- What is Vitest?
- How do I set up Vitest in React?
- Can Vitest be used in frontend and backend testing?
- How do I create and run tests in Vitest?
- What are the most used functions/methods in Vitest?
- How do I check my test results in the browser?
- What is the difference between testing React components and plain JavaScript/TypeScript code?
- What are the most common functions/objects in the React testing library?
- What can I test when using Vitest and React-testing-library?
- What are the examples of tests i can run in React apps?
- How do I run an end-to-end test with Vitest?
- How do I test components with state?
- Is it necessary to test React state and hooks?
-
Can I run the
npm test
andnpm run dev
commands together? - How do I skip tests in a file?
- What are the common configurations in the vitest config file?
- Conclusion
- References
What is Vitest?
Vitest is a testing framework built on top of Vite. It is used to test code written in JavaScript and TypeScript. It supports integration with various tools across the JavaScript ecosystem.
How do I set up Vitest in React?
If you installed React with Vite, all you need is to install Vitest, Jsdom, React testing library and Jest DOM as development dependencies.
npm install --save-dev vitest jsdom @testing-library/react @testing-library/jest-dom
vitest
will be used to run the tests.
jsdom
will be used to simulate a browser environment for running the tests.
@testing-library/react
will be used to render the React components, query elements, and interact with them.
@testing-library/jest-dom
has a list of matchers that simplifies the conditions to be tested on the React components.
Once that is installed, you need to configure the environment to use jsdom
. In your vite.config.ts
file, create a test object with an environment of 'jsdom'.
// vite.config.ts file
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom'
}
});
Although you can have a separate configuration file for Vitest, it’s always advised to use the same file since Vitest was built on top of Vite.
Can Vitest be used in frontend and backend testing?
Yes, Vitest can be used to test frontend and backend code.
Vitest can be used to test components of frontend frameworks like React, Vue, Angular, Svelte, etc. It can be used to test how components behave in response to changes in props, state, and user interactions.
Before running the tests, you need to configure the environment to one that simulates a browser environment in the vite.config.ts
(or vitest.config.ts
) file. The value can either be jsdom
or happy-dom
library. Once this is specified, you'll be able to interact with the DOM and use any browser web API in the test.
For backend, it can be used with other Node.js libraries to test backend logic, API endpoints, database connections, and external services. The environment must be configured to node
to access Node.js-specific features.
How do I create and run tests in Vitest?
Before creating a test, you must have a functionality to test. It can be a function that performs an operation or a component renders some element. After that, you must create another file that'll contain the test for the function. The test file must always end with .test.ts
(or .test.tsx). If the function is in a func.ts
file, the test will be in a func.test.ts
file.
For example, Let’s say you have a add
function in a file (add.ts
)
export default function add(a:number, b:number): number{
return a + b;
}
You can write a test (in an add.test.ts
file) that asserts that the addition of any 2 numbers will return the sum of the numbers:
//add.test.ts
import { test, expect } from 'vitest';
import add from './add'; //import the function
test('addition of two numbers gives the sum', () => {
expect(add(2,4)).toBe(6);
})
From the code above the test expects the addition of 2 and 4 to be 6. If the result of add(2,4)
is the same as the expected value(6), the test will pass. If not, the test will fail.
To run the tests, you must have a script in the package.json
that runs Vitest. ‘vitest’ runs the tests once while ‘vitest – watch’ runs Vitest in watch mode.
"scripts": {
"test": "vitest",
"test:watch": "vitest --watch"
}
When you run npm test
command, it’ll run all the files ending with (.test.ts) once.
When you run the npm test:watch
command, it’ll run Vitest in watch mode and any code changes will automatically trigger a rerun of the test.
After running npm run test
in the CLI, this is the result I got for the test:
From the image above, the test passed!
If you change the test to expect that the addition of 2 and 4 gives 5, the test will fail. This is because the addition of 2 and 4 isn't 5.
//add.test.ts
import { test, expect } from 'vitest';
import add from './add';
test('addition of two numbers gives the sum', () => {
expect(add(2,4)).toBe(5);
})
What are the most used functions/methods in Vitest?
They include describe()
, it()
, test(), expect(), toBe(), not(), and so on.
describe()
describe()
is used to group all related test cases for a function/component together.
For example, you can have 2 test cases for the add
function. One tests that the addition of 2 numbers is equal to the sum of the numbers and the second tests that the addition of 2 numbers isn't the same as the wrong result.
import {describe, it, test, expect } from 'vitest';
import add from './add';
describe('add function', () => {
it('adding 2 numbers equals the sum', () => {
expect(add(2,4)).toBe(6); //the test will pass because the addition of 2 and 4 is 6
})
it('adding 2 numbers is not equal to the wrong sum', () => {
expect(add(2,4)).not.toBe(5); //the test will pass because the addition of 2 and 4 is not 6
})
})
Result:
it() and test()
it
and test
functions represent a single test case.
expect()
expect
is used to test assertions (whether a result matches your expected result).
toBe()
toBe
is one of the expectations or matchers used with the expect
function.
It’s used to check whether the computed value is strictly equal (===) to the predicted value. If it turns true, the test passes. If it’s false the test fails.
For example, in the add.test.ts
file, we expected the addition of 2 and 4 to be 6.
not()
not
is used to confirm that a condition is not true. For example, in the add.test.ts function, we expected that the addition of 2 and 4 should not be equal to 5.
test('addition of two numbers is not equal to the wrong sum', () => {
expect(add(2,4)).not.toBe(5);
})
How do I check my test results in the browser?
Vitest has a UI mode for checking the test results in a browser-based user interface.
To set this up, you need to install Vitest UI.
npm install –save-dev @vitest/ui
After that, you should add another script (vitest --ui
) in your package.json file that runs the test in UI mode.
"script": {
"test": "vitest",
"test:watch": "vitest --watch",
"test:ui": "vitest --ui"
}
And finally, you enter npm run test:ui
in your CLI to run the script.
After a few seconds, a page will be opened in your browser, and you’ll be able to see more information about your tests.
With Vitest UI, you can easily check your test results in watch mode, filter tests, trace, and fix errors.
What is the difference between testing React components and plain JavaScript/TypeScript code?
If you’re testing a JavaScript or Typescript function, the code and test must have the same extension, ending with either .js
or .ts
depending on your configuration (add.js and add.test.js, add.ts and add.test.ts).
Also, code that contains React components must have the same extensions with their test (eg Hello.tsx and Hello.test.tsx).
If the component and test file have different extensions, you won't be able to run the test. (eg Hello.tsx and Hello.test.ts will return an error).
What are the most common functions/objects in the React testing library?
The most used functions/objects are the ones that render, query, and interact with the DOM. They include render()
, screen
, getBy
, and queryBy
methods, toBeInTheDocument()
, fireEvent
., and so on.
render function
The render
function renders the component that’ll be tested in a virtual DOM. Before testing any React component, you first need to render it in a virtual DOM. After that, you can query any element you like.
For example, if you have a component that renders a heading with some text. To test that it renders correctly, you first need to render it with the render
function.
//Hello.tsx
export default function Hello() {
return <h1>Hello world</h1>;
};
//Hello.test.tsx
import {describe, it} from 'vitest';
import {render} from '@testing-library/react';
import Hello from './Hello';
describe('Hello component', () => {
it('renders component', () => {
render( <Hello />);
//code to test that the heading renders
})
})
screen object
screen
is a utility object that has different methods for querying the elements in the DOM. It contains query methods like getByRole()
, getByText()
, and so on.
For example, if you have a component that displays a message when rendered, you can test that the specific element is rendered.
Hello.tsx
export default function Hello() {
return <h1>Hello world</h1>;
};
//Hello.test.tsx
import {describe, it} from 'vitest';
import {render, screen} from '@testing-library/react';
import "@testing-library/jest-dom/vitest";
import Hello from './Hello';
describe('Hello component', () => {
it('renders heading', () => {
render( <Hello />); //renders the component
let heading = screen.getByRole('heading'); //selects the heading element
expect(heading).toBeInTheDocument(); //the test will pass if the heading is rendered in the DOM
})
})
getBy and queryBy methods
getBy
and queryBy
are query methods used to select an element based on some conditions. There are different variants of these query methods which includes getByRole()
, getByText()
, getByTestId()
, and so on.
For example, if you have a component that renders some content based on whether the user is logged in or not. You can use these query methods to check that the elements are rendered.
//UserInfo.tsx
function UserInfo({ isLoggedIn, user}: {isLoggedIn:boolean, user:string}) {
if (!isLoggedIn){
return <button>Login</button>
}
return (
<div>
<p>Welcome, {user}!</p>
<button>Logout</button>
</div>
)
};
export default UserInfo;
//UserInfo.test.tsx
import {it, expect, describe} from 'vitest';
import {render, screen } from '@testing-library/react';
import "@testing-library/jest-dom/vitest";
import UserInfo from './UserInfo';
describe('UserInfo component', () => {
it('displays info for loggedin user', () => {
render( <UserInfo isLoggedIn={true} user="user 1" />); //renders the element in the DOM
let welcomeText = screen.getByText(/Welcome.*user/i ) //matches the element that contains 'Welcome' and 'user' text
let logoutButton = screen.getByRole('button', {name: /logout/i}); //matches a button element with a text content of 'logout'
expect(welcomeText).toBeInTheDocument();
expect(logoutButton).toBeInTheDocument();
})
})
The variants are the same for getBy
and queryBy
methods. (getByRole does the same thing as queryByRole, getByText does the same thing as queryByText, and so on.). The difference them is that getBy
returns an error if the element is not found while queryBy
returns null if the element is not found.
For example, let’s say you want to test that the login button is not rendered when the user is logged in. If you use getBy
to query the login button, you’ll get an error.
describe('UserInfo component', () => {
it('does not display login button when user is logged in', () => {
render( <UserInfo isLoggedIn={true} user="user 1" />);
let loginButton = screen.getByRole('button', {name: /login/i});
expect(loginButton).not.toBeInTheDocument();
})
})
But when you use queryBy
method, the test works.
describe('UserInfo component', () => {
it('does not display login button when user is logged in', () => {
render( <UserInfo isLoggedIn={true} user="user 1" />);
let loginButton = screen.queryByRole('button', {name: /login/i});
expect(loginButton).not.toBeInTheDocument(); //passes if the element is not rendered in the DOM
})
})
toBeInTheDocument()
The toBeInTheDocument() method is one of the matchers from jest-dom
. It checks if the element is in the DOM. Other jest-dom matchers include toBeDisabled()
, toBeChecked()
, toHaveClass()
, toHaveTextContent()
, and so on.
import {it, expect, describe} from 'vitest';
import {render, screen } from '@testing-library/react';
import "@testing-library/jest-dom/vitest";
import Component from './Component';
describe('Component', () => {
it('renders heading', () => {
render( <Component />);
let heading = screen.getByRole('heading')
expect(heading).toBeInTheDocument();
})
})
fireEvent object
The fireEvent object is used to simulate user interactions.
To use fireEvent, you first need to import it from the React testing library and then simulate whatever action you want. fireEvent supports events like click, change, submit, keypress, etc.
Let’s say, for example, you have a component that contains a form that displays the result entered in the input when submitted.
//FormComponent.tsx
import { useState } from 'react';
function FormComponent() {
const [inputValue, setInputValue] = useState<string>('');
const [submittedValue, setSubmittedValue] = useState<string | null>(null);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); // Prevent form reload
if (inputValue.length > 0) {
setSubmittedValue(inputValue);
setInputValue('');
} else {
setSubmittedValue(null);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter some info"
/>
<button type="submit" data-testid="submit-button">Submit</button>
</form>
{submittedValue && <p>{submittedValue}</p>}
</div>
);
};
export default FormComponent;
//FormComponent.test.tsx
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import FormComponent from './FormComponent';
describe('FormComponent', () => {
let inputElement: HTMLInputElement;
let submitButton: HTMLButtonElement;
beforeEach(() => {
// Render the component once before each test
render(<FormComponent />);
// Setup references to elements
inputElement = screen.getByPlaceholderText('Enter some info');
submitButton = screen.getByTestId('submit-button');
});
afterEach(() => {
// Cleanup the DOM after each test
cleanup();
});
it('should render input content when form is submitted', () => {
// Simulate entering text in the input
fireEvent.change(inputElement, { target: { value: 'Test input' }
});
// Simulate clicking the submit button
fireEvent.click(submitButton);
// Assert that the paragraph with submitted text is displayed
expect(screen.getByText('Test input')).toBeInTheDocument();
});
it('should not render anything if the input is empty on submit', () => {
// Simulate clicking the submit button without entering any text
fireEvent.click(submitButton);
// Assert that the paragraph is not rendered
const paragraph = screen.queryByText('Test input');
expect(paragraph).not.toBeInTheDocument();
});
});
What can I test when using Vitest and React-testing-library?
When testing React components, you’re to test the output not the implementation. You can test how the component renders when given specific props or the element that renders when a button is clicked, and so on.
What are the examples of tests in can run in React apps?
There are different scenarios for tests you can write for your React apps.
In a todo app, you can test if the list renders correctly when a new note is added.
In an e-commerce site, you can test if the product information is correctly rendered in the cart component when the 'add to cart' button is clicked.
For forms, you can test for password strength, invalid inputs, and form submission.
How do I run an end-to-end test with Vitest?
Vitest is best suited for unit and integration testing. For end-to-end testing, tools like Cypress, Playwright, or Puppeteer are better suited for it.
How do I test components with state?
If you have a component that depends on some internal states. You can test that the UI updates correctly in response to the state changes.
Let’s say, for example, you have an 'add to cart' and 'remove from cart' button. You can test that the 'add to cart' increases the total product and 'remove from cart' removes the total product.
Is it necessary to test React state and hooks?
No, it isn’t. You should run tests based on how the user sees and interacts with it. The user doesn’t see state or hooks since they are internal implementations. So should only test that the content displays as it should.
Can I run the npm test
and npm run dev
commands together?
Yes, you can.
You can do this by using either the Concurrently library or runnint them in separate terminal windows
Concurrently
The first method is by using a library like Concurrently to run the 2 scripts together.
To set this up, you first need to install concurrently as a development dependency
npm install --save-dev concurrently
After that, you should create a new script in the package.json
file that concurrently runs the two scripts.
"scripts": {
"test": "vitest",
"test:watch": "vitest --watch",
"test:ui": "vitest --ui",
"start:all": "concurrently \"npm run dev\" \"npm run test\""
}
Then you can finally run npm run start:all
in your CLI
Separate terminal windows
The second method is by creating 2 terminal windows. One for running the React code. The other for running Vitest in watch mode.
First, you need to open a terminal, navigate to the project’s directory, and run the command below to run your React code.
npm run dev
Next, you should open another terminal window, navigate to the project directory and run Vitest in watch mode.
npm run test:watch
How do I skip tests in a file?
You can use the skip()
method to temporarily exclude tests you don’t want to run. You can skip individual tests or an entire test suite.
Here is the syntax:
describe('test-suite', (){
it.skip('test case 1', () => { //this test case is skipped
//assertions
})
it.skip('test case 2', () => { //this test case is also skipped
//assertions
})
it('test case 3', () => { //this test is executed
//assertions
})
})
What are the common configurations in the vitest config file?
The properties in the vite.config.ts
(or vitest.config.t
s) file allow you to control how the test is run. Every property for configuring the test must be within the test object.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
//properties
}
});
The common properties include environment
, globals
, setupFiles
, and so on.
environment
The environment property specifies the environment in which the test will run. If you’re using Vitest to test your React components, the environment needs to simulate a browser environment. This means the value can be either jsdom
or happy-dom
. However, if you’re running the test for Node.js, the value should be node.
test: {
environment: 'jsdom'
}
globals
When globals is set to true, you’ll be able to use global functions(like describe, it, expect, etc) from Vitest without explicitly importing it.
test: {
environment: 'jsdom',
globals: true
}
setupFiles
setupFiles defines the path to files that’ll be executed before each test files. Adding property also reduces the number of imports you add in your test files.
test: {
environment: 'jsdom',
globals: true,
setupFiles: `./src/setup.ts`
}
In the setup.ts file, you can store all the repetitive imports in it.
import {render, screen} from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
Once that is done, the only import you’ll need will be the component you want to test.
//File.test.tsx
import Component from ‘./component’;
describe('test suite', (){
it('test case 1', () => {
//assertions
}
it('test case 2', () => {
//assertions
}
})
Conclusion
In this article, we covered a lot of things. You learned how to set up and test React code in Vitest. You also got answers to some of the questions you might have asked (or thought) about when running tests with Vitest. I hope you find this article a useful guide to learning how to run React tests with Vitest. Thanks for reading. Bye.
Top comments (0)