Vitest is a lightweight and fast testing framework built on Vite, designed to integrate with modern JavaScript and TypeScript projects smoothly.
With Vitest, you benefit from first-class ES module support, instant test execution, and a familiar Jest-compatible API, making it an excellent choice for front-end and back-end applications.
In this article, you'll explore Vitest's core features and learn how to write and execute tests efficiently.
Prerequisites
Before proceeding with this tutorial, ensure you have Node.js installed, preferably the latest LTS version.
Additionally, this guide assumes familiarity with JavaScript and a basic understanding of testing concepts.
Step 1 — Setting up the directory
In this section, you'll create a project directory and set up Vitest to test your JavaScript code.
To begin, create a new directory and navigate into it:
Then, initialize the directory as an npm project:
This command creates a package.json file, which stores metadata and dependencies for your project.
Next, enable ES Modules by adding the following to your package.json file:
Now, install Vitest as a development dependency:
After installation, add a test script to your package.json file:
You're now ready to create a simple ES Module function and test it using Vitest. Here’s the function in full:
The add() function takes two numbers and returns their sum. Save this file at the root of your project.
Next, let’s write the first test using Vitest.
Step 2 — Writing your first test
Unit tests help ensure that individual functions behave as expected under different conditions. Instead of manually verifying outputs, you will automate the process using Vitest.
Start by creating a tests directory in your project root:
Next, create a file named math.test.js inside the tests directory and add the following test:
The describe function organizes related tests within a test suite, providing a clear structure to the test file.
Within this suite, the it function defines individual test cases, each specifying a particular behavior to verify.
To ensure correctness, the expect function performs assertions, checking whether add(1, 2) returns 3 as expected.
Now that the test is written, let's run it in the next step.
Step 3 — Running your tests
With your test file set up, you can run it using Vitest.
To execute all tests, run:
Since the test script in package.json is set to vitest, this command is equivalent to:
Vitest will automatically find and run test files inside the tests directory. The output should look something like this:
The test runner successfully detected and executed the math.test.js file, confirming that the test suite ran as expected. Alongside the results, the execution time for each test case is displayed, providing insight into the performance of the test run.
Vitest also enables watch mode by default, as indicated by the PASS Waiting for file changes... line in the output. Once tests have run, Vitest remains active, continuously monitoring for modifications.
Any changes to math.js or math.test.js trigger an automatic re-run, eliminating the need to restart the process manually. This creates a fast feedback loop, making it easier to catch errors and verify updates in real-time.
Step 4 — Test filtering and running specific tests
As your test suite grows, it becomes increasingly important to run only relevant tests. Vitest provides several ways to filter and run specific tests, making the development process faster and more efficient.
Using .only to focus on specific tests
If you want to run just one test or a group of tests within a file, you can use the .only method. This will execute only the test(s) marked with .only, allowing you to focus on specific functionality during development.
For example, if you're working on a feature and only want to run the add function test, modify the test like this:
When you run the tests, only the test with .only will execute, and the other test will be skipped. Here's how the output would look:
As shown in the output above, the first test ran successfully, while the second one was skipped because it wasn’t marked with .only.
This allows you to zero in on the specific functionality you're working on without running the entire suite.
Using .skip to temporarily exclude tests
Sometimes, you may want to temporarily exclude certain tests, like when they're not ready or are causing issues. You can do this with .skip, which will skip the marked test during the run.
For instance, if you want to skip the first test, you can change it to .skip like this:
In this case, when you run the tests, the first test will be skipped, but the second will run:
As shown in the output above, the test with .skip is excluded from the run, allowing you to focus on the tests that are relevant to your current work.
Filtering tests with command line arguments
Vitest provides powerful command-line filtering options that let you selectively run tests without modifying your code. These options are especially useful in CI/CD pipelines or when you need to focus on specific test areas.
For example, let's say you want to run all tests related to the add function. Here's the original test suite without any filtering:
You can filter tests based on their description using the -t (or --testNamePattern) flag. This allows you to run only tests that match a specific pattern in their description, which is perfect for isolating functionality while working on a particular feature or debugging an issue.
To run the tests that mention the add function in their description, use the following command:
The double dash -- separates npm’s arguments from those passed to Vitest. This command runs only tests that include the string "add function" in their descriptions. The output should look like this:
In this example, only the tests related to the add function are run, and the output confirms that both tests passed.
You can further narrow your test selection by using more specific patterns. For example, if you want to run only the tests that contain the phrase "should return 3", you can do so like this:
This will run only the test containing "should return 3" in its description.
Step 5 — Using in-source tests
Vitest makes writing tests directly within your source code easy, eliminating the need for separate test files.
This approach, known as in-source testing, is useful when you want to quickly test individual functions without the overhead of creating and managing separate test files.
For example, take the math.js file that contains the add() function. You can modify it to include in-source tests by adding the following code:
Here, the tests are added directly below the function. When you run the tests, Vitest detects these in-source tests and runs them, just like it would run any test file.
The import.meta.env.MODE === 'test' condition ensures that the tests are only executed during test runs, preventing them from affecting production builds.
Since the test logic is embedded within the same file as the source code, Vitest runs these in-source tests along with any other tests defined in your project. The output will look similar to the following:
The output shows that Vitest executes both the standard test file we created in Step 2 and the in-source tests.
While this method is convenient, especially in small applications, separating your tests from the source code is generally recommended as your project grows. This separation improves maintainability and scalability, especially when working in teams or on larger codebases.
With that in mind, we’ll stick to the traditional approach of keeping the tests separate from the source code. Here's the original math.js function again:
Now that you’ve learned how to write and execute basic in-source tests, you will learn mocking.
Step 6 — Mocking with Vitest
Mocking allows you to simulate external dependencies, making isolating the functionality you're testing easier. This is particularly helpful when avoiding slow or unreliable systems like file systems, databases, or APIs in your tests.
This section will walk you through an example of mocking the fs.readFile method using Vitest’s built-in mocking features. The goal is to mock the behavior of reading from a file without actually interacting with the filesystem.
Let’s say you have a function that reads the contents of a text file. Instead of reading the file during the test, which could be time-consuming or unreliable, you can mock fs.readFile to return a fixed value. This makes your tests faster and more controlled.
Start by creating an empty file, text-content.txt:
Then, create a fileReader.js file in the root of the project directory with the following function to read the contents of a file:
This function uses fs.promises.readFile to asynchronously read the contents of a file and returns the content as a string.
Next, create a test file called fileReader.test.js inside the tests directory. The goal here is to mock fs.promises.readFile so it returns a predefined string instead of actually reading from the filesystem.
Here, the code imports the necessary testing utilities and the readFileContent function being tested. The important part is where vi.mock() replaces the actual fs/promises module with a mock implementation containing a simulated readFile function that returns "Mocked content" instead of reading from the actual file system.
When the test calls readFileContent(), it unknowingly uses this mock instead of the real file system API. The test then verifies that the mock was called exactly once with the correct arguments, and that the function returned the expected mocked content.
This approach isolates the test from the actual file system, making it faster, more predictable, and free from external dependencies—a key practice in unit testing.
Upon saving, the output should look something like this:
This means your test ran successfully, and fs.readFile was mocked as expected.
In some cases, you may need to reset mocks between tests to ensure they don’t interfere with each other. Vitest automatically resets mocks after each test, but you can manually reset them using mock.reset() if you need finer control over the process.
For example, if you’re running multiple tests and want to ensure that each test starts with a clean mock state, you can add:
This ensures that mocks are cleared before each test, making your tests more isolated and predictable.
Now that you know how to mock external dependencies in Vitest, you'll explore setup and teardown hooks in the next section.
Step 7 — Using setup and teardown hooks
When writing multiple tests, you'll often need to perform the same setup or cleanup tasks before or after each test. Vitest provides a set of powerful hooks to help you manage these tasks more efficiently, keeping your test code clean and maintainable.
Let's build on our file reader example and introduce setup and teardown functionality using Vitest's built-in hooks. This will allow us to ensure mocks are properly reset and maintained across multiple tests.
Start by updating the fileReader.test.js file to incorporate these setup/teardown hooks:
The beforeEach hook runs before each test. In this case, you use it to reset all mocks to their initial state and set a default mock implementation for fs.readFile. This ensures that each test runs with a clean slate, avoiding any cross-test contamination.
Once you run this test, you should see output like the following:
With this setup, you've successfully integrated setup hooks into your tests. Using beforeEach, you can automate repetitive tasks such as resetting mocks or initializing resources. This leads to cleaner, more organized, and maintainable test code.
Beyond beforeEach, Vitest offers additional hooks—afterEach, beforeAll, and afterAll. These allow you to execute setup and teardown logic either before or after a group of tests, rather than for each individual test. This is especially useful when working with shared resources like database connections, ensuring efficient setup and cleanup.
For instance, when testing database operations, you can use beforeAll to establish a connection once before any tests run and afterAll to close the connection once all tests have completed:
Using setup and teardown hooks ensures that tests remain isolated and run in a controlled environment. Automating setup tasks like mock resets and resource initialization reduces redundancy and makes the test suite more structured.
Now that you've incorporated setup and teardown hooks, the next step is to measure test coverage and ensure that all critical parts of your code are properly tested.
Step 8 — Code coverage with Vitest
Code coverage is a critical metric that helps you understand how much of your code is exercised by your tests. Vitest provides built-in coverage reporting capabilities, making identifying untested parts of your codebase easy and improving your testing strategy.
Vitest uses c8 for coverage reporting by default. To enable coverage reporting, you need to install the @vitest/coverage-v8 package:
Once installed, you can run tests with coverage by using the --coverage flag. Update the package.json file to include a dedicated script for running tests with coverage:
Now, you can run the coverage report with the following:
This will run your tests and generate a detailed coverage report. The output will look something like this:
The coverage report provides several key metrics, such as statement coverage, branch coverage, function coverage, and line coverage.
In the example above, you can see that math.js has 100% coverage across all metrics, which means all code in this file is being tested.
However, fileReader.js has only 77.77% line coverage and 50% branch coverage, indicating that the error handling path (lines 8-9) isn't being tested.
Modify the test for the error case in fileReader.js to improve coverage:
Running the coverage report again will show improved coverage:
Now, all files have 100% coverage, which means all code paths are being tested.
With coverage reports in place, you can adopt a coverage-driven testing approach. Run coverage reports regularly to identify untested code and focus on areas with low coverage first.
Step 9 — Exploring Vitest UI
Vitest offers a graphical user interface that provides a more interactive way to work with your tests. The UI offers valuable insights into your test suite, making debugging failing tests easier and analyzing execution times.
To enable the Vitest UI, install the package first:
Then update your package.json file to include a dedicated script:
Now, you can launch the UI with the following command:
This command starts the Vitest UI server and automatically opens it in your default browser. If it doesn't open automatically, you can access it by navigating to http://localhost:51204/__vitest__/#/ in your browser.
The Vitest UI consists of three main sections:
- Test list panel – Displays all test files, their statuses, and execution times.
- Test details panel – Shows logs, errors, and assertions for individual test cases.
- Dashboard summary – Provides an overview of total tests, passed/failed counts, and overall test duration.
Tests can be rerun individually or as a group directly from the UI. Clicking the play button next to a test case re-executes only that test without running the entire suite.
Upon re-running a test, the module graph is updated, helping to visualize dependencies and track test execution flow.
For further analysis, the UI also allows viewing the test source code, making inspecting the logic behind failing tests easier.
The UI also provides interactive capabilities for running specific tests. You can:
- Run all tests with a single click
- Click on an individual test to run just that test
- Filter tests by status (passed, failed, skipped)
- View test execution times to identify slow tests
This visual approach to test execution simplifies debugging and speeds up development, especially when working with larger test suites.
Instead of manually modifying test files or running command-line filters, the UI enables quick navigation, targeted execution, and deeper insights into test performance.
Final thoughts
In this article, you've explored key features of Vitest. You've learned to set up a project, write tests, and leverage advanced features like filtering, mocking, and code coverage.
As you continue your testing journey, the official Vitest documentation is an excellent resource for exploring more advanced features, configuration options, and best practices