<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ vitest - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ vitest - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 28 Aug 2026 22:40:06 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/vitest/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Automate Your Tests in Express Using Vitest ]]>
                </title>
                <description>
                    <![CDATA[ Thinking through API logic while constantly switching tabs to test application integration can be overwhelming and time-consuming. Well, you can save your time and energy by writing tests for your app ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-automate-your-tests-in-express-using-vitest/</link>
                <guid isPermaLink="false">6a91aa2c85465e213741c4de</guid>
                
                    <category>
                        <![CDATA[ vitest ]]>
                    </category>
                
                    <category>
                        <![CDATA[ unit testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Integration Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mocking ai ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API mocking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mocking api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mongoose-mocking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ qa testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Quality Assurance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Supertest ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mockingoose ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ jabo Landry ]]>
                </dc:creator>
                <pubDate>Fri, 28 Aug 2026 15:33:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/098eaca4-3334-4a81-b6f6-ad0b167eee44.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Thinking through API logic while constantly switching tabs to test application integration can be overwhelming and time-consuming.</p>
<p>Well, you can save your time and energy by writing tests for your application that run whenever you add a new feature, all without leaving your IDE during development. This will help you be confident that each feature works as expected.</p>
<p>In this guide, I'll help you build confidence through code: you'll learn how to validate APIs with tests first, then confirm the results in Postman or any other API testing tool.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p><strong>Node.js &amp; Express basics:</strong> You should have a working knowledge of Node.js and Express (or a similar library like <code>fastify</code>), including how to build and run a simple API.</p>
</li>
<li><p><strong>Working knowledge of TypeScript:</strong> The code snippets in this guide are written using TypeScript, so you should have a solid understanding of TypeScript basics.</p>
</li>
<li><p><strong>Basic familiarity with MongoDB:</strong> Helpful but not required. The examples in this guide use MongoDB for demonstration purposes, but the underlying logic applies to any database or data layer. Only the tooling differs.</p>
</li>
<li><p><strong>Hands-on API experience:</strong> Prior experience writing at least one backend API with Express will help you follow along more effectively.</p>
</li>
<li><p><strong>Curiosity and motivation:</strong> A willingness to deepen your backend skills by learning how to write and run tests for your APIs.</p>
</li>
<li><p><strong>Environment setup:</strong> Node.js version <strong>20 or higher</strong> installed on your machine.</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-testing-concept">Testing Concept</a></p>
</li>
<li><p><a href="#heading-unit-testing">Unit testing</a></p>
</li>
<li><p><a href="#heading-comparison-between-unit-and-integration-tests">Comparison between unit and integration tests</a></p>
</li>
<li><p><a href="#heading-summary">Summary</a></p>
</li>
</ul>
<p>You can find all the code snippets used in this guide in this <a href="https://github.com/jabo-arnold-landry/testing-lesson">Git repository</a>. Each example has its own branch, and they're combined into a main branch if you want a full example version. Please consider starring the repository if you find it helpful.</p>
<h2 id="heading-key-testing-concepts">Key Testing Concepts</h2>
<p>Testing helps you build confidence in your codebase. You wrie code that tests other code in your application, or code that checks if a function or feature behaves the way that it should.</p>
<p>In this guide, we'll cover two types of testing:</p>
<ul>
<li><p><strong>Unit testing</strong>: This is the most basic type of testing and, in my opinion, the easiest. You test a single piece of code in isolation to see how it behaves.</p>
</li>
<li><p><strong>Integration testing</strong>: This approach is used to test how different parts of the application are integrated to make sure they're working together as intended.</p>
</li>
</ul>
<h3 id="heading-key-testing-terms">Key Testing Terms</h3>
<p>Throughout this guide, I'll be using some technical terms are related to testing which I want to explain up front:</p>
<ul>
<li><p><strong>Mocking</strong>: mocking is a technique used to make a fake implementation of a real function call.</p>
</li>
<li><p><strong>Spying</strong>: spying is a way of inspecting a function to see, for example, if the function is called with a certain type of argument or how many times it's been called.</p>
</li>
<li><p><strong>Assertion</strong>: assertions check to see if the output you're getting matches the expected output.</p>
</li>
</ul>
<p>Alright now that we have that covered, let's go over some basics of testing so you have the best practices down before we start writing tests.</p>
<h3 id="heading-how-to-name-a-test-file">How to Name a Test File</h3>
<p>Make sure your test files follow one of these naming conventions:</p>
<ul>
<li><p><code>filename.test.ts</code></p>
</li>
<li><p><code>filename.spec.ts</code></p>
</li>
<li><p><code>filename.test.js</code></p>
</li>
<li><p><code>filename.spec.js</code></p>
</li>
</ul>
<p>Both the <code>spec</code> and <code>test</code> keyword in a file name makes it possible to run the test file. They also help testing frameworks run the right file.</p>
<p>You can choose either the TypeScript or JavaScript extension on a file based on which language you're using to write the test. For this guide I'm using TypeScript so I'll be using the TypeScript (<code>.ts</code>) test file version.</p>
<h3 id="heading-parts-of-a-testing-file">Parts of a Testing File</h3>
<p>Typically, a test file will have four main parts that you should be familiar with, which are:</p>
<ul>
<li><p><code>describe</code>: Used to describe which test you're going to write.</p>
</li>
<li><p><code>it</code>: Used to specify a condition that a function must pass when tested against.</p>
</li>
<li><p><code>expect</code>: Used to determine what type of results you're expecting when you call a function that's being tested</p>
</li>
<li><p><code>matchers</code>: These are different method available on the <code>expect</code> keyword that we use to evaluate if the function we're testing returns a value that meets the expected data or value.</p>
</li>
</ul>
<p>Example of a test file:</p>
<pre><code class="language-typescript">import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () =&gt; {
  it("must define email validation function", () =&gt; {
    expect(validateEmail).toBeDefined();
  });
});
</code></pre>
<p>The above snippet tests if the <code>validateEmail</code> function is defined.</p>
<p>In this test file:</p>
<ul>
<li><p>We use <code>describe</code> to specify the description of the test. <code>describe</code> receives a string description of the test and a function to handle different test cases.</p>
</li>
<li><p>The <code>it</code> keyword specifies and defines a test for a function you're testing, <code>it</code> receives a string describing a specific test description and a callback function to execute and handle test assertion.</p>
</li>
<li><p>Then <code>expect</code> uses the function return type to check if it matches a specific condition through the <code>toBeDefined</code> matcher method.</p>
</li>
</ul>
<h3 id="heading-list-of-matchers">List of Matchers:</h3>
<p>There are many matchers available to you. Below are a few of them:</p>
<ul>
<li><p><code>toBe</code>: compares the passed-in value to see if it matches the function's returned value. It's used on primitive data types like strings, numbers, and so on.</p>
</li>
<li><p><code>toEqual</code>: compares the passed-in value to see if it matches the function's returned value. It's used on non-primitive data types like objects, arrays, and so on.</p>
</li>
<li><p><code>toThrow</code>: used on a function that threw an error to check if the function threw expected error object or instance.</p>
</li>
<li><p><code>toBeCalledWith</code>: used to check if a function is called with a given parameter.</p>
</li>
<li><p><code>toBecalledOnce</code>: used to check if a function is called only once.</p>
</li>
<li><p><code>toBeDefined</code>: used to check if a function is defined.</p>
</li>
<li><p><code>toBeUndefined</code>: used to check if a function returns an undefined value.</p>
</li>
<li><p><code>toBeTruthy</code>: used to check if a function returns a true Boolean value.</p>
</li>
<li><p><code>toBeFalsy</code>: used to check if a function returns a false Boolean value.</p>
</li>
</ul>
<p>These are few of the many matchers out there.</p>
<h3 id="heading-vitest-installation">Vitest Installation</h3>
<p>Now that you know some testing basics, we can get into the actual tests. We'll start by installing <code>vitest</code>, the framework that we'll use to run and write tests for our application.</p>
<p>You can choose your preferred package manager to use to install <code>vitest</code> from the list below:</p>
<pre><code class="language-shell">pnpm add -D vitest # for pnpm package manager
npm install -D vitest # for npm package manager
yarn add -D vitest # for yarn package manager
bun add -D vitest # for bun package manager
</code></pre>
<h2 id="heading-unit-testing">Unit Testing</h2>
<p>A unit test focuses on testing a small piece of code in isolation in your application. A simple example could be if you have a function that adds contact info to a database. For the test, you could check if the email is valid before adding the contact to the database.</p>
<p>Let's start by testing a simple email validation function so you can get comfortable with how unit tests works and how to write one:</p>
<pre><code class="language-typescript">export default function validateEmail(email: string) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

  if (regex.test(email)) {
    return true;
  } else {
    throw new Error("Invalid email format");
  }
}
</code></pre>
<p>The above snippet exports a function that receives an email and then uses regex to validate if the email is valid. It throws an error if the email is invalid.</p>
<h3 id="heading-tests-for-the-validateemail-function">Tests for the <code>validateEmail</code> Function</h3>
<p>Let's start by checking if <code>validateEmail</code> returns true for correct emails:</p>
<pre><code class="language-typescript">import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () =&gt; {

  it("returns true for valid email", () =&gt; {
    const sampleEmail = "arnoldjabo@gmail.com";
    expect(validateEmail(sampleEmail)).toBeTruthy();
  });

});
</code></pre>
<p>In the above test, we're creating a variable <code>sampleEmail</code> to be used as a sample email in the <code>validateEmail</code> function. Save this and then run <code>npx vitest</code> in your terminal. You should see a terminal with the results of your test. It should look like the below screenshot:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c7bcff7cf27065100ae8be/84af7e2b-8344-49c8-a744-35d64ee2a1c0.png" alt="passed tests in vitest terminal" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>When you run tests, Vitest shows the list of test files you're testing, how many tests were executed, and how many passed and failed tests you have.</p>
<p>Let's create another test that detects an invalid email:</p>
<pre><code class="language-typescript">import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () =&gt; {

    it("throws error for invalid email", () =&gt; {
    const sampleEmail = "verymasd.com";
    const invalidEmailResults = () =&gt; validateEmail(sampleEmail);
    expect(invalidEmailResults).toThrow("Invalid email format");
  });

});
</code></pre>
<p>For functions that throw errors, you need to wrap them inside another function to prevent them from stopping the test before the test reaches the assertion or <code>expect</code> section.</p>
<p>In our example above, the <code>validateEmail</code> function is wrapped inside another function which will hold whatever the error <code>validateEmail</code> throws is. It then assigns it to the <code>invalidEmailResults</code> variable. Next we use the <code>toThrow</code> matcher on the <code>expect</code> assertion to match a type of error <code>validateEmail</code> expects to be thrown for an invalid email.</p>
<p>If your run the test, you'll have two passed tests now:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c7bcff7cf27065100ae8be/521702d7-073b-44e7-90dc-9a30b664b81d.png" alt="Passed test for invalid email that throws an error for an invalid email" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>If you didn't wrap the <code>validateEmail</code> function inside another function when it throws an error, you'll see something like this when you run the test:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c7bcff7cf27065100ae8be/693fec20-7f56-42ea-be18-4caa5cba3dac.png" alt="The error message you would receive inside the test terminal if you didn't wrap a function that throws an error inside another function." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>As you can see, the error fires before reaching the test final assertion. So when you have a function that throws errors, remember to wrap your function inside another function to avoid throwing errors mid-test.</p>
<p><strong>Tip</strong>: When writing unit tests, focus on the expected input and output of a function. You don't have to worry or need to think about the implementation of the function: the input and output are the key elements here.</p>
<h3 id="heading-testing-api-calls">Testing API Calls</h3>
<p>By now you should understand how a unit test works. So next, let's see how you can unit test an API that makes a call to a database.</p>
<pre><code class="language-typescript">import Contacts from "../../schema/contactList";
import { Request, Response } from "express";
import validateEmail from "../../utils/email-validation";

async function addContacts(req: Request, res: Response) {
  const { contactName, phoneNumber, email } = req.body;
  validateEmail(email);

  const contact = await Contacts.create({ contactName, phoneNumber, email });

  return res.status(201).json({ message: `successfully created ${contact.contactName}` });
}

export default addContacts;
</code></pre>
<p>In the code snippet above, we have a function that creates a contact with the database. It also validates if the passed email is valid.</p>
<p>In this example, we're using mongo DB for the database and Mongoose for connecting the codebase with our mongo DB instance.</p>
<p>Unit tests for non-pure functions (that is, functions that are dependent on external services, like making API calls or calling other functions) are tested a bit differently.</p>
<p>For these cases, you create a mock or fake version of the original function that makes a call to the external service and then define its behavior to match the expected return value that you'd have if you used its real version.</p>
<p>We'll start by mocking the implementation of the email validation function. This will help solidify the understanding on how mocking works in unit testing and testing in general.</p>
<p><strong>Note</strong>: mocking the <code>validateEmail</code> function isn't that important because it doesn't make a big difference from using the email validation function directly here. But for learning purposes, we'll mock it to help you understand how it works.</p>
<p>When mocking the modules import, we use the <code>vi.mock</code> function which helps transform the imports of a given module into mocks or fake versions of the real ones.</p>
<pre><code class="language-typescript">vi.mock(filepath,callback);
</code></pre>
<p><code>vi.mock</code> receives two arguments: the file path location of the module you want to mock, and a callback function called a factory function which we'll use to transform the module imports into mocks.</p>
<p>Let's start by mocking the <code>validateEmail</code> path and creating a factory function to transform the module into mocks:</p>
<pre><code class="language-typescript">vi.mock("../utils/email-validation", () =&gt; {
  return { default: vi.fn()};
});
import validateEmail from "../utils/email-validation";
</code></pre>
<p>In the callback function (factory function), we then return an object of the exported module, with a key of <strong>default</strong> and a value of <code>vi.fn</code>. For function mocking we use <code>vi.fn()</code> which automatically replaces the function's return value with <code>undefined</code>.</p>
<p>We use default as a key because the <code>validateEmail</code> function is exported as a default export. If it was a named export, we would have used the actual export name instead of default in the return object.</p>
<pre><code class="language-typescript">vi.mock("../utils/email-validation", () =&gt; {
  return { validateEmail: vi.fn() };
});
import { validateEmail } from "../utils/email-validation";
</code></pre>
<p>Always import your module after the mock module operation to avoid using the real module.</p>
<p>There are methods on <code>vi.fn()</code> that help define the implementation and behaviors of the mocked function. Some of these methods include:</p>
<ul>
<li><p><code>mockReturnValue</code>: Used to define a return value for a mocked function</p>
</li>
<li><p><code>mockRejectsValue</code>: Used for promise-based functions to define the error the function will return.</p>
</li>
<li><p><code>mockResolveValue</code>: Used for promise-based functions to define the data the function will return.</p>
</li>
<li><p><code>mockImplementation</code>: Used to define a new function behavior of a mocked function.</p>
</li>
<li><p><code>mockReturnThis</code>: Used to return the actual instance of a function you're mocking.</p>
</li>
</ul>
<p>These are the methods that you'll likely use most of the time when defining mock implementation and setting mock return value. Just keep in mind that there are many others.</p>
<p>Here, for <code>emailValidate</code>, we'll be using the <code>mockReturnValue</code> and <code>mockImplementation</code> methods.</p>
<p>Let's use <code>mockReturnValue</code> to make the validateEmail function return true by default, assuming the email will be formatted correctly:</p>
<pre><code class="language-typescript">vi.mock("../utils/email-validation", () =&gt; {
  return { default: vi.fn().mockReturnValue(true) };
});
import validateEmail from "../utils/email-validation";
</code></pre>
<p>We define a mock function with <code>vi.fn</code> and then chain on the <code>mockReturnValue(true)</code> to change the mock function default return value (undefined) to true in our case.</p>
<h3 id="heading-defining-the-mocking-implementation">Defining the Mocking Implementation</h3>
<p>You can do a lot with a mocked function, like defining a new implementation for the mocked function that replaces existing logic in the original function.</p>
<p>Let's create a test suite with a fake email that throws an error when you pass the wrong email while creating contacts.</p>
<pre><code class="language-typescript">import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";
import addContacts from "../src/controllers/add-contacts.controller";

import { type Response, type Request } from "express";

vi.mock("../utils/email-validation", () =&gt; {
  return { default: vi.fn().mockReturnValue(true) };
});

import validateEmail from "../utils/email-validation";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () =&gt; {
  it("throws error for the wrong email address", async () =&gt; {
    const req = {
      body: { ...fakeContact, email: "fakemail" },
    } as Request;  

    const res = {
      status: vi.fn().mockReturnThis(),
      json: vi.fn(),
    } as any as Response;

    (validateEmail as ReturnType&lt;typeof vi.fn&gt;).mockImplementation(() =&gt; {
      throw new Error("invalid email!");
    });
 
    await expect(addContacts(req, res)).rejects.toThrow();
  });
});
</code></pre>
<p>In the snippet above, we're mocking or creating a fake request object that's cast as request type of <code>express</code>. We do the same with the response object – but the difference here is that with response we're also creating mocks for common methods that you'd use on an Express response (which are status and a <code>json</code> object).</p>
<p>We then turn the return type of the <code>validateEmail</code> function into the vitest mocking function type to avoid TypeScript warnings. Then we use the <code>mockImplementation</code> method to throw a new error inside <code>validateEmail</code>.</p>
<p>The assertion works differently because now we're throwing a promise-based error. We use <code>rejects</code> on the assertion and then chain on another matcher that stimulates which type of error the function will throw.</p>
<p><strong>Tip:</strong> when working with TypeScript, the response object can't be cast like we did on the request object because the response object is much stricter than request. So you'll first need to cast it as any and then cast back to the response object. That way you avoid the TypeScript warning while still keeping the type in play for your test code.</p>
<h3 id="heading-mocking-a-mongoose-model">Mocking a Mongoose Model</h3>
<p>With unit tests, we don't want to save test data to a real database. Instead we can fake the implementation of the service that calls the database service – in our case, we can use the <code>create</code> method from Mongoose. It'll save the record to a Mongo database. We can then define what it should return on success (and it should look identical to what it would return if we were using a real database).</p>
<p>We'll start by installing a library for mocking a Mongoose model called <code>mockingoose</code>:</p>
<pre><code class="language-shell">pnpm add -D mockingoose # for pnpm package manager
npm install -D mockingoose # for npm package manager
yarn add -D mockingoose # for yarn package manager
bun add -D mockingoose # for bun package manager
</code></pre>
<p>After installation we'll create a mock for our contacts model:</p>
<pre><code class="language-typescript">import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () =&gt; {
  it("successfully create a new contact to the database", async () =&gt; {
    mockinggoose(Contacts).toReturn(fakeContact, "save");
  });

});
</code></pre>
<p>To mock a Mongoose model, we call the <code>mockinggoose()</code> function and pass the model to mock. Then we use the <code>toReturn</code> matcher to describe what it should return, <code>toReturn</code> matcher expects two arguments.</p>
<p>Those arguments are a fake dataset for the model and a Mongo method that we'll use to work with the data. For our example we'll use <code>save</code> because we're creating records in the document.</p>
<h3 id="heading-unit-testing-the-api">Unit Testing the API</h3>
<p>We can start by writing the first test for the add contact API call like this:</p>
<pre><code class="language-typescript">import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";
import addContacts from "../src/controllers/add-contacts.controller";
import { type Response, type Request } from "express";

vi.mock("../utils/email-validation", () =&gt; {
  return { default: vi.fn().mockReturnValue(true) };
});

import validateEmail from "../utils/email-validation";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () =&gt; {
  it("successfully create a new contact to the database", async () =&gt; {
    mockinggoose(Contacts).toReturn(fakeContact, "save");

    const req = {
      body: fakeContact,
    } as Request;

    const res = {
      status: vi.fn().mockReturnThis(),
      json: vi.fn(),
    } as any as Response;

    await addContacts(req, res);
    expect(res.status).toHaveBeenCalledWith(201);
    expect(res.json).toHaveBeenCalledWith({
      message: `successfully created ${fakeContact.contactName}`,
    });
  });
 });
</code></pre>
<p>For this test assertion, we're using different matchers called <strong>spies</strong>. These are used on a function to inspect how many times it's been called or which parameters were used to call it (and so on).</p>
<p>Here we're expecting the status function of response to be called with a status of 201 as its argument. Then the JSON object is called with a message argument that we're using to send out the response.</p>
<h2 id="heading-integration-tests">Integration Tests</h2>
<p>Integration tests test the communication and integration of different parts of an application. For example, they might check if your database integrates well with the function that makes the API call to the database.</p>
<p>Unlike unit tests (where we don't need to have our test making API calls), with integration tests we're testing if parts of the application integrate together and works as expected. We don't need to mock anything, because we want to make sure that we're successfully sending a request to the backend and connecting to the database.</p>
<h3 id="heading-creating-integration-test-data-storage">Creating Integration Test Data Storage</h3>
<p>When you're running integration tests, there are two ways to create a testing environment that acts as a database. They include:</p>
<ul>
<li><p>Creating a duplicate schema of your real database and using the copy as a testing database environment. Whenever you're running tests, you point your database connectivity to the test DB.</p>
</li>
<li><p>Creating in-memory database storage. This approach doesn't require you to have two separate schemas (one for testing and another for production). Instead you construct the same schema shape in your codebase memory and use it as your testing environment.</p>
</li>
</ul>
<p>Using the first approach is complicated because you have to set up and configure which database to use for which environment. But for the second approach, you can just set up the right schema structure as the original schema and use it for testing without needing to configure it in the database and remove it after use.</p>
<p>For Mongo DB there's a package that simplifies the in-memory storage option for us called <code>mongodb-memory-server</code>. It deletes all the data that was used for testing after the tests have finished running.</p>
<h3 id="heading-how-to-set-up-the-environment-for-the-integration-tests">How to Set Up the Environment for the Integration Tests</h3>
<p>You'll need to install:</p>
<ul>
<li><p><code>supertest</code>: a package that helps you make API calls/requests and returns back the response when testing.</p>
</li>
<li><p><code>mongodb-memory-server</code>: a package that makes in memory database storage for testing data.</p>
</li>
</ul>
<pre><code class="language-shell"># command for pnpm package manager
pnpm add -D mongodb-memory-server supertest @types/supertest

# command for npm package manager
npm install --save-dev mongodb-memory-server supertest @types/supertest

# command for yarn package manager
yarn add --dev mongodb-memory-server supertest @types/supertest

# command for bun package manager
bun add -d mongodb-memory-server supertest @types/supertest
</code></pre>
<p>Before moving on, we need to change the setup of our server entry file.</p>
<p>If you've been using Express with Node or any other framework, you may be familiar with the following type of setup for the server entry file where everything is added into a single file:</p>
<pre><code class="language-typescript">import express from "express";
import { loadEnvFile } from "node:process";
import connectToDB from "../config/dbConfig";
import addContacts from "./controllers/add-contacts.controller";

const app = express();
loadEnvFile();
async function dbConnection() {
  await connectToDB();
}
dbConnection();

app.use(express.json());
app.post("/add-contacts", addContacts);
app.listen(5000, () =&gt; console.log("the server successfully connected"));
</code></pre>
<p>This setup works fine and it's valid in certain cases. But when working with integration tests, it can be problematic. This is because in integration tests, we'll need an instance of Express to use when making the request. If we export the <code>app</code> variable here inside the main file when we make a request while testing, the production DB connection will conflict with the testing DB connection. This'll cause the tests to stop working.</p>
<p>The solution here is create another file, define an Express instance, and export it. Then we'll use the exported Express instance in the server to start a server. The setup looks like this:</p>
<p><code>app.ts</code></p>
<pre><code class="language-typescript">import express from "express";
import addContacts from "./controllers/add-contacts.controller";

const app = express()

app.use(express.json())
app.post("/add-contacts", addContacts);

export default app;
</code></pre>
<p>Then the main file <code>server.ts</code> or <code>main.ts</code> uses the <code>app</code> variable like this:</p>
<pre><code class="language-typescript">import { loadEnvFile } from "node:process";
import connectToDB from "../config/dbConfig";
import app from "./app";

loadEnvFile();

async function bootsrap() {
  await connectToDB();
  app.listen(5000, () =&gt; console.log("the server successfully connected"));
}
bootsrap();
</code></pre>
<p>We're importing the Express instance from the <code>app</code> file and then using the <code>bootstrap()</code> function to set up the database and start the server. With this in place, we can start writing integration tests for the <code>addContact</code> module.</p>
<h3 id="heading-how-to-write-the-integration-tests">How to Write the Integration Tests</h3>
<p><strong>Tip</strong>: With integration test(s) you can name your file like <code>filename.integration.test.ts</code> this is the most commonly used naming convention for integration tests, but it is not mandatory it just a naming convention.</p>
<p>You first need to set up the database testing data storage using the <code>mongoose</code> and <code>mongdb-memory-server</code> packages and the Express instance for making requests.</p>
<pre><code class="language-typescript">import { afterAll, beforeAll, describe, expect, it } from "vitest";

import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import app from "../src/app";


describe("intergration test setup for add contact api", () =&gt; {
  let mongoServer: MongoMemoryServer;
  let server: any;

  beforeAll(async () =&gt; {
    mongoServer = await MongoMemoryServer.create();
    const uri = mongoServer.getUri();
    await mongoose.connect(uri);
    server = app.listen(0);
  });

  afterAll(async () =&gt; {
    mongoServer.stop();
    mongoose.disconnect();
    server.close();
  });
});
</code></pre>
<p>The <code>beforeAll</code> and <code>afterAll</code> functions are <code>vitest</code> functions. <code>beforeAll</code> runs before any test starts executing and <code>afterAll</code> will run after all tests are done executing.</p>
<p>In the test, we set up the database and Express instance before any test runs.</p>
<p>First, we created the <code>mongoServer</code> variable. Then, inside the <code>beforeAll</code> block, we initialize it with <code>MongoMemoryServer.create</code> to create an in-memory database for testing data storage. We get the connection string using the <code>uri</code> variable using the <code>getUri</code> method. Finally we use Mongoose to connect to the generated in-memory connection string.</p>
<p>The server variable is assigned to the Express instance listening to port 0, but you can use any port number of your choice – it's just for demonstration purposes. This creates an Express instance for our testing environment.</p>
<p>In <code>afterAll</code>, after all tests have finished executing, we close the server and in-memory DB and then also disconnect our Mongoose instance.</p>
<p>Within the same <code>describe</code> block, we then add the test description and assertion (same as we did in unit testing):</p>
<pre><code class="language-typescript">import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import app from "../src/app";
import request from "supertest";
import Contacts from "../schema/contactList";

describe("intergration test for add contact api", () =&gt; {
  /*
    Here we do server setup and database in memory setup that was discussed,
    in the previous snippets for setting up integration data storage testing environment 
   */
  const fakeContact = {
    contactName: "arnold",
    phoneNumber: 798600102,
    email: "arnoldjabo@gmail.com",
  };

  describe("POST /add-contacts", () =&gt; {
    it("creates a new record to the database", async () =&gt; {
      const response = await request(app)
        .post("/add-contacts")
        .send(fakeContact);

       const contactList = await Contacts.findOne({
        email: "arnoldjabo@gmail.com",
      })!;
      expect(contactList?.email).toBe("arnoldjabo@gmail.com");
      console.log(contactList);

      expect(response.status).toBe(201);
      expect(response.body).toEqual({
        message: `successfully created ${fakeContact.contactName}`,
      });
    });
  });
});
</code></pre>
<p>Here in the test file, we're describing the test as a post method test for the add-contact endpoint. Then we test if it adds data to the database.</p>
<p>Within the <code>it</code> body, we use <code>request</code> from <code>supertest</code> to make a request to the server we've created. We also chain on an HTTP method with the endpoint we want to test.</p>
<p>For methods that send data to the backend like POST, PATCH, or PUT, we use the <code>send</code>() method on <code>request</code> to add an object of the data that we're sending.</p>
<p>We'll use the response to assert what the response could look like. For example, we're expecting the server to give a status code of 201 on successful data entry and a JSON object with a message property that confirms that it has added a contact.</p>
<p>We're using an assertion to check if the response's status matches what we expect, as well as if the response body matches the expected message we should be getting.</p>
<p>To test if the data are really being added to the database, I've added <code>contactList</code> to get the contact we just added by finding it by email. Then we log the <code>contactList</code> to the console to show how in-memory works. It's pretty much the same as a real Mongo DB instance. If we were to run the tests, we would have something that looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c7bcff7cf27065100ae8be/e20082ae-0d06-48e2-b40b-8c66846c7ed4.png" alt="Out-put for integration test with the console log showing how that stored using in memory database looks like when printed to the screen." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>You can see from the console the in-memory stores and retrieves data as a regular Mongo database does.</p>
<h2 id="heading-when-to-use-unit-vs-integration-tests">When to Use Unit vs Integration Tests</h2>
<p>So when do you use each type of test?</p>
<p>Use unit tests when you have pure functions like the email validation example we had earlier.</p>
<p>And use Integration tests for functions that makes external API calls that are dependent on external service like database calls to avoid mocking every function that you're importing.</p>
<h2 id="heading-summary">Summary</h2>
<p>This guide explains how two types of testing work: unit tests and integration tests.</p>
<p>Unit tests are code that tests specific pieces of your codebase in isolation, and are best for pure functions. Integration test are code that tests successful integration and communication between parts of your application, and they're best for non-pure functions.</p>
<p>If you found the article helpful, you can <a href="https://buymeacoffee.com/jabo1200">buy me coffee</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test React Applications with Vitest ]]>
                </title>
                <description>
                    <![CDATA[ Testing is one of those things that every developer knows they should do, but many put off until problems start appearing in production. If you’re building React applications with Vite, there's a testing framework that fits so naturally into your wor... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-test-react-applications-with-vitest/</link>
                <guid isPermaLink="false">698bb499f3de8b702a26aec1</guid>
                
                    <category>
                        <![CDATA[ unit testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vitest ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aiyedogbon Abraham ]]>
                </dc:creator>
                <pubDate>Tue, 10 Feb 2026 22:43:37 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770763375195/82544dec-aec2-4de9-b7f8-f90349394e81.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Testing is one of those things that every developer knows they should do, but many put off until problems start appearing in production. If you’re building React applications with Vite, there's a testing framework that fits so naturally into your workflow that you might actually enjoy writing tests. That framework is Vitest.</p>
<p>In this tutorial, you’ll learn how to set up Vitest in a React project, write effective tests for your components and hooks, and understand the testing patterns that will help you build more reliable applications.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-vitest-and-why-should-you-use-it">What is Vitest and Why Should You Use It?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-vitest-in-your-react-project">How to Set Up Vitest in Your React Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-write-your-first-test">How to Write Your First Test</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-react-components">How to Test React Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-user-interactions">How to Test User Interactions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-custom-hooks">How to Test Custom Hooks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-mock-api-calls">How to Mock API Calls</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-testing-react-components">Best Practices for Testing React Components</a></p>
</li>
</ul>
<h2 id="heading-what-is-vitest-and-why-should-you-use-it">What is Vitest and Why Should You Use It?</h2>
<p>Vitest is a testing framework built on top of Vite. It uses Vite’s development server and plugin pipeline to transform and load files during testing. This means your tests use the same configuration and plugins as your app (for example, the React plugin, TypeScript support,and so on), so you don’t need a separate build or compile step.</p>
<p>Vitest runs tests in parallel across worker threads for maximum speed, and it automatically enables an instant “watch” mode (similar to Vite’s HMR) that reruns only the tests related to changed files. Vitest also has first-class support for modern JavaScript out of the box: it handles ESM, TypeScript, and JSX natively via Vite’s transformer (powered by Oxc).</p>
<p>Because Vitest provides a Jest-compatible API, you can continue to use familiar testing libraries (for example, React Testing Library, jest-dom matchers, user-event, and so on) without extra setup.</p>
<p>In short, Vitest tightly integrates with your Vite-powered stack (or can even run standalone) and lets you plug in existing testing tools seamlessly.</p>
<p>Here is why Vitest has become popular in the React ecosystem:</p>
<ul>
<li><p><strong>Speed</strong>: Vitest can run tests more than four times faster than Jest in many scenarios. This speed comes from Vite's fast Hot Module Replacement and efficient caching capabilities.</p>
</li>
<li><p><strong>Zero configuration</strong>: Unlike Jest, which required Babel integration, TSJest setup, and multiple dependencies, Vitest works out of the box. It reuses your existing Vite configuration, eliminating the need to configure a separate test pipeline.</p>
</li>
<li><p><strong>Native TypeScript support</strong>: Vitest handles TypeScript and JSX natively through ESBuild, with no additional configuration needed.</p>
</li>
<li><p><strong>Modern JavaScript</strong>: Vitest offers native support for ES modules out of the box, making it ideal for modern JavaScript stacks.</p>
</li>
<li><p><strong>Familiar API</strong>: If you know Jest, you already know most of Vitest. The API is intentionally compatible, making migration straightforward.</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along with this tutorial, you should have:</p>
<ul>
<li><p>Basic knowledge of React and JavaScript</p>
</li>
<li><p>Understanding of React Hooks</p>
</li>
<li><p>Node.js installed (version 14 or higher)</p>
</li>
<li><p>A React project created with Vite (or you can create one as we go)</p>
</li>
</ul>
<h2 id="heading-how-to-set-up-vitest-in-your-react-project">How to Set Up Vitest in Your React Project</h2>
<p>Let's start by creating a new React project with Vite and setting up Vitest.</p>
<h3 id="heading-step-1-create-a-react-project-with-vite">Step 1: Create a React Project with Vite</h3>
<p>If you don't have an existing project, create one with the following command:</p>
<pre><code class="lang-bash">npm create vite@latest my-react-app -- --template react
<span class="hljs-built_in">cd</span> my-react-app
npm install
</code></pre>
<p>This creates a React project with Vite as the build tool.</p>
<h3 id="heading-step-2-install-vitest-and-testing-dependencies">Step 2: Install Vitest and Testing Dependencies</h3>
<p>Install Vitest along with the React Testing Library and other necessary dependencies:</p>
<pre><code class="lang-bash">npm install --save-dev vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
</code></pre>
<p>Here's what each package does:</p>
<ul>
<li><p><strong>vitest</strong>: The testing framework itself</p>
</li>
<li><p><strong>@testing-library/react</strong>: Provides utilities for testing React components</p>
</li>
<li><p><strong>@testing-library/jest-dom</strong>: Adds custom matchers for DOM assertions</p>
</li>
<li><p><strong>@testing-library/user-event</strong>: Simulates user interactions</p>
</li>
<li><p><strong>jsdom</strong>: Provides a DOM environment for testing</p>
</li>
</ul>
<h3 id="heading-step-3-configure-vitest">Step 3: Configure Vitest</h3>
<p>Create a <code>vitest.config.js</code> file in your project root:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vitest/config'</span>;
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">'@vitejs/plugin-react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">plugins</span>: [react()],
  <span class="hljs-attr">test</span>: {
    <span class="hljs-attr">globals</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">environment</span>: <span class="hljs-string">'jsdom'</span>,
    <span class="hljs-attr">setupFiles</span>: <span class="hljs-string">'./src/test/setup.js'</span>,
  },
});
</code></pre>
<p>Setting <code>globals: true</code> exposes the <code>describe</code> and <code>it</code> functions on the global object, so you don't need to import them in every test file. The <code>environment: 'jsdom'</code> setting tells Vitest to use jsdom for simulating a browser environment.</p>
<h3 id="heading-step-4-create-the-test-setup-file">Step 4: Create the Test Setup File</h3>
<p>Create a file at <code>src/test/setup.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { expect, afterEach } <span class="hljs-keyword">from</span> <span class="hljs-string">'vitest'</span>;
<span class="hljs-keyword">import</span> { cleanup } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'@testing-library/jest-dom'</span>;

afterEach(<span class="hljs-function">() =&gt;</span> {
  cleanup();
});
</code></pre>
<p>The <code>cleanup()</code> function runs after each test to clean up the DOM, ensuring tests don't interfere with each other.</p>
<h3 id="heading-step-5-add-test-scripts">Step 5: Add Test Scripts</h3>
<p>Add the following script to your <code>package.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"vite"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"vite build"</span>,
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"vitest"</span>,
    <span class="hljs-attr">"test:ui"</span>: <span class="hljs-string">"vitest --ui"</span>,
    <span class="hljs-attr">"coverage"</span>: <span class="hljs-string">"vitest --coverage"</span>
  }
}
</code></pre>
<p>Now you can run tests with <code>npm test</code>.</p>
<h2 id="heading-how-to-write-your-first-test">How to Write Your First Test</h2>
<p>Let's write a simple test to make sure everything is working. Create a file called <code>sum.test.js</code> in your <code>src</code> directory:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { expect, test } <span class="hljs-keyword">from</span> <span class="hljs-string">'vitest'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum</span>(<span class="hljs-params">a, b</span>) </span>{
  <span class="hljs-keyword">return</span> a + b;
}

test(<span class="hljs-string">'adds 1 + 2 to equal 3'</span>, <span class="hljs-function">() =&gt;</span> {
  expect(sum(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)).toBe(<span class="hljs-number">3</span>);
});
</code></pre>
<p>Run <code>npm test</code> and you should see your test pass. A test in Vitest passes if it doesn't throw an error.</p>
<h2 id="heading-how-to-test-react-components">How to Test React Components</h2>
<p>Now let's test an actual React component. We'll start with a simple component and gradually build up to more complex scenarios.</p>
<h3 id="heading-testing-a-simple-component">Testing a Simple Component</h3>
<p>Create a component called <code>Greeting.jsx</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Greeting</span>(<span class="hljs-params">{ name }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello, {name}!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Welcome to our application<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Now create a test file <code>Greeting.test.jsx</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;
<span class="hljs-keyword">import</span> { Greeting } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Greeting'</span>;

describe(<span class="hljs-string">'Greeting Component'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should render the greeting with the provided name'</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Greeting</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Alice"</span> /&gt;</span></span>);

    <span class="hljs-keyword">const</span> heading = screen.getByRole(<span class="hljs-string">'heading'</span>, { <span class="hljs-attr">level</span>: <span class="hljs-number">1</span> });
    expect(heading).toHaveTextContent(<span class="hljs-string">'Hello, Alice!'</span>);
  });

  it(<span class="hljs-string">'should render the welcome message'</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Greeting</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Bob"</span> /&gt;</span></span>);

    <span class="hljs-keyword">const</span> paragraph = screen.getByText(<span class="hljs-string">'Welcome to our application'</span>);
    expect(paragraph).toBeInTheDocument();
  });
});
</code></pre>
<p>The <code>describe</code> function groups related tests into a single describe block. Each <code>it</code> function contains one test case.</p>
<p>The <code>render</code> function from React Testing Library renders your component in a test environment. The <code>screen</code> object provides query methods to find elements in the rendered output.</p>
<h3 id="heading-understanding-query-functions">Understanding Query Functions</h3>
<p>React Testing Library provides three types of query functions: <code>get</code>, <code>query</code>, and <code>find</code>.</p>
<p><strong>getBy queries</strong>: Throw an error if the element isn't found. Use these when you expect the element to be present.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> button = screen.getByRole(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/click me/i</span> });
</code></pre>
<p><strong>queryBy queries</strong>: Return <code>null</code> if the element isn't found. Use these when you want to assert that an element doesn't exist.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> errorMessage = screen.queryByText(<span class="hljs-string">'Error'</span>);
expect(errorMessage).not.toBeInTheDocument();
</code></pre>
<p><strong>findBy queries</strong>: Return a promise and wait for the element to appear. Use these for asynchronous operations.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> loadedData = <span class="hljs-keyword">await</span> screen.findByText(<span class="hljs-string">'Data loaded'</span>);
</code></pre>
<h3 id="heading-testing-a-counter-component">Testing a Counter Component</h3>
<p>Let's test a more interactive component. Create <code>Counter.jsx</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Counter</span>(<span class="hljs-params">{ initialCount = <span class="hljs-number">0</span> }</span>) </span>{
  <span class="hljs-keyword">const</span> [count, setCount] = useState(initialCount);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Count: {count}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setCount(count + 1)}&gt;Increment<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setCount(count - 1)}&gt;Decrement<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setCount(0)}&gt;Reset<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Create the test file <code>Counter.test.jsx</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;
<span class="hljs-keyword">import</span> userEvent <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/user-event'</span>;
<span class="hljs-keyword">import</span> { Counter } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Counter'</span>;

describe(<span class="hljs-string">'Counter Component'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should render with initial count of 0'</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Counter</span> /&gt;</span></span>);

    expect(screen.getByText(<span class="hljs-string">'Count: 0'</span>)).toBeInTheDocument();
  });

  it(<span class="hljs-string">'should render with custom initial count'</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Counter</span> <span class="hljs-attr">initialCount</span>=<span class="hljs-string">{5}</span> /&gt;</span></span>);

    expect(screen.getByText(<span class="hljs-string">'Count: 5'</span>)).toBeInTheDocument();
  });

  it(<span class="hljs-string">'should increment count when increment button is clicked'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = userEvent.setup();
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Counter</span> /&gt;</span></span>);

    <span class="hljs-keyword">const</span> incrementButton = screen.getByRole(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/increment/i</span> });
    <span class="hljs-keyword">await</span> user.click(incrementButton);

    expect(screen.getByText(<span class="hljs-string">'Count: 1'</span>)).toBeInTheDocument();
  });

  it(<span class="hljs-string">'should decrement count when decrement button is clicked'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = userEvent.setup();
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Counter</span> <span class="hljs-attr">initialCount</span>=<span class="hljs-string">{5}</span> /&gt;</span></span>);

    <span class="hljs-keyword">const</span> decrementButton = screen.getByRole(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/decrement/i</span> });
    <span class="hljs-keyword">await</span> user.click(decrementButton);

    expect(screen.getByText(<span class="hljs-string">'Count: 4'</span>)).toBeInTheDocument();
  });

  it(<span class="hljs-string">'should reset count to 0 when reset button is clicked'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = userEvent.setup();
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Counter</span> <span class="hljs-attr">initialCount</span>=<span class="hljs-string">{10}</span> /&gt;</span></span>);

    <span class="hljs-keyword">const</span> resetButton = screen.getByRole(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/reset/i</span> });
    <span class="hljs-keyword">await</span> user.click(resetButton);

    expect(screen.getByText(<span class="hljs-string">'Count: 0'</span>)).toBeInTheDocument();
  });
});
</code></pre>
<p>In these Counter tests, we first use <code>render(&lt;Counter /&gt;)</code> to mount the component in a virtual DOM. We then query the output using Testing Library’s <code>screen</code> object. For example, <code>screen.getByText('Count: 0')</code> finds the element displaying the initial count of 0, and <code>expect(...).toBeInTheDocument()</code> asserts that it is present. The <code>getByText</code> query will throw an error if the text isn’t found, immediately failing the test.</p>
<p>For interactive tests, we create a <code>user</code> with <code>const user = userEvent.setup()</code> and then call <code>await user.click(...)</code> on the increment/decrement/reset buttons. The <code>userEvent.click</code> method simulates a real user click (dispatching the sequence of events a browser would fire). We locate buttons by their accessible role and name (for example, <code>getByRole('button', { name: /increment/i })</code>), following best practices for accessible queries.</p>
<p>After each click, we assert that the DOM updates accordingly (for example, the count text changes to “Count: 1”). Using <code>async/await</code> with <code>user.click</code> ensures the test waits for any state changes. In this way, each test checks the user-visible behavior: that clicking the Increment button increases the count, the Decrement button decreases it, and the Reset button sets it back to zero, without depending on the component’s internal implementation.</p>
<h2 id="heading-how-to-test-user-interactions">How to Test User Interactions</h2>
<p>User interactions are a critical part of testing React applications. The <code>@testing-library/user-event</code> library provides a more realistic simulation of user behaviour than simple event dispatching.</p>
<h3 id="heading-testing-form-inputs">Testing Form Inputs</h3>
<p>Create a <code>LoginForm.jsx</code> component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">LoginForm</span>(<span class="hljs-params">{ onSubmit }</span>) </span>{
  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);
  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    e.preventDefault();

    <span class="hljs-keyword">if</span> (!email || !password) {
      setError(<span class="hljs-string">'Both fields are required'</span>);
      <span class="hljs-keyword">return</span>;
    }

    setError(<span class="hljs-string">''</span>);
    onSubmit({ email, password });
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
          <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
          <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
        /&gt;
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"password"</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
          <span class="hljs-attr">id</span>=<span class="hljs-string">"password"</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
          <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
        /&gt;
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {error &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"alert"</span>&gt;</span>{error}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>}
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Log In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
}
</code></pre>
<p>Create the test file <code>LoginForm.test.jsx</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;
<span class="hljs-keyword">import</span> userEvent <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/user-event'</span>;
<span class="hljs-keyword">import</span> { LoginForm } <span class="hljs-keyword">from</span> <span class="hljs-string">'./LoginForm'</span>;

describe(<span class="hljs-string">'LoginForm Component'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should render email and password inputs'</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">LoginForm</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{()</span> =&gt;</span> {}} /&gt;</span>);

    expect(screen.getByLabelText(<span class="hljs-regexp">/email/i</span>)).toBeInTheDocument();
    expect(screen.getByLabelText(<span class="hljs-regexp">/password/i</span>)).toBeInTheDocument();
  });

  it(<span class="hljs-string">'should update input values when user types'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = userEvent.setup();
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">LoginForm</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{()</span> =&gt;</span> {}} /&gt;</span>);

    <span class="hljs-keyword">const</span> emailInput = screen.getByLabelText(<span class="hljs-regexp">/email/i</span>);
    <span class="hljs-keyword">const</span> passwordInput = screen.getByLabelText(<span class="hljs-regexp">/password/i</span>);

    <span class="hljs-keyword">await</span> user.type(emailInput, <span class="hljs-string">'test@example.com'</span>);
    <span class="hljs-keyword">await</span> user.type(passwordInput, <span class="hljs-string">'password123'</span>);

    expect(emailInput).toHaveValue(<span class="hljs-string">'test@example.com'</span>);
    expect(passwordInput).toHaveValue(<span class="hljs-string">'password123'</span>);
  });

  it(<span class="hljs-string">'should show error when form is submitted empty'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = userEvent.setup();
    <span class="hljs-keyword">const</span> mockSubmit = vi.fn();
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">LoginForm</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{mockSubmit}</span> /&gt;</span></span>);

    <span class="hljs-keyword">const</span> submitButton = screen.getByRole(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/log in/i</span> });
    <span class="hljs-keyword">await</span> user.click(submitButton);

    expect(screen.getByRole(<span class="hljs-string">'alert'</span>)).toHaveTextContent(<span class="hljs-string">'Both fields are required'</span>);
    expect(mockSubmit).not.toHaveBeenCalled();
  });

  it(<span class="hljs-string">'should call onSubmit with form data when valid'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = userEvent.setup();
    <span class="hljs-keyword">const</span> mockSubmit = vi.fn();
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">LoginForm</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{mockSubmit}</span> /&gt;</span></span>);

    <span class="hljs-keyword">await</span> user.type(screen.getByLabelText(<span class="hljs-regexp">/email/i</span>), <span class="hljs-string">'test@example.com'</span>);
    <span class="hljs-keyword">await</span> user.type(screen.getByLabelText(<span class="hljs-regexp">/password/i</span>), <span class="hljs-string">'password123'</span>);
    <span class="hljs-keyword">await</span> user.click(screen.getByRole(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/log in/i</span> }));

    expect(mockSubmit).toHaveBeenCalledWith({
      <span class="hljs-attr">email</span>: <span class="hljs-string">'test@example.com'</span>,
      <span class="hljs-attr">password</span>: <span class="hljs-string">'password123'</span>,
    });
  });
});
</code></pre>
<p>The LoginForm tests similarly use <code>render</code> and <code>screen</code> to interact with the component. We use <code>screen.getByLabelText(/email/i)</code> and <code>screen.getByLabelText(/password/i)</code> to find the input fields by their associated labels, mimicking how users identify form fields.</p>
<p>To simulate typing, we use <code>await user.type(input, text)</code>, which sends real keyboard events to the input (via user-event). After typing, we assert the input’s value with <code>expect(input).toHaveValue(...)</code> (a custom matcher from jest-dom).</p>
<p>When submitting the form empty, clicking the <strong>Log In</strong> button triggers the form’s validation and displays an error message. We find this error by querying <code>getByRole('alert')</code> and check its text content. We also assert that the mock <code>onSubmit</code> handler was <em>not</em> called.</p>
<p>In the valid submission test, we fill both fields and click <strong>Log In</strong>; then <code>expect(mockSubmit).toHaveBeenCalledWith({...})</code> verifies the submit handler received the correct <code>{ email, password }</code> object.</p>
<p>These tests focus on user actions and outcomes: typing and clicking drive the form logic, and our assertions confirm the expected outputs (visible error text or the callback arguments).</p>
<h2 id="heading-how-to-test-custom-hooks">How to Test Custom Hooks</h2>
<p>Custom hooks encapsulate reusable logic, and they need testing just like components. React Testing Library provides a <code>renderHook</code> function specifically for this purpose.</p>
<h3 id="heading-creating-and-testing-a-custom-hook">Creating and Testing a Custom Hook</h3>
<p>Create a custom hook <code>useFetch.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useFetch</span>(<span class="hljs-params">url</span>) </span>{
  <span class="hljs-keyword">const</span> [data, setData] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        setLoading(<span class="hljs-literal">true</span>);
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url);

        <span class="hljs-keyword">if</span> (!response.ok) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Network response was not ok'</span>);
        }

        <span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> response.json();
        setData(json);
        setError(<span class="hljs-literal">null</span>);
      } <span class="hljs-keyword">catch</span> (err) {
        setError(err.message);
        setData(<span class="hljs-literal">null</span>);
      } <span class="hljs-keyword">finally</span> {
        setLoading(<span class="hljs-literal">false</span>);
      }
    };

    fetchData();
  }, [url]);

  <span class="hljs-keyword">return</span> { data, loading, error };
}
</code></pre>
<p>Create the test file <code>useFetch.test.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { renderHook, waitFor } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;
<span class="hljs-keyword">import</span> { useFetch } <span class="hljs-keyword">from</span> <span class="hljs-string">'./useFetch'</span>;

describe(<span class="hljs-string">'useFetch Hook'</span>, <span class="hljs-function">() =&gt;</span> {
  beforeEach(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">global</span>.fetch = vi.fn();
  });

  afterEach(<span class="hljs-function">() =&gt;</span> {
    vi.restoreAllMocks();
  });

  it(<span class="hljs-string">'should return loading state initially'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">global</span>.fetch.mockImplementation(<span class="hljs-function">() =&gt;</span> 
      <span class="hljs-built_in">Promise</span>.resolve({
        <span class="hljs-attr">ok</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">json</span>: <span class="hljs-keyword">async</span> () =&gt; ({ <span class="hljs-attr">data</span>: <span class="hljs-string">'test'</span> }),
      })
    );

    <span class="hljs-keyword">const</span> { result } = renderHook(<span class="hljs-function">() =&gt;</span> useFetch(<span class="hljs-string">'https://api.example.com/data'</span>));

    expect(result.current.loading).toBe(<span class="hljs-literal">true</span>);
    expect(result.current.data).toBe(<span class="hljs-literal">null</span>);
    expect(result.current.error).toBe(<span class="hljs-literal">null</span>);
  });

  it(<span class="hljs-string">'should return data when fetch succeeds'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> mockData = { <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">title</span>: <span class="hljs-string">'Test Post'</span> };

    <span class="hljs-built_in">global</span>.fetch.mockImplementation(<span class="hljs-function">() =&gt;</span>
      <span class="hljs-built_in">Promise</span>.resolve({
        <span class="hljs-attr">ok</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">json</span>: <span class="hljs-keyword">async</span> () =&gt; mockData,
      })
    );

    <span class="hljs-keyword">const</span> { result } = renderHook(<span class="hljs-function">() =&gt;</span> useFetch(<span class="hljs-string">'https://api.example.com/posts/1'</span>));

    <span class="hljs-keyword">await</span> waitFor(<span class="hljs-function">() =&gt;</span> expect(result.current.loading).toBe(<span class="hljs-literal">false</span>));

    expect(result.current.data).toEqual(mockData);
    expect(result.current.error).toBe(<span class="hljs-literal">null</span>);
  });

  it(<span class="hljs-string">'should return error when fetch fails'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-built_in">global</span>.fetch.mockImplementation(<span class="hljs-function">() =&gt;</span>
      <span class="hljs-built_in">Promise</span>.resolve({
        <span class="hljs-attr">ok</span>: <span class="hljs-literal">false</span>,
      })
    );

    <span class="hljs-keyword">const</span> { result } = renderHook(<span class="hljs-function">() =&gt;</span> useFetch(<span class="hljs-string">'https://api.example.com/posts/1'</span>));

    <span class="hljs-keyword">await</span> waitFor(<span class="hljs-function">() =&gt;</span> expect(result.current.loading).toBe(<span class="hljs-literal">false</span>));

    expect(result.current.data).toBe(<span class="hljs-literal">null</span>);
    expect(result.current.error).toBe(<span class="hljs-string">'Network response was not ok'</span>);
  });
});
</code></pre>
<p>The <code>renderHook</code> function from React Testing Library renders custom hooks, and <code>waitFor</code> is used to wait for asynchronous state updates in the hook.</p>
<h2 id="heading-how-to-mock-api-calls">How to Mock API Calls</h2>
<p>When testing components that make API calls, you don't want to hit real endpoints. Mocking ensures your tests are fast, reliable, and don't depend on network conditions.</p>
<h3 id="heading-mocking-with-vitest">Mocking with Vitest</h3>
<p>Vitest doesn’t auto-mock modules like Jest does, so you need to manually mock them. Let's see how to mock an Axios call.</p>
<p>Create a <code>PostsList.jsx</code> component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">PostsList</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [posts, setPosts] = useState([]);
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchPosts = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'https://api.example.com/posts'</span>);
        setPosts(response.data);
      } <span class="hljs-keyword">catch</span> (err) {
        setError(err.message);
      } <span class="hljs-keyword">finally</span> {
        setLoading(<span class="hljs-literal">false</span>);
      }
    };

    fetchPosts();
  }, []);

  <span class="hljs-keyword">if</span> (loading) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Error: {error}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
      {posts.map((post) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{post.id}</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span>
  );
}
</code></pre>
<p>Create the test file <code>PostsList.test.jsx</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { render, screen, waitFor } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> { PostsList } <span class="hljs-keyword">from</span> <span class="hljs-string">'./PostsList'</span>;

vi.mock(<span class="hljs-string">'axios'</span>);

describe(<span class="hljs-string">'PostsList Component'</span>, <span class="hljs-function">() =&gt;</span> {
  beforeEach(<span class="hljs-function">() =&gt;</span> {
    vi.clearAllMocks();
  });

  it(<span class="hljs-string">'should display loading state initially'</span>, <span class="hljs-function">() =&gt;</span> {
    axios.get.mockImplementation(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">() =&gt;</span> {}));
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">PostsList</span> /&gt;</span></span>);

    expect(screen.getByText(<span class="hljs-string">'Loading...'</span>)).toBeInTheDocument();
  });

  it(<span class="hljs-string">'should display posts when API call succeeds'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> mockPosts = [
      { <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">title</span>: <span class="hljs-string">'First Post'</span> },
      { <span class="hljs-attr">id</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">title</span>: <span class="hljs-string">'Second Post'</span> },
    ];

    axios.get.mockResolvedValue({ <span class="hljs-attr">data</span>: mockPosts });
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">PostsList</span> /&gt;</span></span>);

    <span class="hljs-keyword">await</span> waitFor(<span class="hljs-function">() =&gt;</span> {
      expect(screen.queryByText(<span class="hljs-string">'Loading...'</span>)).not.toBeInTheDocument();
    });

    expect(screen.getByText(<span class="hljs-string">'First Post'</span>)).toBeInTheDocument();
    expect(screen.getByText(<span class="hljs-string">'Second Post'</span>)).toBeInTheDocument();
  });

  it(<span class="hljs-string">'should display error when API call fails'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    axios.get.mockRejectedValue(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Network error'</span>));
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">PostsList</span> /&gt;</span></span>);

    <span class="hljs-keyword">await</span> waitFor(<span class="hljs-function">() =&gt;</span> {
      expect(screen.queryByText(<span class="hljs-string">'Loading...'</span>)).not.toBeInTheDocument();
    });

    expect(screen.getByText(<span class="hljs-regexp">/error/i</span>)).toBeInTheDocument();
  });
});
</code></pre>
<p>In these tests, we verify specific UI states: the “loading” test checks that a loading indicator shows while data is being fetched, the “success” test confirms that post items render when the API returns data, and the “error” test makes sure an error message appears if the call fails.</p>
<p>We mock Axios by calling <code>vi.mock('axios')</code> and then using methods like <code>mockResolvedValue(...)</code> on <code>axios.get</code> to simulate a successful response (and <code>mockRejectedValue(...)</code> to simulate a failure). This kind of mocking isolates our tests from real network calls (making them fast and reliable) and lets us control exactly what data or error the hook receives.</p>
<p>We use <code>await waitFor(...)</code> to pause the test until those asynchronous updates complete before making assertions. Finally, we use <code>screen.getByText(...)</code> to find elements that should be present (it will throw an error if they’re missing) and <code>screen.queryByText(...)</code> to check that elements aren’t present (it returns null if the element is not in the DOM).</p>
<h3 id="heading-mocking-specific-module-functions">Mocking Specific Module Functions</h3>
<p>Sometimes you only want to mock specific functions while keeping the rest of a module's behaviour intact. Here's how to do that:</p>
<pre><code class="lang-javascript">vi.mock(<span class="hljs-string">'date-fns'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> original = <span class="hljs-keyword">await</span> vi.importActual(<span class="hljs-string">'date-fns'</span>);
  <span class="hljs-keyword">return</span> {
    ...original,
    <span class="hljs-attr">format</span>: vi.fn(<span class="hljs-function">() =&gt;</span> <span class="hljs-string">'2025-01-01'</span>),
  };
});
</code></pre>
<p>In Vitest, you use <code>vi.importActual</code> to retain all original methods while mocking only the <code>format</code> method.</p>
<h2 id="heading-best-practices-for-testing-react-components">Best Practices for Testing React Components</h2>
<p>Now that you know how to write tests, let's talk about how to write good tests.</p>
<h3 id="heading-test-user-behaviour-not-implementation">Test User Behaviour, Not Implementation</h3>
<p>Focus on testing what users see and do, not internal component details. If you refactor your component's implementation without changing its behaviour, your tests shouldn't break.</p>
<p><strong>Bad test (testing implementation):</strong></p>
<pre><code class="lang-javascript">it(<span class="hljs-string">'should set isOpen state to true'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { result } = renderHook(<span class="hljs-function">() =&gt;</span> useState(<span class="hljs-literal">false</span>));
  <span class="hljs-comment">// Testing internal state directly</span>
});
</code></pre>
<p><strong>Good test (testing behaviour):</strong></p>
<pre><code class="lang-javascript">it(<span class="hljs-string">'should show menu when button is clicked'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> user = userEvent.setup();
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Menu</span> /&gt;</span></span>);

  <span class="hljs-keyword">await</span> user.click(screen.getByRole(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/menu/i</span> }));
  expect(screen.getByRole(<span class="hljs-string">'navigation'</span>)).toBeVisible();
});
</code></pre>
<h3 id="heading-use-accessible-queries">Use Accessible Queries</h3>
<p>React Testing Library encourages you to query elements the way users do. Prefer queries that mirror user interaction:</p>
<ol>
<li><p><code>getByRole</code> (best for interactive elements)</p>
</li>
<li><p><code>getByLabelText</code> (for form fields)</p>
</li>
<li><p><code>getByPlaceholderText</code></p>
</li>
<li><p><code>getByText</code></p>
</li>
<li><p><code>getByTestId</code> (last resort)</p>
</li>
</ol>
<h3 id="heading-keep-tests-simple-and-focused">Keep Tests Simple and Focused</h3>
<p>Each test should verify one thing. If your test needs a lot of setup or has many assertions, consider splitting it into multiple tests.</p>
<h3 id="heading-clean-up-between-tests">Clean Up Between Tests</h3>
<p>Use <code>afterEach</code> to clean up the DOM after each test run, ensuring tests don't interfere with each other. This is already handled if you followed the setup steps earlier.</p>
<h3 id="heading-use-descriptive-test-names">Use Descriptive Test Names</h3>
<p>Test names should clearly describe what they're testing and what the expected outcome is.</p>
<p>Good test names:</p>
<pre><code class="lang-javascript">it(<span class="hljs-string">'should display error message when form is submitted empty'</span>);
it(<span class="hljs-string">'should call onSubmit with email and password when form is valid'</span>);
it(<span class="hljs-string">'should disable submit button while request is pending'</span>);
</code></pre>
<h3 id="heading-mock-external-dependencies">Mock External Dependencies</h3>
<p>Always mock API calls, timers, and other external dependencies. Your tests should be isolated and not depend on network conditions or external services.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Now, you have learned how to set up Vitest in a React project and write effective tests for components, user interactions, custom hooks, and API calls. Vitest provides a powerful and efficient way to test React applications, especially when combined with modern tools like Vite.</p>
<p>Testing is about building confidence in your code, documenting expected behaviour, and enabling safe refactoring. Vitest's speed makes testing feel less like a chore and more like a natural part of development.</p>
<p>Start small. Add tests for critical user flows. Test the components that change frequently. As you build the habit, you will find that tests actually make development faster, not slower. The code will still be there tomorrow. But the bugs you catch today won't be.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
