<?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[ Aiyedogbon Abraham - 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[ Aiyedogbon Abraham - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 29 Jun 2026 16:28:38 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/abrahamaiyedogbon/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How Attribute-Based Access Control Helps You Write Better Authorization Rules ]]>
                </title>
                <description>
                    <![CDATA[ Every application that handles user data eventually hits the same problem: not all users should see the same things. A junior nurse should not be able to access every patient record in the hospital. A ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-attribute-based-access-control-helps-you-write-better-authorization-rules/</link>
                <guid isPermaLink="false">6a21b44e09761aac249579f9</guid>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ access control ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aiyedogbon Abraham ]]>
                </dc:creator>
                <pubDate>Thu, 04 Jun 2026 17:22:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/1bcd9989-cf38-4375-a0ed-03cf1bd3c3b8.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every application that handles user data eventually hits the same problem: not all users should see the same things.</p>
<p>A junior nurse should not be able to access every patient record in the hospital. A contractor should not be able to read internal financial reports. An employee logged in from an unrecognized device at 2AM probably should not be editing production configuration files.</p>
<p>Simple role-based systems handle obvious cases well. But as applications grow and access rules become more nuanced, those systems start to crack. You end up creating more and more specific roles, like <code>finance_viewer</code>, <code>finance_viewer_us_only</code>, <code>finance_viewer_us_only_readonly</code>, until the roles themselves become unmanageable.</p>
<p>Attribute-Based Access Control (ABAC) was designed to solve exactly this problem. It shifts from "what role does this user have?" to "what do we know about this user, this resource, and this situation?" and makes access decisions based on all of those factors together.</p>
<p>In this guide, you'll learn how ABAC works, how it evolved from earlier access control models, how policies are structured, how to implement it in code, and when to use it.</p>
<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-how-access-control-has-evolved">How Access Control Has Evolved</a></p>
</li>
<li><p><a href="#heading-what-is-attribute-based-access-control">What is Attribute-Based Access Control?</a></p>
</li>
<li><p><a href="#heading-the-four-building-blocks-of-abac">The Four Building Blocks of ABAC</a></p>
</li>
<li><p><a href="#heading-how-an-abac-decision-is-made">How an ABAC Decision is Made</a></p>
</li>
<li><p><a href="#heading-how-to-write-abac-policies">How to Write ABAC Policies</a></p>
</li>
<li><p><a href="#heading-how-to-implement-abac-in-code">How to Implement ABAC in Code</a></p>
</li>
<li><p><a href="#heading-abac-vs-rbac-when-to-use-which">ABAC vs RBAC: When to Use Which</a></p>
</li>
<li><p><a href="#heading-real-world-use-cases">Real-World Use Cases</a></p>
</li>
<li><p><a href="#heading-enterprise-abac-considerations">Enterprise ABAC Considerations</a></p>
</li>
<li><p><a href="#heading-limitations-and-challenges">Limitations and Challenges</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-glossary">Glossary</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To get the most from this article, you should have:</p>
<ul>
<li><p>A basic understanding of web authentication (logins, sessions, tokens)</p>
</li>
<li><p>Familiarity with how users and resources relate in applications</p>
</li>
<li><p>Some experience reading JavaScript or pseudocode</p>
</li>
</ul>
<p>No prior knowledge of access control theory is required.</p>
<h2 id="heading-how-access-control-has-evolved">How Access Control Has Evolved</h2>
<p>To understand why ABAC exists, it helps to understand what came before it and why each generation fell short.</p>
<h3 id="heading-discretionary-and-mandatory-access-control">Discretionary and Mandatory Access Control</h3>
<p>Early access control models emerged from Department of Defense applications in the 1960s and 1970s. According to NIST Special Publication 800-162, these were Discretionary Access Control (DAC) and Mandatory Access Control (MAC).</p>
<p>In DAC, the owner of a resource decides who can access it. Think of a file on your computer where you choose who can read or edit it. In MAC, access is governed by a central authority using labels like "Classified" or "Top Secret." The system enforces these labels, not individual owners.</p>
<p>Both worked for their original purposes but didn't scale well to the complexity of modern networked systems.</p>
<h3 id="heading-identity-based-access-control-and-access-control-lists">Identity-Based Access Control and Access Control Lists</h3>
<p>As networks grew, identity-based access control (IBAC) became common. The most familiar implementation is the Access Control List (ACL), a list of users or groups attached to a resource, specifying what each can do.</p>
<p>ACLs are simple and transparent, but they create a management burden as systems grow. Every new user needs to be added to every relevant list. Every permission change means hunting through lists across multiple resources. And when someone leaves the organization, you need to find and remove them everywhere.</p>
<p>Failure to do this consistently leads to users accumulating privileges they should no longer have.</p>
<h3 id="heading-role-based-access-control">Role-Based Access Control</h3>
<p>Role-Based Access Control (RBAC) was a major step forward. Instead of assigning permissions directly to users, RBAC assigns them to roles. Users are then assigned roles. A hospital might have roles like <code>nurse</code>, <code>doctor</code>, <code>admin</code>, and <code>billing_staff</code>, each with different permissions.</p>
<p>This made administration much more manageable. Adding a new employee means assigning them appropriate roles. Removing an employee means removing their roles. Changing what nurses can do means updating the nurse role once.</p>
<p>RBAC became widely adopted and is still the right choice for many applications. But it has a structural weakness: as permission requirements become more granular, you have to create more specific roles. A nurse who can only see patients on their floor, only during their shift, or only for certain record types, needs a very specific role, or a combination of roles that interacts in complicated ways.</p>
<p>This proliferation is called "role explosion." The roles multiply until they are as difficult to manage as the individual permissions RBAC was supposed to replace.</p>
<h3 id="heading-attribute-based-access-control">Attribute-Based Access Control</h3>
<p>ABAC emerged as a response to role explosion. Instead of assigning roles that bundle fixed permissions, ABAC evaluates the actual characteristics of the user, the resource, and the context at the moment of every access request.</p>
<p>A nurse gets access to a patient record not because they have the <code>nurse</code> role, but because their job title is "Nurse Practitioner," the patient is on their assigned floor, it's currently their shift, and the record type is within their scope of care. Change any of those facts, and the access decision changes accordingly.</p>
<p>As NIST SP 800-162 defines it, ABAC is:</p>
<blockquote>
<p>"an access control method where subject requests to perform operations on objects are granted or denied based on assigned attributes of the subject, assigned attributes of the object, environment conditions, and a set of policies that are specified in terms of those attributes and conditions."</p>
</blockquote>
<h2 id="heading-what-is-attribute-based-access-control">What is Attribute-Based Access Control?</h2>
<p>ABAC is a logical access control model where every access decision is made by evaluating a set of rules against the current values of attributes. Nothing is pre-computed or cached in role assignments. Every time a user tries to do something, the system asks: given what we know about this user, this resource, and this moment, should this be allowed?</p>
<p>This makes ABAC highly precise and highly dynamic. Permissions don't accumulate over time. They don't need manual cleanup when someone's role changes. The system simply evaluates the current state of attributes every time.</p>
<p>The model is formally described in NIST's guide to ABAC as being capable of enforcing both Discretionary Access Control and Mandatory Access Control concepts, making it more expressive than models that only support one or the other.</p>
<p>Companies like Axiomatics, major government agencies, and large enterprises managing cross-organizational data sharing all rely on ABAC for its ability to scale security policies across complex environments.</p>
<h2 id="heading-the-four-building-blocks-of-abac">The Four Building Blocks of ABAC</h2>
<p>Every ABAC system is built from four types of information. Understanding these clearly is the key to understanding how ABAC works.</p>
<h3 id="heading-1-subject-attributes">1. Subject Attributes</h3>
<p>The subject is whoever or whatever is requesting access. This is usually a user, but it can also be a service, an application, or an automated system, what NIST calls a Non-Person Entity (NPE).</p>
<p>Subject attributes describe who the subject is:</p>
<pre><code class="language-plaintext">user.jobTitle         = "Nurse Practitioner"
user.department       = "Cardiology"
user.clearanceLevel   = "Confidential"
user.employmentStatus = "Active"
user.location         = "Floor 3"
user.shiftActive      = true
</code></pre>
<p>These attributes are typically sourced from an identity provider, HR system, or user directory. They're facts about the user that can be used in policies.</p>
<h3 id="heading-2-object-attributes-resource-attributes">2. Object Attributes (Resource Attributes)</h3>
<p>The object is whatever the subject is trying to access. This could be a file, a database record, an API endpoint, a service, or any other protected resource.</p>
<p>Object attributes describe what the resource is:</p>
<pre><code class="language-plaintext">record.type           = "PatientMedical"
record.floor          = "Floor 3"
record.sensitivity    = "High"
record.owner          = "Dr. Williams"
record.department     = "Cardiology"
</code></pre>
<p>Object attributes are typically assigned when a resource is created and updated throughout its lifecycle. They're facts about the resource that determine who should be able to access it.</p>
<h3 id="heading-3-action-attributes">3. Action Attributes</h3>
<p>The action is what the subject is trying to do to the object. Common actions include read, write, edit, delete, copy, execute, and share.</p>
<p>In many ABAC implementations, the action itself has attributes:</p>
<pre><code class="language-plaintext">action.type           = "read"
action.bulk           = false
</code></pre>
<p>Policies can restrict which actions are allowed independently of the other attributes. A user might be able to read a document but not delete it, even if all their other attributes match.</p>
<h3 id="heading-4-environment-conditions">4. Environment Conditions</h3>
<p>Environment conditions are contextual factors that don't belong to either the subject or the object, but that should influence the access decision. NIST describes these as "dynamic factors, independent of subject and object, that may be used as attributes at decision time to influence an access decision."</p>
<p>Examples include:</p>
<pre><code class="language-plaintext">environment.time           = "14:30"
environment.dayOfWeek      = "Wednesday"
environment.userLocation   = "Corporate Office"
environment.ipAddress      = "192.168.1.10"
environment.deviceStatus   = "compliant"
environment.threatLevel    = "low"
</code></pre>
<p>Environment conditions are what make ABAC truly dynamic. The same user, the same resource, and the same action might be allowed during business hours on a trusted device but denied at midnight from an unknown IP address.</p>
<h2 id="heading-how-an-abac-decision-is-made">How an ABAC Decision is Made</h2>
<p>When a subject tries to perform an action on an object, the ABAC system runs through a specific process:</p>
<h3 id="heading-step-1-collect-attributes">Step 1: Collect Attributes</h3>
<p>The system gathers current attributes for the subject, object, action, and environment. This might involve querying a user directory, reading resource metadata, and checking current time and location.</p>
<h3 id="heading-step-2-find-applicable-policies">Step 2: Find Applicable Policies</h3>
<p>The system identifies which policies apply to this particular request. A request to read a patient record might have several policies that apply: one about clinical staff access, one about after-hours access, and one about record sensitivity levels.</p>
<h3 id="heading-step-3-evaluate-each-policy">Step 3: Evaluate Each Policy</h3>
<p>Each applicable policy evaluates the collected attributes and returns permit or deny.</p>
<h3 id="heading-step-4-reconcile-conflicts">Step 4: Reconcile Conflicts</h3>
<p>If multiple policies apply and they conflict, the system uses predefined combining rules. Common approaches are "deny overrides" (if any policy says deny, the request is denied) or "permit overrides" (if any policy says permit, the request is permitted).</p>
<h3 id="heading-step-5-enforce-the-decision">Step 5: Enforce the Decision</h3>
<p>The system grants or denies access based on the final decision.</p>
<p>This process happens every time an access request is made. There's no caching of role assignments or pre-computed permission tables. The decision reflects the current state of all attributes at the moment of the request.</p>
<h2 id="heading-how-to-write-abac-policies">How to Write ABAC Policies</h2>
<p>Policies are the logic at the heart of ABAC. They're written as conditional rules that reference attributes. A well-written policy reads like a business rule, because that's exactly what it is.</p>
<h3 id="heading-simple-boolean-policy">Simple Boolean Policy</h3>
<p>The most basic form evaluates whether certain attributes match:</p>
<pre><code class="language-javascript">// Policy: Only active employees can access internal resources
function canAccessInternalResource(user) {
  return user.employmentStatus === "Active";
}
</code></pre>
<p><strong>What this does:</strong> Checks a single attribute, employment status, before allowing access. Any inactive, suspended, or terminated user is denied, regardless of their roles or past access history.</p>
<h3 id="heading-multi-attribute-policy">Multi-Attribute Policy</h3>
<p>Real policies typically combine multiple attributes:</p>
<pre><code class="language-javascript">// Policy: A nurse can read a patient record
// if the patient is on their assigned floor
// and during their active shift

function canReadPatientRecord(user, record, environment) {
  const isNurse = user.jobTitle === "Nurse Practitioner";
  const isAssignedFloor = user.assignedFloor === record.floor;
  const isActiveDuty = user.shiftActive === true;

  return isNurse &amp;&amp; isAssignedFloor &amp;&amp; isActiveDuty;
}
</code></pre>
<p><strong>What this does:</strong> Combines three conditions using AND logic. All three must be true for access to be granted. Change the nurse's floor assignment, and they immediately lose access to records on the previous floor, without any manual intervention.</p>
<h3 id="heading-environment-aware-policy">Environment-Aware Policy</h3>
<p>Adding environment conditions makes policies context-sensitive:</p>
<pre><code class="language-javascript">// Policy: Users can only access sensitive financial records
// during business hours from the corporate network

function canAccessSensitiveFinancialRecord(user, record, environment) {
  const isFinanceStaff = user.department === "Finance";
  const isHighSensitivity = record.sensitivity === "High";
  
  // If this is a high-sensitivity record, apply time and location controls
  if (isHighSensitivity) {
    const currentHour = new Date(environment.timestamp).getHours();
    const isBusinessHours = currentHour &gt;= 9 &amp;&amp; currentHour &lt; 17;
    const isCorporateNetwork = environment.ipRange === "corporate";

    return isFinanceStaff &amp;&amp; isBusinessHours &amp;&amp; isCorporateNetwork;
  }

  // Lower sensitivity records only require finance department membership
  return isFinanceStaff;
}
</code></pre>
<p><strong>What this does:</strong> Applies stricter controls to higher-sensitivity resources. The same user gets access to low-sensitivity records at any time, but high-sensitivity records require them to be on the corporate network during business hours. The policy logic mirrors the actual business rule: sensitive data needs more protection.</p>
<h3 id="heading-ownership-based-policy">Ownership-Based Policy</h3>
<p>ABAC can also implement discretionary ownership rules:</p>
<pre><code class="language-javascript">// Policy: A user can edit a document
// if they own it, or if they have editor permissions
// and the document isn't locked

function canEditDocument(user, document, action) {
  const isOwner = document.ownerId === user.id;
  const hasEditorPermission = user.permissions.includes("document.edit");
  const isUnlocked = document.status !== "locked";

  return (isOwner || hasEditorPermission) &amp;&amp; isUnlocked;
}
</code></pre>
<p><strong>What this does:</strong> Combines ownership (an attribute of the relationship between user and document) with explicit permissions and resource state. An editor can't edit a locked document even if they have the edit permission. An owner can edit their own documents but not locked ones.</p>
<h2 id="heading-how-to-implement-abac-in-code">How to Implement ABAC in Code</h2>
<p>Let's build a simple ABAC evaluation engine that puts these pieces together.</p>
<h3 id="heading-step-1-define-the-attribute-structure">Step 1: Define the Attribute Structure</h3>
<p>First, define clear data structures for your attributes:</p>
<pre><code class="language-javascript">// A user (subject) requesting access
const user = {
  id: "user-123",
  name: "Sarah Chen",
  department: "Cardiology",
  jobTitle: "Nurse Practitioner",
  clearanceLevel: 2,
  assignedFloor: "Floor 3",
  shiftActive: true,
  employmentStatus: "Active"
};

// A resource (object) being accessed
const patientRecord = {
  id: "record-456",
  type: "PatientMedical",
  floor: "Floor 3",
  sensitivity: 2,
  ownerId: "doctor-789",
  department: "Cardiology"
};

// Environment conditions
const environment = {
  timestamp: new Date().toISOString(),
  ipAddress: "10.0.1.25",
  ipRange: "corporate",
  deviceCompliant: true
};
</code></pre>
<h3 id="heading-step-2-write-policy-functions">Step 2: Write Policy Functions</h3>
<p>Write individual policies as pure functions that take attributes and return boolean values:</p>
<pre><code class="language-javascript">// policies/patientRecord.js

// Policy 1: User must be active and clinical staff
function isClinicalStaff(user) {
  const clinicalTitles = [
    "Nurse Practitioner",
    "Physician",
    "Resident",
    "Medical Assistant"
  ];
  return (
    user.employmentStatus === "Active" &amp;&amp;
    clinicalTitles.includes(user.jobTitle)
  );
}

// Policy 2: Record must be within the user's assigned area
function isAssignedToRecord(user, record) {
  return (
    user.department === record.department &amp;&amp;
    user.assignedFloor === record.floor
  );
}

// Policy 3: User must be on active shift
function isOnActiveShift(user) {
  return user.shiftActive === true;
}

// Policy 4: High-sensitivity records require compliant devices
function meetsDeviceRequirements(record, environment) {
  if (record.sensitivity &gt;= 3) {
    return environment.deviceCompliant === true;
  }
  return true; // No device requirement for lower sensitivity
}
</code></pre>
<p><strong>What this does:</strong> Each policy is a small, focused function. This makes policies easy to test individually, easy to read, and easy to reuse across different access decisions. A policy for "is this user clinical staff" can be applied to many different resource types.</p>
<h3 id="heading-step-3-build-an-evaluation-engine">Step 3: Build an Evaluation Engine</h3>
<p>Combine your policies into a decision engine:</p>
<pre><code class="language-javascript">// abac/engine.js

function evaluateAccess(user, resource, action, environment, policies) {
  // Collect all policy results
  const results = policies.map(policy =&gt; {
    try {
      return policy(user, resource, action, environment);
    } catch (error) {
      console.error(`Policy evaluation error: ${error.message}`);
      return false; // Fail closed: deny on error
    }
  });

  // Deny-overrides: if any policy denies, access is denied
  return results.every(result =&gt; result === true);
}

// Assemble policies for reading patient records
const readPatientRecordPolicies = [
  (user) =&gt; isClinicalStaff(user),
  (user, record) =&gt; isAssignedToRecord(user, record),
  (user) =&gt; isOnActiveShift(user),
  (user, record, action, environment) =&gt; meetsDeviceRequirements(record, environment)
];

// Make an access decision
const canRead = evaluateAccess(
  user,
  patientRecord,
  "read",
  environment,
  readPatientRecordPolicies
);

console.log(`Access ${canRead ? "granted" : "denied"}`);
// → Access granted (all conditions met)
</code></pre>
<p><strong>What this does:</strong> The engine loops through each policy function, passing in the relevant attributes. If all policies return true, access is granted. If any returns false, access is denied. This is called "deny-overrides combining". The <code>try-catch</code> ensures that if a policy throws an error, access is denied rather than granted, following the security principle of fail-closed.</p>
<h3 id="heading-step-4-add-attribute-collection">Step 4: Add Attribute Collection</h3>
<p>In a real application, attributes come from multiple sources:</p>
<pre><code class="language-javascript">// attributes/collector.js

async function collectAttributes(userId, resourceId) {
  // Collect in parallel for performance
  const [user, resource, environment] = await Promise.all([
    fetchUserAttributes(userId),      // From identity provider or HR system
    fetchResourceAttributes(resourceId), // From resource metadata store
    collectEnvironmentConditions()    // Time, IP, device status
  ]);

  return { user, resource, environment };
}

async function fetchUserAttributes(userId) {
  // This would query your user directory, LDAP, or identity provider
  const user = await userDirectory.findById(userId);
  const shift = await shiftService.getActiveShift(userId);
  
  return {
    ...user,
    shiftActive: shift !== null,
    assignedFloor: shift?.floor || null
  };
}

async function collectEnvironmentConditions() {
  return {
    timestamp: new Date().toISOString(),
    ipAddress: request.ip,
    ipRange: await networkService.classifyIP(request.ip),
    deviceCompliant: await deviceService.checkCompliance(request.deviceId)
  };
}
</code></pre>
<p><strong>What this does:</strong> Attribute collection is separated from policy evaluation. This is an important design decision: it means you can test policies with any attribute values without needing real users or resources. It also means you can swap out the source of attributes (say, moving from an on-premise directory to a cloud identity provider) without changing your policies.</p>
<h3 id="heading-step-5-integrate-with-your-api">Step 5: Integrate with Your API</h3>
<p>Use the evaluation engine in your API handlers:</p>
<pre><code class="language-javascript">// middleware/abac.js

function requireAccess(action, resourceType) {
  return async (req, res, next) =&gt; {
    try {
      const { user, resource, environment } = await collectAttributes(
        req.user.id,
        req.params.id
      );

      const policies = getPoliciesFor(resourceType, action);
      const allowed = evaluateAccess(user, resource, action, environment, policies);

      if (!allowed) {
        // Log the denial for audit purposes
        auditLog.record({
          userId: req.user.id,
          resourceId: req.params.id,
          action,
          decision: "denied",
          timestamp: new Date()
        });

        return res.status(403).json({ error: "Access denied" });
      }

      next();
    } catch (error) {
      // Fail closed: deny access on unexpected errors
      return res.status(403).json({ error: "Access denied" });
    }
  };
}

// Use in route definitions
app.get(
  "/patient-records/:id",
  authenticate(),                               // First verify identity
  requireAccess("read", "patientRecord"),       // Then evaluate ABAC
  patientRecordController.getById               // Then handle the request
);
</code></pre>
<p><strong>What this does:</strong> The ABAC check lives in middleware that runs between authentication and the route handler. Authentication establishes who the user is. ABAC decides whether that user can do what they're trying to do. This separation keeps authorization logic out of your business logic.</p>
<h2 id="heading-abac-vs-rbac-when-to-use-which">ABAC vs RBAC: When to Use Which</h2>
<p>RBAC isn't obsolete. It's genuinely the right choice for many applications. The question is which model fits your specific access requirements.</p>
<h3 id="heading-rbac-strengths">RBAC Strengths</h3>
<p>RBAC is simple to understand, simple to implement, and simple to audit. If you can describe your access requirements as a list of roles with fixed permissions, RBAC works well. Most SaaS applications start with RBAC and it serves them fine for years.</p>
<p>A typical RBAC check looks like:</p>
<pre><code class="language-javascript">// Simple RBAC: does the user have the required role?
function canAccess(user, requiredRole) {
  return user.roles.includes(requiredRole);
}
</code></pre>
<p>It's fast, clear, and easy to debug. When something goes wrong, you check which roles the user has and which roles the resource requires.</p>
<h3 id="heading-where-rbac-breaks-down">Where RBAC Breaks Down</h3>
<p>RBAC struggles when permissions need to depend on factors that aren't captured by a role. If you need to express "finance managers can view financial records, but only for their own region, and only during business hours," you're outside what a role alone can express cleanly.</p>
<p>You either need an extremely specific role (<code>finance_manager_us_east_business_hours</code>) that creates the role explosion problem, or you add conditional logic to your application code that effectively recreates ABAC, just in a less organized way.</p>
<h3 id="heading-rbac-vs-abac-comparison">RBAC vs ABAC Comparison</h3>
<table>
<thead>
<tr>
<th>Factor</th>
<th>RBAC</th>
<th>ABAC</th>
</tr>
</thead>
<tbody><tr>
<td>Logic</td>
<td>Permissions assigned to roles, roles assigned to users</td>
<td>Policies evaluate attributes at decision time</td>
</tr>
<tr>
<td>Granularity</td>
<td>Coarse-grained</td>
<td>Fine-grained and context-aware</td>
</tr>
<tr>
<td>Flexibility</td>
<td>Low, new rules require new roles</td>
<td>High, update policies without changing roles</td>
</tr>
<tr>
<td>Scalability</td>
<td>Role explosion under complexity</td>
<td>Scales with policy complexity, not role count</td>
</tr>
<tr>
<td>Auditability</td>
<td>Simple, check role assignments</td>
<td>Requires logging attributes at decision time</td>
</tr>
<tr>
<td>Complexity</td>
<td>Low</td>
<td>Higher, more moving parts</td>
</tr>
<tr>
<td>Best for</td>
<td>Simple, stable permission structures</td>
<td>Complex, dynamic, or context-dependent permissions</td>
</tr>
</tbody></table>
<h3 id="heading-combining-both-models">Combining Both Models</h3>
<p>RBAC and ABAC work well together. A common pattern is to use RBAC for coarse-grained access control (which sections of your application can this user see?) and ABAC for fine-grained control within those sections (which specific records can they access?).</p>
<p>For example, a role might grant access to the patient records section of a hospital system. Within that section, ABAC policies determine which specific records a user can view or edit based on their department, assigned floor, and active shift.</p>
<h2 id="heading-real-world-use-cases">Real-World Use Cases</h2>
<h3 id="heading-healthcare-records-management">Healthcare Records Management</h3>
<p>Healthcare is one of the clearest examples of why ABAC matters. Patient privacy regulations require precise access control, and patient care requires that the right staff can access records quickly when they need them.</p>
<p>An ABAC policy in a hospital might allow a nurse to view a patient's record only when:</p>
<ol>
<li><p>the patient is currently admitted to the nurse's assigned floor,</p>
</li>
<li><p>the nurse is on an active shift,</p>
</li>
<li><p>the access occurs from within the hospital network,</p>
</li>
<li><p>and the record type is within the nurse's care scope.</p>
</li>
</ol>
<p>According to WorkOS's ABAC analysis, in emergency situations ABAC systems can automatically expand access rights. For example, an ER doctor automatically gains broader access to patient records to provide immediate care, with this access being time-bound and closely monitored.</p>
<p>All of these rules would require dozens of roles in an RBAC system, and those roles would still struggle to handle the emergency access scenario dynamically.</p>
<h3 id="heading-corporate-data-access">Corporate Data Access</h3>
<p>Large enterprises typically have employees across departments, roles, locations, and clearance levels who need different views of the same underlying data. A document might be accessible to finance managers in the US region during business hours, accessible to executives globally at any time, but inaccessible to contractors entirely.</p>
<p>ABAC expresses all of these rules in policies. As employees change departments, go on leave, or change roles, their attributes update in the identity system and their access changes automatically, with no manual ACL updates required.</p>
<h3 id="heading-government-and-classified-information">Government and Classified Information</h3>
<p>The US federal government's adoption of ABAC is described in NIST SP 800-162, which was developed to address the Federal Identity, Credential, and Access Management (FICAM) requirements. Federal agencies deal with information shared across organizational boundaries, with varying classification levels and need-to-know requirements.</p>
<p>ABAC allows an analyst in one agency to access information from another agency without requiring the second agency to pre-provision an account for them. The analyst's clearance attributes, organizational affiliation, and project assignments are evaluated against the resource's classification and access rules at the time of the request.</p>
<h3 id="heading-multi-tenant-saas-applications">Multi-Tenant SaaS Applications</h3>
<p>SaaS applications that serve multiple organizations need to ensure strict data isolation between tenants while supporting complex permission structures within each tenant.</p>
<p>ABAC handles this naturally. A resource attribute like <code>record.tenantId</code> is evaluated against the user attribute <code>user.tenantId</code>, and no cross-tenant access is possible through policy. Within a tenant, ABAC supports as much complexity as the tenant's policies require.</p>
<h2 id="heading-enterprise-abac-considerations">Enterprise ABAC Considerations</h2>
<p>Deploying ABAC at enterprise scale introduces several challenges that don't exist in smaller implementations.</p>
<h3 id="heading-policy-administration">Policy Administration</h3>
<p>Policies need to be authored, reviewed, tested, and deployed. According to NIST SP 800-162, this requires a Policy Administration Point (PAP), an interface for creating and managing policies. Without proper tooling, policies become difficult to audit and maintain.</p>
<p>In practice, this means treating policies like code: version control, code review, and automated testing.</p>
<h3 id="heading-attribute-quality-and-freshness">Attribute Quality and Freshness</h3>
<p>ABAC is only as good as the attributes it evaluates. If user attributes are stale, for example, for a user who changed departments but whose directory entry hasn't been updated, the access decisions will be wrong.</p>
<p>NIST warns that "attributes that are not refreshed as often will ultimately be less secure than attributes that are refreshed in real time." Building reliable attribute pipelines from authoritative sources is often the hardest part of ABAC deployment.</p>
<h3 id="heading-performance">Performance</h3>
<p>Evaluating policies on every request has a performance cost. Each evaluation may require fetching attributes from multiple sources. To manage this, many implementations use attribute caching, but caching introduces the staleness problem described above.</p>
<p>The solution is to cache with appropriate TTLs (time-to-live values) based on how quickly each attribute type can change. A user's department changes rarely and can be cached for hours. A user's active shift status might change every 8 hours and needs a shorter cache. Real-time location might not be cacheable at all.</p>
<h3 id="heading-audit-logging">Audit Logging</h3>
<p>Because ABAC makes decisions dynamically, auditing requires logging the attributes used in each decision, not just the decision itself. A log entry that says "access denied" is only useful if it also captures why access was denied and which attributes failed to satisfy which policies.</p>
<p>NIST notes that without tracking attribute values at decision time, accountability requirements can't be met.</p>
<h2 id="heading-limitations-and-challenges">Limitations and Challenges</h2>
<p>ABAC is powerful, but it's not the right solution for every access control problem. It's worth being honest about its limitations before committing to an implementation.</p>
<p><strong>Complexity</strong>: According to NIST SP 800-162, "an ABAC system is more complicated, and therefore more costly to implement and maintain, than simpler access control systems." The flexibility that makes ABAC powerful also makes it harder to reason about. A user asking "why can't I access this?" requires examining all the attributes that were evaluated and which conditions weren't met.</p>
<p><strong>Policy Conflicts</strong>: In complex systems with many policies, conflicts between policies can occur. Two policies might individually seem correct but together produce unexpected results. Resolving these conflicts requires clear precedence rules and careful policy design.</p>
<p><strong>Attribute Management Overhead</strong>: Maintaining accurate attributes across large user populations requires investment in identity infrastructure. Attributes from different systems need to be normalized, validated, and kept synchronized. As NIST describes it, organizations need an entire attribute management infrastructure, not just a policy engine.</p>
<p><strong>Testing is Hard</strong>: Because access depends on the combination of potentially dozens of attributes, testing edge cases comprehensively requires thought. A policy that works correctly for typical cases might behave unexpectedly for unusual attribute combinations.</p>
<p><strong>Not Always Worth the Investment</strong>: For applications with straightforward access requirements, ABAC introduces unnecessary complexity. If your needs can be expressed cleanly as a set of roles with fixed permissions, RBAC is the better choice.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Attribute-Based Access Control represents a genuine evolution in how applications manage authorization. Rather than maintaining ever-growing lists of roles and permissions, ABAC evaluates the actual characteristics of users, resources, and context at the moment of every request.</p>
<p>It solves the role explosion problem that plagues complex RBAC implementations. It enables access rules that reflect real business policies rather than technical approximations of them. It handles dynamic scenarios, emergencies, time-based restrictions, and cross-organizational access that are difficult or impossible to express with static roles.</p>
<p>But ABAC isn't universally better. It's more complex to build, harder to debug, and requires investment in attribute management infrastructure that simpler models don't need. Many applications are well-served by RBAC, and some use RBAC and ABAC together.</p>
<p>The right question isn't "should I use ABAC?" It's "are my access requirements complex enough that the investment in ABAC pays off?" If your access rules change frequently, depend on resource or environment context, or need to scale across organizational boundaries, ABAC is worth serious consideration.</p>
<p>Start by identifying where your current access control model is breaking down. If you're creating roles to represent every edge case, if you're writing conditional logic inside route handlers that checks specific attribute values, or if users are accumulating permissions they should no longer have, those are signals that a more expressive model would help.</p>
<p>ABAC is the tool for when roles aren't enough.</p>
<h2 id="heading-glossary">Glossary</h2>
<p><strong>ABAC (Attribute-Based Access Control)</strong>: An access control method where authorization decisions are made by evaluating policies against the attributes of subjects, objects, actions, and environment conditions. Defined by NIST as the approach where "subject requests to perform operations on objects are granted or denied based on assigned attributes."</p>
<p><strong>Subject</strong>: The entity requesting access to a resource. Usually a human user, but can also be a service, automated process, or device. Also called the "requestor."</p>
<p><strong>Object</strong>: The resource being protected, such as a file, database record, API endpoint, service, or any system resource whose access is managed by the ABAC system.</p>
<p><strong>Attribute</strong>: A characteristic of a subject, object, action, or environment expressed as a name-value pair. For example, <code>user.department = "Finance"</code> or <code>record.sensitivity = "High"</code>.</p>
<p><strong>Subject Attributes</strong>: Properties describing the user or service making the request, such as job title, department, clearance level, or current location.</p>
<p><strong>Object Attributes</strong>: Properties describing the resource being accessed, such as its type, owner, sensitivity level, or department.</p>
<p><strong>Environment Conditions</strong>: Contextual factors independent of both subject and object that influence access decisions. Examples include time of day, day of week, IP address, device compliance status, or current threat level.</p>
<p><strong>Policy</strong>: A rule or set of rules that evaluates attribute values to determine whether a specific access request should be permitted or denied. ABAC policies are typically written as logical conditions.</p>
<p><strong>Policy Decision Point (PDP)</strong>: The component of an ABAC system that evaluates policies and attributes to compute an access decision.</p>
<p><strong>Policy Enforcement Point (PEP)</strong>: The component that intercepts access requests and enforces the decisions made by the PDP.</p>
<p><strong>Policy Information Point (PIP)</strong>: The component that retrieves attribute values needed by the PDP to make decisions.</p>
<p><strong>Policy Administration Point (PAP)</strong>: The component that provides an interface for creating, testing, and managing policies.</p>
<p><strong>RBAC (Role-Based Access Control)</strong>: An access control model that assigns permissions to roles and users to roles. Simpler than ABAC but less expressive for complex, dynamic access requirements.</p>
<p><strong>Role Explosion</strong>: The proliferation of increasingly specific roles in an RBAC system as access requirements become more granular, eventually making the roles as difficult to manage as individual permissions.</p>
<p><strong>DAC (Discretionary Access Control)</strong>: An access control model where resource owners control who can access their resources. Common in file systems.</p>
<p><strong>MAC (Mandatory Access Control)</strong>: An access control model where access is governed by a central authority using classification labels, independent of resource owner preferences.</p>
<p><strong>ACL (Access Control List)</strong>: A list associated with a resource that specifies which users or groups have which permissions. Common in identity-based access control systems.</p>
<p><strong>Non-Person Entity (NPE)</strong>: A subject that is not a human user, such as an automated service, application, or network device, that can request access to resources.</p>
<p><strong>Attribute Caching</strong>: Storing previously retrieved attribute values to improve performance, at the cost of potentially using stale data for access decisions.</p>
<p><strong>Deny-Overrides Combining</strong>: A policy combining rule where if any applicable policy returns deny, the overall decision is deny, regardless of other policies that may return permit.</p>
<p><strong>Fail-Closed</strong>: A security design principle where unexpected errors or missing information result in access being denied rather than granted, reducing the risk of unauthorized access.</p>
<p><em>Source: Definitions adapted from NIST Special Publication 800-162, Guide to Attribute Based Access Control (ABAC) Definition and Considerations, January 2014 (with updates through August 2019).</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use OpenStreetMap as a Free Alternative to Google Maps ]]>
                </title>
                <description>
                    <![CDATA[ Google Maps has been the default choice for developers building location-based applications for years. But for many teams, especially those operating at scale, pricing has become a real concern. Googl ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-openstreetmap-free-alternative-to-google-maps/</link>
                <guid isPermaLink="false">69c41cdf10e664c5dacd6389</guid>
                
                    <category>
                        <![CDATA[ #LocationServices  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ maps ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aiyedogbon Abraham ]]>
                </dc:creator>
                <pubDate>Wed, 25 Mar 2026 17:35:27 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/0ab3655a-4212-451d-93e1-5c707ed1b07e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Google Maps has been the default choice for developers building location-based applications for years. But for many teams, especially those operating at scale, pricing has become a real concern.</p>
<p>Google Maps provides a $200 monthly credit, but beyond that, usage is billed per request. For applications like logistics, ride-hailing, or fleet tracking – where thousands of requests are made daily – costs can grow quickly depending on which APIs you use.</p>
<p>OpenStreetMap (OSM) offers a different approach. Instead of charging for access to map APIs, it provides free, open geographic data that you can build on.</p>
<p>In this guide, you'll learn what OpenStreetMap is, how it differs from Google Maps, and how to integrate it into a React application using Leaflet.</p>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ol>
<li><p><a href="#heading-what-is-openstreetmap">What is OpenStreetMap?</a></p>
</li>
<li><p><a href="#heading-why-choose-openstreetmap-over-google-maps">Why Choose OpenStreetMap Over Google Maps?</a></p>
</li>
<li><p><a href="#heading-understanding-the-open-street-map-ecosystem">Understanding the OpenStreetMap Ecosystem</a></p>
<ul>
<li><p><a href="#heading-data-layer-openstreetmap">Data Layer (OpenStreetMap)</a></p>
</li>
<li><p><a href="#heading-rendering-layer-leaflet-maplibre">Rendering Layer (Leaflet, MapLibre)</a></p>
</li>
<li><p><a href="#heading-services-layer">Services Layer</a></p>
</li>
<li><p><a href="#heading-how-everything-works-together">How Everything Works Together</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-integrate-openstreetmap-in-react-with-leaflet">How to Integrate OpenStreetMap in React with Leaflet</a></p>
</li>
<li><p><a href="#heading-how-to-add-geocoding-with-nominatim">How to Add Geocoding with Nominatim</a></p>
</li>
<li><p><a href="#heading-advanced-features">Advanced Features</a></p>
</li>
<li><p><a href="#heading-when-to-choose-openstreetmap-vs-google-maps">When to Choose OpenStreetMap vs Google Maps</a></p>
</li>
<li><p><a href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ol>
<h2 id="heading-what-is-openstreetmap">What is OpenStreetMap?</h2>
<p>OpenStreetMap is a free, open, and community-driven map of the world. Anyone can contribute to it, and anyone can use it.</p>
<p>Unlike Google Maps, which gives access through controlled APIs, OpenStreetMap gives you access to the underlying geographic data itself.</p>
<p>This data is structured in three main ways:</p>
<ol>
<li><p><strong>Nodes</strong>: single points (for example, a bus stop or a tree)</p>
</li>
<li><p><strong>Ways</strong>: lines or shapes made up of nodes (like roads or buildings)</p>
</li>
<li><p><strong>Relations</strong>: groups of nodes and ways that define more complex things (like routes or boundaries)</p>
</li>
</ol>
<p>Each of these elements includes tags (key-value pairs), such as:</p>
<pre><code class="language-plaintext">highway=residential
name=Allen Avenue
</code></pre>
<p>So instead of just displaying a map, OpenStreetMap lets you work with structured geographic data.</p>
<h3 id="heading-the-open-database-license-odbl">The Open Database License (ODbL)</h3>
<p>OpenStreetMap data is licensed under the ODbL. This means:</p>
<ul>
<li><p>You can use it for commercial or personal projects</p>
</li>
<li><p>You must give proper attribution</p>
</li>
</ul>
<p>This makes it especially useful for developers who want clarity around data ownership.</p>
<h2 id="heading-why-choose-openstreetmap-over-google-maps">Why Choose OpenStreetMap Over Google Maps?</h2>
<h3 id="heading-cost">Cost</h3>
<p>OpenStreetMap data is free to use. But it's important to be precise here: <strong>OpenStreetMap removes licensing costs, but not infrastructure costs.</strong></p>
<p>You may still need to pay for:</p>
<ul>
<li><p>Tile hosting</p>
</li>
<li><p>Geocoding services</p>
</li>
<li><p>Routing engines</p>
</li>
</ul>
<h3 id="heading-control">Control</h3>
<p>With Google Maps, you can't modify the data, and you rely entirely on Google's APIs</p>
<p>But with OpenStreetMap, you can download and store the data, modify it, and build custom solutions on top of it.</p>
<h3 id="heading-customization">Customization</h3>
<p>OpenStreetMap gives you more flexibility:</p>
<ul>
<li><p>You control how maps are rendered</p>
</li>
<li><p>You can choose or build your own map styles</p>
</li>
<li><p>You can create domain-specific maps</p>
</li>
</ul>
<h3 id="heading-adoption">Adoption</h3>
<p>OpenStreetMap is widely used. Companies like Meta and Microsoft contribute to it, and many platforms rely on it directly or indirectly.</p>
<p>This shows that the ecosystem is mature and reliable.</p>
<h2 id="heading-understanding-the-openstreetmap-ecosystem">Understanding the OpenStreetMap Ecosystem</h2>
<p>A common mistake is to think that OpenStreetMap works like a single API. It doesn't.</p>
<p>Instead, it works as a set of layers, where each layer handles a different responsibility.</p>
<h3 id="heading-data-layer-openstreetmap">Data Layer (OpenStreetMap)</h3>
<p>This is the foundation. It contains all the raw geographic data:</p>
<ul>
<li><p>Roads</p>
</li>
<li><p>Buildings</p>
</li>
<li><p>Landmarks</p>
</li>
<li><p>Boundaries</p>
</li>
</ul>
<p>This is what you are ultimately working with.</p>
<h3 id="heading-rendering-layer-leaflet-maplibre">Rendering Layer (Leaflet, MapLibre)</h3>
<p>Raw data isn't visual. It needs to be turned into something users can see.</p>
<p>There are two main approaches:</p>
<ol>
<li><p><strong>Raster tiles</strong> (used by Leaflet): pre-rendered images</p>
</li>
<li><p><strong>Vector tiles</strong> (used by MapLibre): raw geometry styled in the browser</p>
</li>
</ol>
<p>Leaflet uses raster tiles by default, which makes it simple and fast to start with.</p>
<h3 id="heading-services-layer">Services Layer</h3>
<p>This is what makes your map interactive. <strong>Geocoding</strong> converts addresses into coordinates, while <strong>reverse geocoding</strong> converts coordinates into addresses.</p>
<p><strong>Routing</strong> calculates directions between points, and <strong>tile servers</strong> provide the actual map visuals.</p>
<h3 id="heading-how-everything-works-together">How Everything Works Together</h3>
<p>When a user searches for a place:</p>
<ol>
<li><p>The user enters a location</p>
</li>
<li><p>A geocoding service converts it into coordinates</p>
</li>
<li><p>The map updates its position</p>
</li>
<li><p>A tile server provides the visual map</p>
</li>
</ol>
<p>Each part is separate, but they work together to create the full experience.</p>
<h2 id="heading-how-to-integrate-openstreetmap-in-react-with-leaflet">How to Integrate OpenStreetMap in React with Leaflet</h2>
<p>Let's build a simple map.</p>
<h3 id="heading-step-1-create-a-react-app">Step 1: Create a React App</h3>
<pre><code class="language-bash">npm create vite@latest osm-app -- --template react
cd osm-app
npm install
</code></pre>
<h3 id="heading-step-2-install-dependencies">Step 2: Install Dependencies</h3>
<pre><code class="language-bash">npm install leaflet react-leaflet
npm install --save-dev @types/leaflet
</code></pre>
<h3 id="heading-step-3-import-leaflet-css">Step 3: Import Leaflet CSS</h3>
<pre><code class="language-javascript">import 'leaflet/dist/leaflet.css';
</code></pre>
<p>This is required for the map to display correctly.</p>
<h3 id="heading-step-4-create-a-map-component">Step 4: Create a Map Component</h3>
<pre><code class="language-javascript">import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';

function Map() {
  const position = [51.505, -0.09]; // latitude, longitude

  return (
    &lt;MapContainer
      center={position}
      zoom={13}
      style={{ height: '100vh' }}
    &gt;
      &lt;TileLayer
        attribution='&amp;copy; &lt;a href="https://www.openstreetmap.org/copyright"&gt;OpenStreetMap&lt;/a&gt; contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      /&gt;
      &lt;Marker position={position}&gt;
        &lt;Popup&gt;Hello from OpenStreetMap&lt;/Popup&gt;
      &lt;/Marker&gt;
    &lt;/MapContainer&gt;
  );
}

export default Map;
</code></pre>
<p>Let's break down the important parts here:</p>
<p><code>MapContainer</code> initializes the map.</p>
<ul>
<li><p><code>center</code> is where the map starts</p>
</li>
<li><p><code>zoom</code> is how close the view is</p>
</li>
<li><p><code>style</code> must include height, or the map won't show</p>
</li>
</ul>
<p><code>TileLayer</code> defines where the map visuals come from.</p>
<ul>
<li><p><code>{z}</code> is the zoom level</p>
</li>
<li><p><code>{x}</code>, <code>{y}</code> are the tile coordinates</p>
</li>
<li><p><code>{s}</code> is the subdomain</p>
</li>
</ul>
<p>Each tile is a small image (usually 256×256 pixels), and Leaflet combines them to form the full map.</p>
<p><code>Marker</code> adds a point on the map at a specific coordinate.</p>
<p><code>Popup</code> displays information when the marker is clicked.</p>
<h4 id="heading-important-note">Important note:</h4>
<p>The default OpenStreetMap tile server:</p>
<pre><code class="language-plaintext">https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
</code></pre>
<p>is meant for learning, demos, and low-traffic apps. For production, you should use a dedicated provider or your own tile server.</p>
<h2 id="heading-how-to-add-geocoding-with-nominatim">How to Add Geocoding with Nominatim</h2>
<p>Nominatim is OpenStreetMap's geocoding service. It allows you to convert addresses into coordinates and coordinates into readable locations.</p>
<h3 id="heading-custom-hook-for-geocoding">Custom Hook for Geocoding</h3>
<pre><code class="language-javascript">import { useState } from 'react';

export function useGeocoding() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const searchAddress = async (query) =&gt; {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch(
        `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&amp;format=json&amp;limit=5`,
        {
          headers: {
            'User-Agent': 'YourAppName/1.0'
          }
        }
      );

      if (!response.ok) {
        throw new Error('Request failed');
      }

      const data = await response.json();
      setLoading(false);
      return data;
    } catch (err) {
      setError(err.message);
      setLoading(false);
      return [];
    }
  };

  return { searchAddress, loading, error };
}
</code></pre>
<p>In this code:</p>
<ul>
<li><p><code>useState</code> manages loading and error states</p>
</li>
<li><p><code>encodeURIComponent</code> ensures safe URLs</p>
</li>
<li><p><code>User-Agent</code> is required by Nominatim</p>
</li>
<li><p><code>response.json()</code> converts response into usable data</p>
</li>
</ul>
<p>Nominatim returns coordinates as strings, so you have to convert them before using them.</p>
<h3 id="heading-important-usage-rules">Important Usage Rules</h3>
<p>The public Nominatim service:</p>
<ul>
<li><p>Allows about 1 request per second</p>
</li>
<li><p>Requires proper identification</p>
</li>
<li><p>May block excessive usage</p>
</li>
</ul>
<p>You should debounce user input, cache results, and avoid repeated requests.</p>
<h3 id="heading-creating-a-search-component">Creating a Search Component</h3>
<p>The search component lets users type an address or place name and get matching locations via Nominatim. It includes a text input and a submit button.</p>
<p>When the form is submitted, it calls our <code>searchAddress</code> function (from the <code>useGeocoding</code> hook), which fetches up to 5 address results. These results are displayed below the input as clickable items.</p>
<p>When the user clicks a result, the component parses the returned latitude and longitude into numbers and passes them (along with a display name) up to the parent component via the <code>onLocationSelect</code> callback. This will allow the parent (for example, the map) to update its center based on the chosen location.</p>
<pre><code class="language-javascript">function SearchBox({ onLocationSelect }) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const { searchAddress, loading } = useGeocoding();

  const handleSearch = async (e) =&gt; {
    e.preventDefault();
    if (!query.trim()) return;

    const data = await searchAddress(query);
    setResults(data);
  };

  const selectLocation = (result) =&gt; {
    onLocationSelect({
      lat: parseFloat(result.lat),
      lon: parseFloat(result.lon),
      name: result.display_name
    });
  };

  return (
    &lt;div&gt;
      &lt;form onSubmit={handleSearch}&gt;
        &lt;input
          value={query}
          onChange={(e) =&gt; setQuery(e.target.value)}
          placeholder="Search location"
        /&gt;
        &lt;button type="submit"&gt;
          {loading ? 'Searching...' : 'Search'}
        &lt;/button&gt;
      &lt;/form&gt;

      &lt;div&gt;
        {results.map((result) =&gt; (
          &lt;div key={result.place_id} onClick={() =&gt; selectLocation(result)}&gt;
            {result.display_name}
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>Key concepts here:</p>
<ul>
<li><p><code>useState</code> stores the current input (<code>query</code>) and the array of search <code>results</code>.</p>
</li>
<li><p><code>e.preventDefault()</code> stops the form submission from reloading the page.</p>
</li>
<li><p>Calling <code>searchAddress(query)</code> fetches geocoding results from Nominatim.</p>
</li>
<li><p><code>parseFloat()</code> converts the returned <code>lat</code>/<code>lon</code> strings into JavaScript numbers before using them.</p>
</li>
<li><p><code>onLocationSelect</code> is a callback prop that sends the selected coordinates and name back to the parent component (for example to update the map).</p>
</li>
</ul>
<h2 id="heading-advanced-features">Advanced Features</h2>
<p>We can further extend the map app by adding more advanced functionality. For example:</p>
<h3 id="heading-routing-osrm-graphhopper">Routing (OSRM, GraphHopper)</h3>
<p>You can integrate turn-by-turn routing on your map. A common solution is to use a library like <a href="https://www.liedman.net/leaflet-routing-machine/">Leaflet Routing Machine</a>, which supports OSRM out of the box and has plugins for GraphHopper. This adds a route UI control where users enter start and end points, and the library fetches a route from one of these engines to draw on the map.</p>
<h3 id="heading-custom-tile-providers-carto-maptiler-and-so-on"><strong>Custom Tile Providers (Carto, MapTiler, and so on)</strong></h3>
<p>Instead of the standard <a href="http://tile.openstreetmap.org"><code>tile.openstreetmap.org</code></a>, you can use hosted tile services that offer OSM-based maps. For example, Carto and MapTiler both provide tile APIs (often with custom style options and higher usage limits).</p>
<p>Carto, MapTiler, and similar services are listed among the providers that allow free usage of OSM tiles. By using a custom tile provider, you gain flexibility in map design and avoid hitting the public server’s limits.</p>
<h3 id="heading-vector-maps-maplibre-gl-js">Vector Maps (MapLibre GL JS)</h3>
<p>You can switch from raster tiles to vector tiles for even richer interactivity. Vector tiles send raw map data (geometries and attributes) to the client, which are then rendered in the browser. This allows dynamic styling and advanced features: for instance, you can change the map’s theme on the fly (for example, switch to a “dark mode” style at night) or highlight certain features like bike lanes more prominently.</p>
<p>Libraries like MapLibre GL JS (the open-source successor to Mapbox GL) can display OSM vector tiles with highly customizable styles and smooth zooming/rotation. This makes your map more responsive and adaptable to different use cases.</p>
<h2 id="heading-when-to-choose-openstreetmap-vs-google-maps">When to Choose OpenStreetMap vs Google Maps</h2>
<h3 id="heading-choose-openstreetmap-when">Choose OpenStreetMap when:</h3>
<ul>
<li><p>You need flexibility</p>
</li>
<li><p>You want to reduce costs at scale</p>
</li>
<li><p>You want control over data</p>
</li>
</ul>
<h3 id="heading-choose-google-maps-when">Choose Google Maps when:</h3>
<ul>
<li><p>You want an all-in-one solution</p>
</li>
<li><p>You need features like Street View</p>
</li>
<li><p>You want minimal setup</p>
</li>
</ul>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>OpenStreetMap offers a powerful alternative to Google Maps for developers who need cost control, data ownership, and customization. While it requires understanding different components, the flexibility it provides is worth the learning curve.</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>
