Eternal Dev
Posted on July 23, 2022
React is really popular when creating frontend components and when the scale of your application is increased, we need to have robust tests to cover all scenarios. The unit testing component becomes a great way to ensure the quality of the application and easily find bugs in the build time when the tests are written well. In this post, we will learn how to test a component with React and Vitest
What are we building?
We are going to build a simple accordion component in React and write unit tests in Vitest. The component will have two states. First is the collapsed state which displays only the title. Another state will be the open state which shows the title and the content below the title.
What is Vite?
Vite is a build tool that is easy to use and fast to compile the react project. We will make use of vite since it is easy to integrate the Vitest testing tool. If you want to know the basics of Vite, we have covered it in this blog post
Why use vitest?
Vitest is really fast and has a good developer experience when used with Vite. We can share the configuration of vite with vitest to make it simple and also ensures that the testing environment is similar to the build environment. Vitest supports HMR which really speeds up your workflow
What is HMR?
HMR stands for Hot Module Reloading. Whenever there is any change to the code, only the changes are updated to the server and the server reflects the new changes
The speed of seeing the changes on your server is improved as we are only sending partial changes for reloading instead of reloading the whole code.
Now that all the jargons are out of the way, let’s see some code that you have come for.
Initialize React project using Vite
We can initialize the project using the following command
npm init vite
cd react-vite-vitest
npm install
Adding Vitest for Testing
We can add Vitest to start adding tests to the project. Install the Vitest as a dev dependency.
npm install -D vitest
Vitest Configuration
One of the advantages of Vitest is that it uses the same config as Vite. This ensures that the test environment is the same as the build environment which increases the reliability of the tests
We will update the config to the following code to add js-dom
which helps in testing
/// <reference types="vitest" />
/// <reference types="vite/client" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
}
})
Creating an Accordion component
Create a new component called Accordion.tsx
and add the following code to create a simple accordion component. It is still incomplete and we will complete it by adding testing first
import React from "react";
type AccordionProps = {
title: string;
children: React.ReactNode;
}
const Accordion = (props: AccordionProps) => {
const {title, children} = props;
return (
<div className="accordion">
<h3 className="accordion-title">{title}</h3>
<div className="accordion-content">
{children}
</div>
</div>
);
}
export default Accordion;
We are just taking the title
and children
and displaying them. An accordion component should be able to shrink and expand when a button is clicked. So let’s add a test case first for that feature and then implement it.
Creating the test in vitest
Create a new file called Accordion.test.tsx
which will contain the test for the Accordion component. Add the following code to that file
import {describe, test} from 'vitest';
describe("Accordion test", () => {
test("Should show title", () => {
})
})
Let’s break down the above code
- describe - Used to group the test and used to describe what is currently being tested
- test - Individual test which is run by Vitest. It can either pass or fail
Here we have not added any test which will return true or false. We will do that shortly after adding the test script
Adding the test script
We need to add the vitest
command to the package.json to start the testing script.
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest"
},
Just by calling the vitest
command, the test execution will start and it will be in watch
mode. This means that any changes being done to the file will re-run the test.
Start the test script
npm run test
Since we don't have any expect statement, the test is considered to be passed
Adding the config for Vitest
We need to have the DOM functionality replicated in the test environment to properly test the react components
JSDom helps in getting that environment for test and so we need to install that as a dev dependency.
We will also make use of testing-library
as well which will help in having more utility functions to help test the components. We will get things like render
function from this package which will simulate the component being rendered on the browser.
Installing the testing dependecies
npm i -D jsdom @testing-library/react
Adding the config for Vitest
/// <reference types="vitest" />
/// <reference types="vite/client" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
}
})
Write a unit test to see if text is visible in vitest
import {describe, expect, test} from 'vitest';
import {render, screen} from '@testing-library/react';
import Accordion from './Accordion';
describe("Accordion test", () => {
test("should show title all the time", () => {
render(<Accordion title='Testing'><h4>Content</h4></Accordion>);
expect(screen.getByText(/Testing/i)).toBeDefined()
})
})
This is the first basic test that makes sure that the title is always displayed on the screen. We are making use of some of the functions from testing-library
like render
and screen.getByText
getByText returns the element if found otherwise it will throw an exception which will fail the test case.
There are lot more utility functions to choose from based on your use case
https://testing-library.com/docs/react-testing-library/api
Create a test for hiding and showing content
We need to render the component on each test case. We can make use of beforeEach
in this case which will run the code inside before each test
import {beforeEach, describe, expect, test} from 'vitest';
import {render, screen} from '@testing-library/react';
import Accordion from './Accordion';
describe("Accordion", () => {
beforeEach(() => {
render(<Accordion title='Testing'><h4>Content</h4></Accordion>);
});
test("should show title all the time", () => {
expect(screen.getByText(/Testing/i)).toBeDefined()
})
test("should not show the content at the start", () => {
expect(screen.getByText(/Content/i)).toBeUndefined()
})
})
The second test should be failing now because we are expecting that the content should not be shown at the start but we didn’t implement the code to do that. This is a good example of how TDD (Test Driven Development) works. We first write a test that will fail and then implement the functionality to make it pass.
Implementing the logic to pass the test
import React, { useState } from "react";
import './Accordion.css'
type AccordionProps = {
title: string;
children: React.ReactNode;
}
const Accordion = (props: AccordionProps) => {
const {title, children} = props;
const [show, setShow] = useState(false);
const onAccordionClick = () => {
setShow(!show);
}
return (
<div className="accordion">
<div className="accordion-title">
<h3>{title}</h3>
<button onClick={() => onAccordionClick()}>{!show ? 'Show' : 'Hide'}</button>
</div>
{show && (
<div>
{children}
</div>
)}
</div>
);
}
export default Accordion;
We are adding the code to hide and show the content of the accordion. This is achieved by simply changing the state variable of show
We are setting the initial value of show
to false which will make the test pass.
Now that we have the basic accordion feature completed, let’s focus on getting more styles using CSS.
Adding the styles for the Accordion
.accordion {
width: 80vw;
border: 1px solid gray;
border-radius: 5px;
}
.accordion-title {
padding: 0px 25px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid gray;
}
Writing test to validate the open/close behavior
We have completed the functionality of Accordion. Now we can add one more test to see if the accordion opens with a click of the button.
Let’s write the 3rd test like below
fireEvent
from the testing library helps in simulating user actions in a unit test. We are using the click
method to click the button. This should trigger the accordion to open and then we are awaiting the action to take place. Since that will be async action, we are using the await
keyword.
The async unit test will have a default timeout and it will wait till that time. Once the timeout is completed, it will fail the test.
import {beforeEach, describe, expect, test} from 'vitest';
import {fireEvent, render, screen, waitFor} from '@testing-library/react';
import Accordion from './Accordion';
import "@testing-library/jest-dom";
import { act } from 'react-dom/test-utils';
describe("Accordion", () => {
beforeEach(() => {
render(<Accordion title='Testing'><h4>Content</h4></Accordion>);
});
test("should show title all the time", () => {
expect(screen.getByText(/Testing/i)).toBeInTheDocument();
})
test("should not show the content at the start", () => {
expect(screen.queryByText(/Content/i)).not.toBeInTheDocument();
})
test("should show the content on accordion click",async () => {
const title = screen.getByText(/Show/i);
fireEvent.click(title)
expect(await screen.findByText(/Content/i)).toBeInTheDocument();
})
})
Conclusion
We have learned how to write unit tests with Vitest in React. Vitest is still in the beta stage and not yet ready for production use. We think vitest has a huge potential and looks like a good alternative to Jest which is being used by a lot of developers.
Let us know if you have worked with Vitest and any feedback on this post is welcome
Join our Discord - https://discord.gg/AUjrcK6eep
Posted on July 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.