<?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[ Software Testing - 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[ Software Testing - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 10 Oct 2026 18:32:49 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/software-testing/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Use Differential Testing During a Legacy Migration ]]>
                </title>
                <description>
                    <![CDATA[ The most dangerous moment in a legacy migration isn't necessarily when you start writing the new implementation. It's when the new implementation looks finished. The code compiles, the tests pass, the ]]>
                </description>
                <link>https://www.freecodecamp.org/news/differential-testing-legacy-migration/</link>
                <guid isPermaLink="false">6aa8200e59dfce663a0e73bd</guid>
                
                    <category>
                        <![CDATA[ legacy code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ migration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hugo Teijiz ]]>
                </dc:creator>
                <pubDate>Mon, 14 Sep 2026 16:25:50 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/6717bbb6-16b9-4fc1-8bfe-7381a3024f73.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The most dangerous moment in a legacy migration isn't necessarily when you start writing the new implementation. It's when the new implementation looks finished.</p>
<p>The code compiles, the tests pass, the architecture is cleaner, and the new service responds faster.</p>
<p>Then everybody starts asking the same question:</p>
<blockquote>
<p>Can we switch traffic now?</p>
</blockquote>
<p>That's where confidence becomes difficult.</p>
<p>A new implementation can pass its own test suite and still behave differently from the system it is replacing.</p>
<p>Maybe rounding changed, or null values are handled differently, or an error became a successful response.</p>
<p>Maybe records are sorted differently, or a side effect happens in a different order, or a business rule you never documented was lost during the migration.</p>
<p>This is why, during a legacy migration, I like having another source of evidence: <strong>run the old and new implementations with the same inputs and compare what they do.</strong></p>
<p>That's the basic idea behind differential testing. Instead of asking only if the new system passes its tests, you also ask: given the same input, where does the new system behave differently from the old one?</p>
<p>Those differences become evidence.</p>
<p>Some are bugs, some are intentional improvements, some are harmless representation differences, and some reveal behavior nobody knew existed.</p>
<p>In this tutorial, I'll show you how to use differential testing during a legacy migration to:</p>
<ul>
<li><p>Compare old and new implementations</p>
</li>
<li><p>Define what should be considered equivalent</p>
</li>
<li><p>Normalize outputs before comparing them</p>
</li>
<li><p>Handle timestamps and other nondeterministic values</p>
</li>
<li><p>Compare errors and side effects</p>
</li>
<li><p>Run differential tests automatically</p>
</li>
<li><p>Introduce tolerances where exact equality doesn't make sense</p>
</li>
<li><p>Analyze mismatches</p>
</li>
<li><p>Use shadow traffic in production safely</p>
</li>
<li><p>Use AI to classify divergences without letting it decide correctness</p>
</li>
<li><p>Determine when the new implementation is ready for cutover</p>
</li>
</ul>
<p>The examples use TypeScript and Vitest, but the approach applies to most languages and migration strategies.</p>
<p>The goal isn't to prove that two implementations are internally identical. It's to obtain evidence that they are <strong>behaviorally equivalent where equivalence matters</strong>.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along here, you should be comfortable with:</p>
<ul>
<li><p>TypeScript or a similar language</p>
</li>
<li><p>unit and integration testing</p>
</li>
<li><p>asynchronous code</p>
</li>
<li><p>API and service boundaries</p>
</li>
<li><p>legacy modernization</p>
</li>
<li><p>basic observability concepts</p>
</li>
</ul>
<p>You should also already have some understanding of the capability being migrated.</p>
<p>Ideally, you know its inputs, outputs, important business rules, external contracts, side effects, and known areas of uncertainty.</p>
<p>Differential testing works best after you've already created a boundary around the capability you want to migrate.</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-what-differential-testing-actually-tells-you">What Differential Testing Actually Tells You</a></p>
</li>
<li><p><a href="#heading-start-with-one-observable-boundary">Start with One Observable Boundary</a></p>
</li>
<li><p><a href="#heading-run-the-legacy-and-new-implementations-with-the-same-input">Run the Legacy and New Implementations with the Same Input</a></p>
</li>
<li><p><a href="#heading-dont-compare-raw-output-blindly">Don't Compare Raw Output Blindly</a></p>
</li>
<li><p><a href="#heading-normalize-values-before-comparing-them">Normalize Values Before Comparing Them</a></p>
</li>
<li><p><a href="#heading-handle-timestamps-and-other-nondeterministic-values">Handle Timestamps and Other Nondeterministic Values</a></p>
</li>
<li><p><a href="#heading-compare-business-meaning-not-just-json">Compare Business Meaning, Not Just JSON</a></p>
</li>
<li><p><a href="#heading-compare-errors-as-part-of-the-contract">Compare Errors as Part of the Contract</a></p>
</li>
<li><p><a href="#heading-compare-side-effects-too">Compare Side Effects, Too</a></p>
</li>
<li><p><a href="#heading-use-tolerances-when-exact-equality-is-wrong">Use Tolerances When Exact Equality Is Wrong</a></p>
</li>
<li><p><a href="#heading-build-a-reusable-differential-test-harness">Build a Reusable Differential Test Harness</a></p>
</li>
<li><p><a href="#heading-generate-test-cases-from-real-behavior">Generate Test Cases from Real Behavior</a></p>
</li>
<li><p><a href="#heading-classify-every-difference">Classify Every Difference</a></p>
</li>
<li><p><a href="#heading-how-to-use-ai-to-investigate-differential-failures">How to Use AI to Investigate Differential Failures</a></p>
</li>
<li><p><a href="#heading-how-to-use-shadow-traffic-safely">How to Use Shadow Traffic Safely</a></p>
</li>
<li><p><a href="#heading-measure-divergence-instead-of-waiting-for-perfection">Measure Divergence Instead of Waiting for Perfection</a></p>
</li>
<li><p><a href="#heading-how-to-know-when-youre-ready-for-cutover">How to Know When You're Ready for Cutover</a></p>
</li>
<li><p><a href="#heading-a-practical-differential-testing-workflow">A Practical Differential Testing Workflow</a></p>
</li>
<li><p><a href="#heading-what-differential-testing-cant-prove">What Differential Testing Can't Prove</a></p>
</li>
<li><p><a href="#heading-differential-testing-turns-migration-risk-into-evidence">Differential Testing Turns Migration Risk into Evidence</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-differential-testing-actually-tells-you">What Differential Testing Actually Tells You</h2>
<p>Imagine that your legacy application calculates the final price of an order.</p>
<p>The legacy implementation looks like this:</p>
<pre><code class="language-typescript">type Order = {
  subtotal: number;
  customerType: "STANDARD" | "PREMIUM";
  country: string;
};

function legacyCalculateTotal(order: Order): number {
  let total = order.subtotal;

  if (order.customerType === "PREMIUM") {
    total *= 0.9;
  }

  if (order.country === "AR") {
    total -= 500;
  }

  return Math.max(total, 0);
}
</code></pre>
<p>During the migration, you create a new implementation:</p>
<pre><code class="language-typescript">function newCalculateTotal(order: Order): number {
  const premiumDiscount =
    order.customerType === "PREMIUM"
      ? order.subtotal * 0.1
      : 0;

  const countryAdjustment =
    order.country === "AR"
      ? 500
      : 0;

  return Math.max(
    order.subtotal -
      premiumDiscount -
      countryAdjustment,
    0
  );
}
</code></pre>
<p>The implementations look different. And that's fine. What matters is whether they produce equivalent behavior.</p>
<p>A simple differential test can run both:</p>
<pre><code class="language-typescript">import { describe, expect, it } from "vitest";

describe("order total migration", () =&gt; {
  it("matches the legacy implementation", () =&gt; {
    const order: Order = {
      subtotal: 10000,
      customerType: "PREMIUM",
      country: "AR",
    };

    const legacy =
      legacyCalculateTotal(order);

    const migrated =
      newCalculateTotal(order);

    expect(migrated).toBe(legacy);
  });
});
</code></pre>
<p>For this input:</p>
<pre><code class="language-text">legacy → 8500
new    → 8500
</code></pre>
<p>Good. But one matching example proves very little.</p>
<p>The value comes from systematically asking:</p>
<pre><code class="language-text">same input
↓
legacy implementation ──→ result A

same input
↓
new implementation ─────→ result B

compare A and B
</code></pre>
<p>Every mismatch gives you something to investigate.</p>
<h2 id="heading-start-with-one-observable-boundary">Start with One Observable Boundary</h2>
<p>Don't begin by comparing entire applications. To start, choose one capability.</p>
<p>For example:</p>
<pre><code class="language-text">Calculate Order Total
Generate Invoice
Approve Customer
Renew Subscription
Calculate Commission
Create Shipment
</code></pre>
<p>Suppose the migration boundary is:</p>
<pre><code class="language-typescript">interface OrderProcessor {
  process(order: Order): Promise&lt;ProcessedOrder&gt;;
}
</code></pre>
<p>Now you have two implementations:</p>
<pre><code class="language-text">LegacyOrderProcessor

NewOrderProcessor
</code></pre>
<p>That is a useful differential boundary, because both receive the same conceptual input, and both produce the same conceptual output.</p>
<p>You can compare them without requiring their internal architecture to match.</p>
<p>That matters because migrations often change structure intentionally.</p>
<p>The legacy implementation might be:</p>
<pre><code class="language-text">controller
→ service
→ SQL
→ provider SDK
</code></pre>
<p>while the new implementation might be:</p>
<pre><code class="language-text">use case
→ repository
→ gateway
→ events
</code></pre>
<p>Differential testing shouldn't care. It should care about observable behavior.</p>
<h2 id="heading-run-the-legacy-and-new-implementations-with-the-same-input">Run the Legacy and New Implementations with the Same Input</h2>
<p>Suppose both implementations expose:</p>
<pre><code class="language-typescript">interface OrderProcessor {
  process(order: Order): Promise&lt;ProcessedOrder&gt;;
}
</code></pre>
<p>You can create:</p>
<pre><code class="language-typescript">const legacyProcessor =
  new LegacyOrderProcessor();

const newProcessor =
  new NewOrderProcessor();
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">it("produces the same processed order", async () =&gt; {
  const input: Order = {
    id: "order-1",
    subtotal: 10000,
    customerType: "PREMIUM",
    country: "US",
  };

  const legacy =
    await legacyProcessor.process(
      structuredClone(input)
    );

  const migrated =
    await newProcessor.process(
      structuredClone(input)
    );

  expect(migrated).toEqual(legacy);
});
</code></pre>
<p>Notice the use of:</p>
<pre><code class="language-typescript">structuredClone(input)
</code></pre>
<p>That matters if either implementation mutates its input.</p>
<p>Without separate copies, the first execution could influence the second.</p>
<p>You want:</p>
<pre><code class="language-text">same initial state
</code></pre>
<p>not:</p>
<pre><code class="language-text">new implementation receives state modified by legacy implementation
</code></pre>
<p>That kind of contamination can create misleading results.</p>
<h2 id="heading-dont-compare-raw-output-blindly">Don't Compare Raw Output Blindly</h2>
<p>The first version of a differential test is often:</p>
<pre><code class="language-typescript">expect(newResult).toEqual(legacyResult);
</code></pre>
<p>Sometimes that's exactly right. But other times it's wrong.</p>
<p>Imagine the legacy system returns:</p>
<pre><code class="language-json">{
  "id": "order-1",
  "total": 9000,
  "status": "PROCESSED",
  "generatedAt": "2026-09-09T10:00:01.231Z",
  "requestId": "legacy-f93a"
}
</code></pre>
<p>The new system returns:</p>
<pre><code class="language-json">{
  "requestId": "new-b517",
  "status": "PROCESSED",
  "generatedAt": "2026-09-09T10:00:01.416Z",
  "total": 9000,
  "id": "order-1"
}
</code></pre>
<p>A raw object comparison may fail because:</p>
<pre><code class="language-text">requestId differs
timestamp differs
</code></pre>
<p>But the business behavior might be equivalent.</p>
<p>You need to decide which fields are part of the meaningful contract.</p>
<p>Maybe:</p>
<pre><code class="language-text">id
total
status
</code></pre>
<p>matter.</p>
<p>While:</p>
<pre><code class="language-text">generatedAt
requestId
</code></pre>
<p>don't need exact equivalence.</p>
<p>That leads to normalization.</p>
<h2 id="heading-normalize-values-before-comparing-them">Normalize Values Before Comparing Them</h2>
<p>Normalization means transforming outputs into a common representation before comparing them.</p>
<p>The goal isn't to change the business meaning of the data. It's to remove differences that are expected and irrelevant to the comparison, such as generated request IDs or timestamps, so the test can focus on the fields that actually define the behavior you care about.</p>
<p>In practice, that often means creating a canonical representation: a smaller, stable shape that contains only the meaningful fields you want to compare.</p>
<p>For example:</p>
<pre><code class="language-typescript">type ProcessedOrder = {
  id: string;
  total: number;
  status: string;
  generatedAt: string;
  requestId: string;
};

function normalizeOrder(
  order: ProcessedOrder
) {
  return {
    id: order.id,
    total: order.total,
    status: order.status,
  };
}
</code></pre>
<p>Here, <code>ProcessedOrder</code> contains both business-relevant fields and values that may legitimately differ between executions.</p>
<p>The <code>normalizeOrder()</code> function keeps <code>id</code>, <code>total</code>, and <code>status</code>, while leaving out <code>generatedAt</code> and <code>requestId</code>. That means two results can still be considered equivalent even if they were generated at slightly different times or used different request identifiers.</p>
<p>Now compare:</p>
<pre><code class="language-typescript">expect(
  normalizeOrder(migrated)
).toEqual(
  normalizeOrder(legacy)
);
</code></pre>
<p>This makes your equivalence rule explicit.</p>
<p>You're saying:</p>
<blockquote>
<p>These fields define relevant behavior for this comparison.</p>
</blockquote>
<p>Normalization can also handle:</p>
<ul>
<li><p>ordering</p>
</li>
<li><p>casing</p>
</li>
<li><p>optional fields</p>
</li>
<li><p>timestamps</p>
</li>
<li><p>generated identifiers</p>
</li>
<li><p>numeric formatting</p>
</li>
<li><p>provider-specific metadata</p>
</li>
</ul>
<p>But normalization must be deliberate. If you remove too much, you can hide real migration bugs.</p>
<h2 id="heading-handle-timestamps-and-other-nondeterministic-values">Handle Timestamps and Other Nondeterministic Values</h2>
<p>Legacy systems contain many nondeterministic values.</p>
<p>For example:</p>
<pre><code class="language-text">timestamps
UUIDs
random tokens
request IDs
trace IDs
database-generated IDs
unordered collections
provider-generated references
</code></pre>
<p>If you compare those values exactly, your differential suite may fail constantly.</p>
<p>One option is dependency control.</p>
<p>Dependency control means moving a nondeterministic source, such as the current time or an ID generator, behind an interface that you can replace during tests.</p>
<p>Instead of letting each implementation read the real clock independently, you inject the same controlled clock into both. That gives them the same value and removes time itself as a source of meaningless divergence.</p>
<p>Suppose the code uses:</p>
<pre><code class="language-typescript">new Date()
</code></pre>
<p>You can replace that dependency with a clock:</p>
<pre><code class="language-typescript">interface Clock {
  now(): Date;
}
</code></pre>
<p>Then both implementations receive:</p>
<pre><code class="language-typescript">const clock = {
  now: () =&gt;
    new Date(
      "2026-09-09T10:00:00.000Z"
    ),
};
</code></pre>
<p>Now time becomes deterministic.</p>
<p>The same technique can work for ID generation:</p>
<pre><code class="language-typescript">interface IdGenerator {
  next(): string;
}
</code></pre>
<p>Then tests can provide:</p>
<pre><code class="language-typescript">const ids = {
  next: () =&gt; "fixed-id",
};
</code></pre>
<p>If controlling nondeterminism is impractical, normalize it out only when it's not part of the behavior you need to protect.</p>
<h2 id="heading-compare-business-meaning-not-just-json">Compare Business Meaning, Not Just JSON</h2>
<p>Two systems can return different representations while expressing the same business state.</p>
<p>Imagine you have this in your legacy system:</p>
<pre><code class="language-json">{
  "status": 2
}
</code></pre>
<p>And this in your new one:</p>
<pre><code class="language-json">{
  "status": "APPROVED"
}
</code></pre>
<p>Raw comparison says:</p>
<pre><code class="language-text">different
</code></pre>
<p>Business comparison may say:</p>
<pre><code class="language-text">equivalent
</code></pre>
<p>You can create a semantic normalizer:</p>
<pre><code class="language-typescript">function normalizeStatus(
  status: number | string
) {
  if (status === 2) {
    return "APPROVED";
  }

  return status;
}
</code></pre>
<p>Here, the normalizer translates the legacy numeric value <code>2</code> into the business meaning used by the new implementation: <code>"APPROVED"</code>.</p>
<p>It doesn't claim that every number and string are interchangeable. It encodes one explicit equivalence rule that you've already decided is valid for this migration.</p>
<p>Then:</p>
<pre><code class="language-typescript">expect(
  normalizeStatus(newResult.status)
).toBe(
  normalizeStatus(legacyResult.status)
);
</code></pre>
<p>This is especially useful when migration intentionally changes:</p>
<pre><code class="language-text">database schema
API representation
enumerations
provider-specific formats
internal identifiers
</code></pre>
<p>The important question becomes:</p>
<blockquote>
<p>Does the observable business meaning remain equivalent?</p>
</blockquote>
<p>Not:</p>
<blockquote>
<p>Are the bytes identical?</p>
</blockquote>
<h2 id="heading-compare-errors-as-part-of-the-contract">Compare Errors as Part of the Contract</h2>
<p>Success responses aren't the whole behavior. Errors matter too.</p>
<p>Suppose the legacy implementation rejects a missing customer:</p>
<pre><code class="language-typescript">throw new Error("Customer not found");
</code></pre>
<p>The new implementation accidentally returns:</p>
<pre><code class="language-typescript">return null;
</code></pre>
<p>These two implementations behave very differently for the same invalid input.</p>
<p>The legacy version fails explicitly, while the new version silently returns a value that a caller may interpret as a successful result.</p>
<p>If your differential tests only exercise cases where a valid customer exists, both implementations may appear equivalent and this contract change will remain invisible.</p>
<p>That's why failure behavior has to be compared too.</p>
<p>Create cases that capture errors:</p>
<pre><code class="language-typescript">async function captureResult&lt;T&gt;(
  operation: () =&gt; Promise&lt;T&gt;
) {
  try {
    return {
      type: "success" as const,
      value: await operation(),
    };
  } catch (error) {
    return {
      type: "error" as const,
      error:
        error instanceof Error
          ? error.message
          : String(error),
    };
  }
}
</code></pre>
<p>The helper wraps an asynchronous operation and converts both possible outcomes into data.</p>
<p>If the operation succeeds, it returns an object with <code>type: "success"</code> and the returned value. If the operation throws, the <code>catch</code> block converts that exception into an object with <code>type: "error"</code> and a readable error message.</p>
<p>This gives both implementations the same comparison shape, so the test can compare success versus failure explicitly instead of letting an exception stop the test before the two behaviors can be evaluated.</p>
<p>Now:</p>
<pre><code class="language-typescript">const legacy =
  await captureResult(() =&gt;
    legacyProcessor.process(input)
  );

const migrated =
  await captureResult(() =&gt;
    newProcessor.process(input)
  );

expect(migrated.type).toBe(legacy.type);
</code></pre>
<p>If errors are contractually important, compare:</p>
<pre><code class="language-text">error category
HTTP status
error code
retryability
validation details
</code></pre>
<p>Don't necessarily compare exact wording unless clients depend on it.</p>
<h2 id="heading-compare-side-effects-too">Compare Side Effects, Too</h2>
<p>One of the easiest migration mistakes is preserving the return value while losing a side effect.</p>
<p>Suppose both implementations return:</p>
<pre><code class="language-json">{
  "status": "PROCESSED"
}
</code></pre>
<p>But the legacy version also:</p>
<pre><code class="language-text">persists the order
publishes an event
creates a payment
writes an audit entry
</code></pre>
<p>and the new version forgets the audit entry.</p>
<p>Response-level differential testing won't catch that. So you'll want to capture side effects.</p>
<p>For example:</p>
<pre><code class="language-typescript">type Effect =
  | {
      type: "payment";
      orderId: string;
      amount: number;
    }
  | {
      type: "event";
      name: string;
      orderId: string;
    };
</code></pre>
<p>A test adapter can record them:</p>
<pre><code class="language-typescript">class RecordingPaymentGateway {
  effects: Effect[] = [];

  async charge(
    orderId: string,
    amount: number
  ) {
    this.effects.push({
      type: "payment",
      orderId,
      amount,
    });
  }
}
</code></pre>
<p>Instead of sending a real payment request, this adapter records what the application attempted to do in the <code>effects</code> array.</p>
<p>You can apply the same idea to event publication:</p>
<pre><code class="language-typescript">class RecordingEvents {
  effects: Effect[] = [];

  async publish(
    name: string,
    orderId: string
  ) {
    this.effects.push({
      type: "event",
      name,
      orderId,
    });
  }
}
</code></pre>
<p>The application still calls its payment and event dependencies as usual. The test doubles simply capture those calls as structured data instead of performing the real external actions.</p>
<p>After running the legacy and migrated implementations with their own recording adapters, you can compare the two recorded effect lists and verify that both systems attempted the same observable side effects.</p>
<p>Now the differential test can compare:</p>
<pre><code class="language-typescript">expect(newEffects).toEqual(legacyEffects);
</code></pre>
<p>Again, exact ordering should only be required if ordering matters.</p>
<h2 id="heading-use-tolerances-when-exact-equality-is-wrong">Use Tolerances When Exact Equality Is Wrong</h2>
<p>Some domains shouldn't use exact equality.</p>
<p>Imagine a migrated calculation produces:</p>
<pre><code class="language-text">legacy → 34.333333333
new    → 34.333333334
</code></pre>
<p>Is that a migration bug? Maybe not.</p>
<p>Floating-point calculations may justify a tolerance.</p>
<p>For example:</p>
<pre><code class="language-typescript">expect(newResult).toBeCloseTo(
  legacyResult,
  6
);
</code></pre>
<p>Or define an explicit comparator:</p>
<pre><code class="language-typescript">function withinTolerance(
  a: number,
  b: number,
  tolerance: number
) {
  return Math.abs(a - b) &lt;= tolerance;
}
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">expect(
  withinTolerance(
    migrated.total,
    legacy.total,
    0.01
  )
).toBe(true);
</code></pre>
<p>But tolerances should come from domain requirements. Don't use them just to make failing tests disappear.</p>
<p>For financial systems, one cent can matter. For scientific calculations, a much smaller numerical difference may matter.</p>
<p>Equivalence is a business and engineering decision.</p>
<h2 id="heading-build-a-reusable-differential-test-harness">Build a Reusable Differential Test Harness</h2>
<p>Once you compare more than a few cases, you can create a reusable harness.</p>
<p>For example:</p>
<pre><code class="language-typescript">type DifferentialResult&lt;T&gt; = {
  input: T;
  equivalent: boolean;
  legacy: unknown;
  migrated: unknown;
};

async function compareImplementations&lt;
  TInput,
  TOutput
&gt;(
  input: TInput,
  legacy: (
    input: TInput
  ) =&gt; Promise&lt;TOutput&gt;,
  migrated: (
    input: TInput
  ) =&gt; Promise&lt;TOutput&gt;,
  normalize: (
    output: TOutput
  ) =&gt; unknown
): Promise&lt;
  DifferentialResult&lt;TInput&gt;
&gt; {
  const legacyResult =
    await legacy(
      structuredClone(input)
    );

  const migratedResult =
    await migrated(
      structuredClone(input)
    );

  const normalizedLegacy =
    normalize(legacyResult);

  const normalizedMigrated =
    normalize(migratedResult);

  return {
    input,
    equivalent:
      JSON.stringify(
        normalizedLegacy
      ) ===
      JSON.stringify(
        normalizedMigrated
      ),
    legacy: normalizedLegacy,
    migrated: normalizedMigrated,
  };
}
</code></pre>
<p>The harness does four things.</p>
<p>First, it runs the legacy and migrated implementations with separate clones of the same input, so one execution can't mutate the data seen by the other.</p>
<p>Second, it passes both outputs through the same <code>normalize()</code> function. That applies the equivalence rules in one place instead of repeating them in every test.</p>
<p>Third, it compares the normalized results and records whether they're equivalent.</p>
<p>Finally, it returns the input and both normalized outputs together. That makes a failed comparison easier to inspect because the test report can show exactly which case diverged and what each implementation produced.</p>
<p>Then:</p>
<pre><code class="language-typescript">const result =
  await compareImplementations(
    input,
    legacyProcessor.process.bind(
      legacyProcessor
    ),
    newProcessor.process.bind(
      newProcessor
    ),
    normalizeOrder
  );

expect(result.equivalent).toBe(true);
</code></pre>
<p>For real systems, I would usually avoid relying on <code>JSON.stringify()</code> as the final equality mechanism.</p>
<p>The example keeps the harness readable.</p>
<p>In production-quality tooling, use a proper structural or domain-specific comparator.</p>
<p>The important part is that comparison logic becomes centralized.</p>
<h2 id="heading-generate-test-cases-from-real-behavior">Generate Test Cases from Real Behavior</h2>
<p>Hand-written examples are useful. But migrations often fail on cases nobody thought to write manually.</p>
<p>Useful sources of inputs include:</p>
<pre><code class="language-text">existing test fixtures
historical incidents
production-safe request samples
database records
boundary values
previous bug reports
known customer scenarios
</code></pre>
<p>Suppose production shows these order shapes:</p>
<pre><code class="language-typescript">const cases: Order[] = [
  {
    subtotal: 0,
    customerType: "STANDARD",
    country: "US",
  },
  {
    subtotal: 500,
    customerType: "PREMIUM",
    country: "AR",
  },
  {
    subtotal: 10000,
    customerType: "STANDARD",
    country: "AR",
  },
];
</code></pre>
<p>The first block is the test data. It captures a small set of representative input shapes that you've observed in real usage or reconstructed safely from production behavior.</p>
<p>The next block is the test itself. <code>it.each(cases)</code> tells Vitest to run the same differential comparison once for every input in that array.</p>
<p>That separates two concerns: defining realistic cases and defining how every case should be evaluated.</p>
<p>Now:</p>
<pre><code class="language-typescript">it.each(cases)(
  "matches legacy behavior",
  async (input) =&gt; {
    const legacy =
      await legacyProcessor.process(
        structuredClone(input)
      );

    const migrated =
      await newProcessor.process(
        structuredClone(input)
      );

    expect(
      normalizeOrder(migrated)
    ).toEqual(
      normalizeOrder(legacy)
    );
  }
);
</code></pre>
<p>Real examples help expose assumptions that synthetic test data often misses. But production data must be handled carefully.</p>
<p>Remove or anonymize:</p>
<pre><code class="language-text">personal data
credentials
tokens
financial identifiers
confidential business data
</code></pre>
<p>The objective is to preserve useful behavioral shapes, not copy sensitive production information into test fixtures.</p>
<h2 id="heading-classify-every-difference">Classify Every Difference</h2>
<p>A differential failure doesn't automatically mean that the new implementation is wrong.</p>
<p>Suppose you find 200 mismatches. Classify them.</p>
<p>I like categories such as:</p>
<pre><code class="language-text">migration defect
legacy defect intentionally preserved
intentional behavior change
representation difference
nondeterministic difference
test/comparator defect
unknown
</code></pre>
<p>For example:</p>
<pre><code class="language-text">Input:
subtotal = 5000

Legacy:
discount = 0

New:
discount = 500

Classification:
unknown
</code></pre>
<p>Investigation reveals that the new implementation changed:</p>
<pre><code class="language-typescript">amount &gt; 5000
</code></pre>
<p>to:</p>
<pre><code class="language-typescript">amount &gt;= 5000
</code></pre>
<p>Now you need a decision.</p>
<p>Was that:</p>
<pre><code class="language-text">accidental migration change
</code></pre>
<p>or:</p>
<pre><code class="language-text">intentional bug fix
</code></pre>
<p>Differential testing exposes the decision. It doesn't make the decision for you.</p>
<p>That's one of its greatest benefits.</p>
<h2 id="heading-how-to-use-ai-to-investigate-differential-failures">How to Use AI to Investigate Differential Failures</h2>
<p>Large migrations can produce hundreds or thousands of differences. And AI can help triage them.</p>
<p>Suppose you have:</p>
<pre><code class="language-json">{
  "input": {
    "subtotal": 5000,
    "country": "AR"
  },
  "legacy": {
    "total": 4500
  },
  "new": {
    "total": 4000
  }
}
</code></pre>
<p>You can give the model:</p>
<ul>
<li><p>the input</p>
</li>
<li><p>both outputs</p>
</li>
<li><p>relevant legacy code</p>
</li>
<li><p>relevant migrated code</p>
</li>
<li><p>the comparator rules</p>
</li>
</ul>
<p>Then ask:</p>
<pre><code class="language-text">Analyze this differential test failure.

Identify the smallest behavioral difference that could
explain the mismatch.

Compare the legacy and migrated implementations.

Return:

1. observed difference,
2. relevant legacy branch,
3. relevant migrated branch,
4. likely cause,
5. evidence supporting the cause,
6. additional test cases that could confirm it.

Do not decide which behavior is correct.
Do not modify the code yet.
</code></pre>
<p>That last instruction matters. AI can be very useful for locating why two implementations diverge. It shouldn't silently turn that diagnosis into a business decision.</p>
<h3 id="heading-dont-let-ai-decide-which-behavior-is-correct">Don't Let AI Decide Which Behavior Is Correct</h3>
<p>Imagine the legacy system does this:</p>
<pre><code class="language-text">Customer age 65 → no discount
Customer age 66 → discount
</code></pre>
<p>The new system does:</p>
<pre><code class="language-text">Customer age 65 → discount
Customer age 66 → discount
</code></pre>
<p>AI may look at the code and say:</p>
<blockquote>
<p>The new implementation appears more logical because senior discounts typically begin at age 65.</p>
</blockquote>
<p>That's irrelevant.</p>
<p>The business rule might be:</p>
<pre><code class="language-text">age &gt; 65
</code></pre>
<p>for a reason. Or the legacy behavior might contain a bug.</p>
<p>You need evidence.</p>
<p>Use:</p>
<pre><code class="language-text">requirements
existing tests
production behavior
business owners
historical tickets
commit history
contracts
</code></pre>
<p>AI can help gather and summarize that evidence. It shouldn't invent the rule.</p>
<p>Differential testing is valuable because it tells you that there's a difference before you accidentally turn that difference into production behavior.</p>
<h2 id="heading-how-to-use-shadow-traffic-safely">How to Use Shadow Traffic Safely</h2>
<p>Once offline differential tests look good, you can sometimes compare behavior with real traffic. This is often called shadowing or traffic mirroring.</p>
<p>The pattern looks like:</p>
<pre><code class="language-text">real request
    │
    ├────────────→ legacy system
    │                  │
    │                  ↓
    │             real response
    │
    └────────────→ new system
                       │
                       ↓
                  shadow result
</code></pre>
<p>The user still receives:</p>
<pre><code class="language-text">legacy response
</code></pre>
<p>while the new system processes a copy of the request.</p>
<p>Then you compare:</p>
<pre><code class="language-text">legacy output
vs.
shadow output
</code></pre>
<p>This can reveal cases that your test suite never captured.</p>
<p>For example:</p>
<pre><code class="language-text">unexpected null combinations
rare customer states
unusual international data
old records
large values
unusual sequence patterns
</code></pre>
<p>But shadow execution requires careful design, especially when the operation has side effects.</p>
<h3 id="heading-how-to-prevent-shadow-execution-from-duplicating-side-effects">How to Prevent Shadow Execution from Duplicating Side Effects</h3>
<p>Imagine shadowing:</p>
<pre><code class="language-text">POST /payments
</code></pre>
<p>If both systems really execute the payment, you have a serious problem.</p>
<p>The same applies to:</p>
<pre><code class="language-text">send email
create shipment
charge card
modify inventory
publish event
write external record
</code></pre>
<p>The shadow implementation shouldn't perform destructive or externally visible effects unless they're safely isolated.</p>
<p>One approach is to replace real gateways with recording adapters:</p>
<pre><code class="language-typescript">class ShadowPaymentGateway
  implements PaymentGateway {
  calls: PaymentRequest[] = [];

  async charge(
    request: PaymentRequest
  ) {
    this.calls.push(request);

    return {
      paymentId: "shadow",
    };
  }
}
</code></pre>
<p>The new implementation still tries to execute:</p>
<pre><code class="language-text">payment
</code></pre>
<p>but instead of charging a real card, the shadow adapter records:</p>
<pre><code class="language-text">what would have been sent
</code></pre>
<p>You can then compare that intent with the legacy side effect.</p>
<p>This distinction is important:</p>
<pre><code class="language-text">compare behavior
</code></pre>
<p>does not mean:</p>
<pre><code class="language-text">duplicate production effects
</code></pre>
<h2 id="heading-measure-divergence-instead-of-waiting-for-perfection">Measure Divergence Instead of Waiting for Perfection</h2>
<p>When running thousands of comparisons, a binary:</p>
<pre><code class="language-text">pass / fail
</code></pre>
<p>may not tell the whole story.</p>
<p>You can measure divergence.</p>
<p>For example:</p>
<pre><code class="language-text">Requests compared:     100,000
Equivalent:             99,620
Different:                 380

Divergence rate:          0.38%
</code></pre>
<p>Then classify those 380:</p>
<pre><code class="language-text">250 timestamp differences
80 known intentional changes
30 comparator problems
15 migration defects fixed
5 still unexplained
</code></pre>
<p>After normalization:</p>
<pre><code class="language-text">meaningful unresolved divergence:
5 / 100,000
= 0.005%
</code></pre>
<p>Now the conversation becomes much more concrete.</p>
<p>Instead of:</p>
<blockquote>
<p>I think the migration is ready.</p>
</blockquote>
<p>you can say:</p>
<blockquote>
<p>We compared 100,000 representative executions and have five unresolved behavioral differences.</p>
</blockquote>
<p>Whether that's acceptable depends on what those five cases are.</p>
<p>One incorrect financial transaction can matter more than 100 harmless formatting differences.</p>
<p>So don't evaluate only the percentage. Evaluate the severity.</p>
<h2 id="heading-how-to-know-when-youre-ready-for-cutover">How to Know When You're Ready for Cutover</h2>
<p>Differential testing doesn't give you a universal threshold. But it can give you evidence.</p>
<p>Before cutover, I would want to answer questions such as:</p>
<h3 id="heading-have-important-input-classes-been-compared">Have Important Input Classes Been Compared?</h3>
<p>Not only happy paths.</p>
<p>Include:</p>
<pre><code class="language-text">boundaries
errors
historical bugs
large values
missing values
rare states
</code></pre>
<h3 id="heading-are-meaningful-differences-classified">Are Meaningful Differences Classified?</h3>
<p>Avoid:</p>
<pre><code class="language-text">we have 47 unexplained mismatches
</code></pre>
<h3 id="heading-are-critical-differences-resolved">Are Critical Differences Resolved?</h3>
<p>Especially:</p>
<pre><code class="language-text">money
authorization
state transitions
data integrity
external contracts
idempotency
</code></pre>
<h3 id="heading-are-intentional-differences-documented">Are Intentional Differences Documented?</h3>
<p>If the new behavior intentionally differs, that should be explicit.</p>
<h3 id="heading-are-side-effects-equivalent">Are Side Effects Equivalent?</h3>
<p>Not only responses.</p>
<h3 id="heading-have-production-like-cases-been-tested">Have Production-like Cases Been Tested?</h3>
<p>Synthetic fixtures alone may not be enough.</p>
<h3 id="heading-can-the-migration-be-rolled-back">Can the Migration Be Rolled Back?</h3>
<p>Differential confidence reduces risk. It doesn't eliminate the need for rollback.</p>
<p>If you can answer these questions, you're much closer to a controlled cutover.</p>
<h2 id="heading-a-practical-differential-testing-workflow">A Practical Differential Testing Workflow</h2>
<p>Here's the workflow I would use.</p>
<h3 id="heading-1-pick-one-capability">1. Pick One Capability</h3>
<p>For example:</p>
<pre><code class="language-text">Process Order
Calculate Invoice
Approve Customer
</code></pre>
<p>Don't compare the whole platform at once.</p>
<h3 id="heading-2-define-the-observable-contract">2. Define the Observable Contract</h3>
<p>List what matters:</p>
<pre><code class="language-text">return value
status
error
database state
events
external calls
</code></pre>
<h3 id="heading-3-create-legacy-and-new-adapters">3. Create Legacy and New Adapters</h3>
<p>Expose both implementations through the same conceptual interface.</p>
<h3 id="heading-4-define-normalization-rules">4. Define Normalization Rules</h3>
<p>Decide how to handle:</p>
<pre><code class="language-text">timestamps
generated IDs
ordering
representation changes
optional values
</code></pre>
<p>Do this before looking at lots of failures. Otherwise you may weaken the comparator simply to make results pass.</p>
<h3 id="heading-5-compare-known-cases">5. Compare Known Cases</h3>
<p>Begin with:</p>
<pre><code class="language-text">existing tests
characterization cases
edge cases
historical bugs
</code></pre>
<h3 id="heading-6-capture-side-effects">6. Capture Side Effects</h3>
<p>Use recording or fake adapters where necessary.</p>
<h3 id="heading-7-automate-the-harness">7. Automate the Harness</h3>
<p>Produce structured output for every mismatch.</p>
<p>For example:</p>
<pre><code class="language-json">{
  "caseId": "case-493",
  "equivalent": false,
  "legacy": {},
  "migrated": {},
  "difference": {}
}
</code></pre>
<h3 id="heading-8-classify-differences">8. Classify Differences</h3>
<p>Use categories:</p>
<pre><code class="language-text">defect
intentional change
normalization issue
nondeterminism
unknown
</code></pre>
<h3 id="heading-9-add-representative-real-world-cases">9. Add Representative Real-World Cases</h3>
<p>Use anonymized or safely reconstructed production patterns.</p>
<h3 id="heading-10-shadow-real-traffic-when-appropriate">10. Shadow Real Traffic When Appropriate</h3>
<p>Only after controlling side effects and privacy risk.</p>
<h3 id="heading-11-measure-divergence">11. Measure Divergence</h3>
<p>Track both:</p>
<pre><code class="language-text">frequency
severity
</code></pre>
<h3 id="heading-12-resolve-unknowns-before-cutover">12. Resolve Unknowns Before Cutover</h3>
<p>The most dangerous category is often not:</p>
<pre><code class="language-text">different
</code></pre>
<p>It is:</p>
<pre><code class="language-text">different and nobody knows why
</code></pre>
<h2 id="heading-what-differential-testing-cant-prove">What Differential Testing Can't Prove</h2>
<p>Differential testing has an important limitation: it compares the new system against the old one.</p>
<p>That means the legacy system becomes a behavioral reference. But the legacy system may already be wrong.</p>
<p>Suppose:</p>
<pre><code class="language-text">legacy output = wrong
new output    = same wrong result
</code></pre>
<p>The differential test passes, but that doesn't make the behavior correct.</p>
<p>This is why differential testing should complement:</p>
<pre><code class="language-text">specification tests
characterization tests
business requirements
security testing
performance testing
contract testing
domain review
</code></pre>
<p>It answers:</p>
<blockquote>
<p>Did behavior change?</p>
</blockquote>
<p>It doesn't automatically answer:</p>
<blockquote>
<p>Is this the right behavior?</p>
</blockquote>
<p>That distinction matters. The legacy application is evidence, it's not absolute truth.</p>
<h2 id="heading-differential-testing-turns-migration-risk-into-evidence">Differential Testing Turns Migration Risk into Evidence</h2>
<p>There's another reason I like this technique. Without differential testing, migration discussions can become subjective.</p>
<p>One person says:</p>
<blockquote>
<p>The new implementation looks ready.</p>
</blockquote>
<p>Another says:</p>
<blockquote>
<p>I do not trust it yet.</p>
</blockquote>
<p>Both may have reasonable instincts, but neither statement is very measurable.</p>
<p>Differential testing changes the conversation.</p>
<p>Now you can say:</p>
<pre><code class="language-text">12,000 cases compared
47 differences found
31 representation differences
9 intentional behavior changes
6 migration defects fixed
1 unresolved
</code></pre>
<p>That is a much better engineering discussion. You're converting uncertainty into observable differences. Then you can decide what to do with them.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>A legacy migration is not complete because the new implementation passes its own tests.</p>
<p>The harder question is whether it preserves the behavior that matters from the system it is replacing.</p>
<p>Differential testing gives you another way to answer that question.</p>
<p>Run both implementations with the same inputs.</p>
<p>Compare outputs.</p>
<p>Compare errors.</p>
<p>Compare side effects.</p>
<p>Normalize only the differences that truly do not matter.</p>
<p>Investigate everything else.</p>
<p>And when possible, use representative production behavior to discover cases your test suite did not anticipate.</p>
<p>The migration sequence now becomes:</p>
<pre><code class="language-text">Understand
↓
Characterize
↓
Refactor
↓
Migrate
↓
Compare
↓
Cut over
</code></pre>
<p>AI can accelerate this process too.</p>
<p>It can help build comparators, analyze failures, group similar divergences, inspect code paths, and suggest additional test cases.</p>
<p>But it should not decide which implementation is correct.</p>
<p>That still requires evidence, domain knowledge, and engineering judgment.</p>
<p>The purpose of differential testing is not to eliminate uncertainty completely.</p>
<p>It is to make uncertainty visible <strong>before</strong> you switch production traffic.</p>
<p>Because during a migration, discovering that the new system behaves differently is useful.</p>
<p>Discovering it after the old system has been turned off is much more expensive.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Characterization Tests Before Refactoring Legacy Code ]]>
                </title>
                <description>
                    <![CDATA[ The first thing many engineers want to do when they inherit legacy code is improve it. You find a function that's difficult to understand. Or you see duplicated logic, deeply nested conditions, databa ]]>
                </description>
                <link>https://www.freecodecamp.org/news/characterization-tests-before-refactoring-legacy-code/</link>
                <guid isPermaLink="false">6a958d8401db3f18f07d0b54</guid>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ refactoring ]]>
                    </category>
                
                    <category>
                        <![CDATA[ legacy code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hugo Teijiz ]]>
                </dc:creator>
                <pubDate>Mon, 31 Aug 2026 14:19:48 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/50ec1fe6-8e1f-4c42-a8ad-fd52ea0d089d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The first thing many engineers want to do when they inherit legacy code is improve it.</p>
<p>You find a function that's difficult to understand. Or you see duplicated logic, deeply nested conditions, database calls mixed with business rules, and dependencies that make testing almost impossible.</p>
<p>You know the code could be better, so you start cleaning it up.</p>
<p>Then something breaks. Not because the new implementation is obviously wrong. It breaks because the old implementation was doing something nobody knew it was doing.</p>
<p>That's one of the most common risks in legacy modernization.</p>
<p>Before changing code, you need a way to answer a simple question:</p>
<blockquote>
<p>Did I preserve the behavior that already mattered?</p>
</blockquote>
<p>That is where characterization tests become useful.</p>
<p>A characterization test doesn't begin by asking what the software <strong>should</strong> do. It begins by documenting what the software <strong>does today</strong>.</p>
<p>That distinction matters.</p>
<p>In a greenfield application, tests usually express intended behavior. But in a legacy application, you may first need tests that capture existing behavior so you can change the implementation without accidentally changing its observable results.</p>
<p>In this tutorial, I'll show you how to use characterization tests as a safety net before refactoring legacy code.</p>
<p>We'll look at how to:</p>
<ul>
<li><p>identify behavior worth protecting,</p>
</li>
<li><p>choose useful test boundaries,</p>
</li>
<li><p>capture current outputs,</p>
</li>
<li><p>deal with side effects,</p>
</li>
<li><p>handle databases and external systems,</p>
</li>
<li><p>use AI to accelerate test discovery,</p>
</li>
<li><p>avoid freezing implementation details,</p>
</li>
<li><p>decide what not to characterize,</p>
</li>
<li><p>and turn characterization tests into a foundation for safer refactoring.</p>
</li>
</ul>
<p>The examples use TypeScript and Vitest, but the approach applies to most languages and testing frameworks.</p>
<p>The goal isn't to preserve every line of legacy behavior forever. The goal is to make behavior visible before you start changing the code that produces it.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should be comfortable with:</p>
<ul>
<li><p>TypeScript or a similar programming language</p>
</li>
<li><p>unit and integration testing</p>
</li>
<li><p>dependency injection</p>
</li>
<li><p>mocks and test doubles</p>
</li>
<li><p>basic refactoring techniques</p>
</li>
<li><p>reading an unfamiliar codebase</p>
</li>
</ul>
<p>It also helps if you've already mapped the capability you want to change.</p>
<p>Before writing characterization tests, you should have some idea of:</p>
<ul>
<li><p>where the behavior starts</p>
</li>
<li><p>what state it changes</p>
</li>
<li><p>which external systems it touches</p>
</li>
<li><p>which outputs may be consumed elsewhere</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-characterization-tests-actually-protect">What Characterization Tests Actually Protect</a></p>
</li>
<li><p><a href="#heading-start-with-behavior-not-implementation">Start with Behavior, Not Implementation</a></p>
</li>
<li><p><a href="#heading-choose-one-capability-before-writing-tests">Choose One Capability Before Writing Tests</a></p>
</li>
<li><p><a href="#heading-find-the-smallest-useful-test-boundary">Find the Smallest Useful Test Boundary</a></p>
</li>
<li><p><a href="#heading-capture-existing-behavior-before-improving-it">Capture Existing Behavior Before Improving It</a></p>
</li>
<li><p><a href="#heading-characterize-edge-cases-you-dont-yet-understand">Characterize Edge Cases You Don't Yet Understand</a></p>
</li>
<li><p><a href="#heading-test-side-effects-not-just-return-values">Test Side Effects, Not Just Return Values</a></p>
</li>
<li><p><a href="#heading-how-to-characterize-code-that-depends-on-a-database">How to Characterize Code That Depends on a Database</a></p>
</li>
<li><p><a href="#heading-how-to-handle-external-services">How to Handle External Services</a></p>
</li>
<li><p><a href="#heading-how-to-use-ai-to-discover-characterization-tests">How to Use AI to Discover Characterization Tests</a></p>
</li>
<li><p><a href="#heading-dont-let-ai-invent-expected-behavior">Don't Let AI Invent Expected Behavior</a></p>
</li>
<li><p><a href="#heading-avoid-testing-implementation-details">Avoid Testing Implementation Details</a></p>
</li>
<li><p><a href="#heading-when-a-characterization-test-reveals-a-bug">When a Characterization Test Reveals a Bug</a></p>
</li>
<li><p><a href="#heading-how-much-behavior-should-you-characterize">How Much Behavior Should You Characterize</a></p>
</li>
<li><p><a href="#heading-use-characterization-tests-during-the-refactor">Use Characterization Tests During the Refactor</a></p>
</li>
<li><p><a href="#heading-a-practical-characterization-testing-workflow">A Practical Characterization Testing Workflow</a></p>
</li>
<li><p><a href="#heading-what-characterization-tests-cant-tell-you">What Characterization Tests Can't Tell You</a></p>
</li>
<li><p><a href="#heading-characterization-tests-are-temporary-knowledge-infrastructure">Characterization Tests Are Temporary Knowledge Infrastructure</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-characterization-tests-actually-protect">What Characterization Tests Actually Protect</h2>
<p>Suppose you inherit this function:</p>
<pre><code class="language-typescript">type Customer = {
  id: string;
  type: "STANDARD" | "PREMIUM";
};

type Order = {
  id: string;
  customer: Customer;
  subtotal: number;
  country: string;
  paymentMethod: "CARD" | "TRANSFER";
};

async function processOrder(order: Order) {
  let total = order.subtotal;

  if (order.customer.type === "PREMIUM") {
    total = total * 0.9;
  }

  if (order.country === "AR" &amp;&amp; order.paymentMethod === "TRANSFER") {
    total = total - 500;
  }

  if (total &lt; 0) {
    total = 0;
  }

  await ordersRepository.save({
    ...order,
    total,
    status: "PROCESSED",
  });

  await eventBus.publish("order.processed", {
    orderId: order.id,
    total,
  });

  return total;
}
</code></pre>
<p>There are several things you may want to refactor here.</p>
<p>For example, the pricing rules could move to another module. Persistence could be isolated. The event publisher could sit behind an interface. And the function could return an object rather than a primitive.</p>
<p>Those may all be good decisions, but before making them, you should ask: What behavior currently matters?</p>
<p>For this function, observable behavior includes at least:</p>
<ul>
<li><p>premium customers receive a 10% discount</p>
</li>
<li><p>Argentine transfers receive another adjustment</p>
</li>
<li><p>totals can't become negative</p>
</li>
<li><p>the order is persisted with a specific status</p>
</li>
<li><p>an event is published</p>
</li>
<li><p>the event contains the calculated total</p>
</li>
<li><p>the function returns that total</p>
</li>
</ul>
<p>A characterization test gives you a baseline for those behaviors.</p>
<p>For example:</p>
<pre><code class="language-typescript">import { describe, expect, it, vi } from "vitest";

describe("processOrder", () =&gt; {
  it("applies the existing premium customer behavior", async () =&gt; {
    const save = vi.spyOn(ordersRepository, "save");
    const publish = vi.spyOn(eventBus, "publish");

    const order: Order = {
      id: "order-1",
      customer: {
        id: "customer-1",
        type: "PREMIUM",
      },
      subtotal: 10000,
      country: "US",
      paymentMethod: "CARD",
    };

    const result = await processOrder(order);

    expect(result).toBe(9000);

    expect(save).toHaveBeenCalledWith(
      expect.objectContaining({
        id: "order-1",
        total: 9000,
        status: "PROCESSED",
      })
    );

    expect(publish).toHaveBeenCalledWith("order.processed", {
      orderId: "order-1",
      total: 9000,
    });
  });
});
</code></pre>
<p>This test isn't saying that a 10% discount is the best pricing model.</p>
<p>It's saying:</p>
<blockquote>
<p>This is what the system currently does.</p>
</blockquote>
<p>That's the contract you need to understand before changing it.</p>
<h2 id="heading-start-with-behavior-not-implementation">Start with Behavior, Not Implementation</h2>
<p>A common mistake is to write tests around the structure you're planning to create.</p>
<p>Suppose you want to refactor the previous code into:</p>
<pre><code class="language-text">OrderProcessor
PricingPolicy
OrdersRepository
OrderEventPublisher
</code></pre>
<p>You may be tempted to write tests for those future classes first.</p>
<p>But those classes don't describe the existing system. They describe your proposed design.</p>
<p>Characterization tests should begin at the current observable boundary.</p>
<p>Instead of asking:</p>
<blockquote>
<p>How should <code>PricingPolicy</code> work?</p>
</blockquote>
<p>ask:</p>
<blockquote>
<p>Given this input, what does <code>processOrder()</code> currently produce?</p>
</blockquote>
<p>That difference helps prevent your new architecture from redefining behavior accidentally.</p>
<p>The sequence should be:</p>
<pre><code class="language-text">Observe existing behavior
↓
Capture it
↓
Refactor implementation
↓
Run characterization tests
↓
Verify behavior remains stable
</code></pre>
<p>Not:</p>
<pre><code class="language-text">Design new architecture
↓
Write tests for new architecture
↓
Assume it matches the old system
</code></pre>
<p>The second workflow tests your design. The first protects the migration.</p>
<h2 id="heading-choose-one-capability-before-writing-tests">Choose One Capability Before Writing Tests</h2>
<p>Don't start by trying to characterize an entire legacy application. Instead, pick one business capability.</p>
<p>For example:</p>
<pre><code class="language-text">Approve Order
Generate Invoice
Renew Subscription
Register Customer
Calculate Commission
Cancel Reservation
</code></pre>
<p>Then trace that capability through the system.</p>
<p>Suppose you choose:</p>
<blockquote>
<p>Generate Invoice</p>
</blockquote>
<p>You discover this path:</p>
<pre><code class="language-text">POST /orders/:id/invoice
        ↓
InvoiceController.generate()
        ↓
InvoiceService.generate()
        ↓
TaxCalculator.calculate()
        ↓
InvoiceRepository.save()
        ↓
PdfGenerator.create()
        ↓
EmailService.send()
</code></pre>
<p>That becomes the scope of your investigation.</p>
<p>Now ask: Which behaviors matter if I refactor this capability?</p>
<p>Perhaps:</p>
<pre><code class="language-text">tax calculation
invoice numbering
database state
PDF fields
email recipient
email attachment
error behavior
</code></pre>
<p>Those are candidates for characterization.</p>
<p>This is more useful than trying to increase test coverage across the repository indiscriminately.</p>
<p>Coverage isn't the goal. Behavioral confidence is.</p>
<h2 id="heading-find-the-smallest-useful-test-boundary">Find the Smallest Useful Test Boundary</h2>
<p>Characterization tests can exist at different levels.</p>
<p>You might test:</p>
<pre><code class="language-text">function
service
module
API endpoint
background job
complete workflow
</code></pre>
<p>The right boundary is usually the smallest one that still captures meaningful behavior.</p>
<p>Suppose the logic you want to refactor lives inside:</p>
<pre><code class="language-typescript">class InvoiceService {
  async generate(orderId: string) {
    // 300 lines of legacy behavior
  }
}
</code></pre>
<p>If <code>generate()</code> coordinates tax calculation, persistence, numbering, and external calls, testing a small internal helper may not protect enough behavior.</p>
<p>Testing the whole production stack may be too slow and difficult.</p>
<p>A service-level characterization test may be the useful compromise.</p>
<p>For example:</p>
<pre><code class="language-typescript">describe("InvoiceService.generate", () =&gt; {
  it("preserves the existing invoice calculation", async () =&gt; {
    const service = createInvoiceService();

    const invoice = await service.generate("order-123");

    expect(invoice.subtotal).toBe(10000);
    expect(invoice.tax).toBe(2100);
    expect(invoice.total).toBe(12100);
  });
});
</code></pre>
<p>You don't want to ask what's the smallest unit you can test. You want to ask what's the smallest boundary that gives you confidence during this refactor.</p>
<p>Those aren't always the same thing.</p>
<h2 id="heading-capture-existing-behavior-before-improving-it">Capture Existing Behavior Before Improving It</h2>
<p>Legacy code often contains behavior that looks suspicious.</p>
<p>Consider:</p>
<pre><code class="language-typescript">function calculateDiscount(amount: number) {
  if (amount &gt; 10000) {
    return amount * 0.15;
  }

  if (amount &gt; 5000) {
    return amount * 0.1;
  }

  return 0;
}
</code></pre>
<p>You run a few examples and discover:</p>
<pre><code class="language-text">5000  -&gt; 0
5001  -&gt; 500.1
10000 -&gt; 1000
10001 -&gt; 1500.15
</code></pre>
<p>You might think:</p>
<blockquote>
<p><code>5000</code> should probably receive the 10% discount.</p>
</blockquote>
<p>Maybe. But that's not what the current code does.</p>
<p>A characterization test could record:</p>
<pre><code class="language-typescript">describe("calculateDiscount", () =&gt; {
  it.each([
    [5000, 0],
    [5001, 500.1],
    [10000, 1000],
    [10001, 1500.15],
  ])(
    "returns the existing discount for amount %d",
    (amount, expected) =&gt; {
      expect(calculateDiscount(amount)).toBe(expected);
    }
  );
});
</code></pre>
<p>This creates a behavioral boundary around the existing implementation.</p>
<p>Later, if the business confirms that <code>5000</code> should receive a discount, you can intentionally change:</p>
<pre><code class="language-typescript">if (amount &gt; 5000)
</code></pre>
<p>to:</p>
<pre><code class="language-typescript">if (amount &gt;= 5000)
</code></pre>
<p>and update the relevant test.</p>
<p>The important part is that the change becomes explicit.</p>
<p>Without the test, it could happen accidentally during an unrelated refactor.</p>
<h2 id="heading-characterize-edge-cases-you-dont-yet-understand">Characterize Edge Cases You Don't Yet Understand</h2>
<p>The obvious cases aren't always the risky ones. Legacy systems often fail at boundaries.</p>
<p>Look for values such as:</p>
<pre><code class="language-text">0
-1
null
empty string
maximum value
minimum value
exact threshold values
unknown status
duplicate identifiers
missing related records
</code></pre>
<p>Suppose you find:</p>
<pre><code class="language-typescript">function normalizeBalance(balance?: number) {
  if (!balance) {
    return 0;
  }

  return Math.round(balance * 100) / 100;
}
</code></pre>
<p>That means:</p>
<pre><code class="language-text">undefined -&gt; 0
0         -&gt; 0
</code></pre>
<p>But also potentially:</p>
<pre><code class="language-text">NaN -&gt; 0
</code></pre>
<p>because <code>NaN</code> is falsy.</p>
<p>Is that intentional? You may not know yet.</p>
<p>You can characterize it:</p>
<pre><code class="language-typescript">describe("normalizeBalance", () =&gt; {
  it("returns zero for undefined", () =&gt; {
    expect(normalizeBalance(undefined)).toBe(0);
  });

  it("returns zero for zero", () =&gt; {
    expect(normalizeBalance(0)).toBe(0);
  });

  it("returns zero for NaN in the current implementation", () =&gt; {
    expect(normalizeBalance(Number.NaN)).toBe(0);
  });
});
</code></pre>
<p>The name matters.</p>
<p>Notice that I wrote:</p>
<blockquote>
<p>in the current implementation</p>
</blockquote>
<p>I'm not pretending that behavior is correct. I'm just documenting it.</p>
<p>That distinction becomes important when a test describes questionable behavior.</p>
<h2 id="heading-test-side-effects-not-just-return-values">Test Side Effects, Not Just Return Values</h2>
<p>A return value is only one kind of behavior.</p>
<p>Legacy functions frequently produce side effects.</p>
<p>Consider:</p>
<pre><code class="language-typescript">async function cancelOrder(order: Order) {
  order.status = "CANCELLED";

  await orders.save(order);
  await inventory.release(order.id);
  await audit.log("ORDER_CANCELLED", order.id);

  return order;
}
</code></pre>
<p>A weak characterization test might only check:</p>
<pre><code class="language-typescript">expect(result.status).toBe("CANCELLED");
</code></pre>
<p>But a refactor could still accidentally remove:</p>
<pre><code class="language-text">inventory.release()
audit.log()
</code></pre>
<p>and the test would continue passing.</p>
<p>A stronger characterization test captures observable side effects:</p>
<pre><code class="language-typescript">it("preserves cancellation side effects", async () =&gt; {
  const save = vi.spyOn(orders, "save");
  const release = vi.spyOn(inventory, "release");
  const log = vi.spyOn(audit, "log");

  const order = {
    id: "order-1",
    status: "APPROVED",
  } as Order;

  await cancelOrder(order);

  expect(save).toHaveBeenCalled();

  expect(release).toHaveBeenCalledWith("order-1");

  expect(log).toHaveBeenCalledWith(
    "ORDER_CANCELLED",
    "order-1"
  );
});
</code></pre>
<p>This doesn't mean every internal call deserves an assertion.</p>
<p>The question is whether the call produces observable behavior that matters outside the implementation.</p>
<h2 id="heading-how-to-characterize-code-that-depends-on-a-database">How to Characterize Code That Depends on a Database</h2>
<p>Database-heavy legacy code can be difficult to test.</p>
<p>Suppose you have:</p>
<pre><code class="language-typescript">async function activateCustomer(customerId: string) {
  const customer = await db.customers.findById(customerId);

  if (!customer) {
    throw new Error("Customer not found");
  }

  await db.customers.update(customerId, {
    status: "ACTIVE",
    activatedAt: new Date(),
  });

  return db.customers.findById(customerId);
}
</code></pre>
<p>You have several options.</p>
<h3 id="heading-use-an-integration-test">Use an Integration Test</h3>
<p>If the database behavior itself matters, run against a disposable test database.</p>
<p>For example:</p>
<pre><code class="language-typescript">it("activates an existing customer", async () =&gt; {
  await seedCustomer({
    id: "customer-1",
    status: "PENDING",
  });

  const result = await activateCustomer("customer-1");

  expect(result?.status).toBe("ACTIVE");
  expect(result?.activatedAt).toBeTruthy();
});
</code></pre>
<p>This gives high confidence, but the test may be slower.</p>
<h3 id="heading-introduce-a-seam">Introduce a Seam</h3>
<p>If database access makes testing impractical, you may need a very small structural change before characterization.</p>
<p>For example:</p>
<pre><code class="language-typescript">type CustomerRepository = {
  findById(id: string): Promise&lt;Customer | null&gt;;
  update(
    id: string,
    data: Partial&lt;Customer&gt;
  ): Promise&lt;void&gt;;
};
</code></pre>
<p>Then:</p>
<pre><code class="language-typescript">async function activateCustomer(
  customerId: string,
  customers: CustomerRepository
) {
  // existing behavior
}
</code></pre>
<p>This is a useful concept from legacy-code work: create a <strong>seam</strong>, a place where behavior can be observed or replaced without rewriting the system.</p>
<p>The key is to keep this preparatory change mechanical.</p>
<p>Don't redesign the business logic while creating the test boundary.</p>
<p>First make it testable. Then characterize it. Then refactor.</p>
<h2 id="heading-how-to-handle-external-services">How to Handle External Services</h2>
<p>Legacy code frequently talks directly to:</p>
<pre><code class="language-text">payment providers
email services
ERPs
CRMs
message brokers
cloud storage
third-party APIs
</code></pre>
<p>You usually don't want characterization tests repeatedly calling those systems.</p>
<p>Instead, capture the interaction at the boundary.</p>
<p>Suppose:</p>
<pre><code class="language-typescript">async function chargeOrder(order: Order) {
  const response = await stripe.charge({
    amount: order.total,
    currency: "usd",
    customerId: order.customerId,
  });

  await orders.markPaid(order.id, response.id);

  return response.id;
}
</code></pre>
<p>You can characterize the request:</p>
<pre><code class="language-typescript">it("sends the existing payment payload", async () =&gt; {
  const charge = vi
    .spyOn(stripe, "charge")
    .mockResolvedValue({
      id: "payment-123",
    });

  const markPaid = vi.spyOn(orders, "markPaid");

  const order = {
    id: "order-1",
    total: 5000,
    customerId: "customer-1",
  } as Order;

  await chargeOrder(order);

  expect(charge).toHaveBeenCalledWith({
    amount: 5000,
    currency: "usd",
    customerId: "customer-1",
  });

  expect(markPaid).toHaveBeenCalledWith(
    "order-1",
    "payment-123"
  );
});
</code></pre>
<p>That protects the external contract without hitting the external system.</p>
<p>But be careful. If the provider behavior itself matters, mocks alone may not be enough.</p>
<p>You might also need:</p>
<ul>
<li><p>provider sandbox tests</p>
</li>
<li><p>contract tests</p>
</li>
<li><p>integration tests</p>
</li>
<li><p>schema validation</p>
</li>
</ul>
<p>Characterization testing doesn't eliminate the need for those layers.</p>
<h2 id="heading-how-to-use-ai-to-discover-characterization-tests">How to Use AI to Discover Characterization Tests</h2>
<p>AI is particularly useful when you are staring at a large legacy function and trying to understand what deserves a test.</p>
<p>Suppose you have a 400-line service.</p>
<p>Instead of asking:</p>
<pre><code class="language-text">Write unit tests for this class.
</code></pre>
<p>use a more investigative prompt:</p>
<pre><code class="language-text">Analyze this class without changing it.

Identify observable behaviors that could change during refactoring.

Group them into:

1. returned values,
2. state changes,
3. persistence effects,
4. external calls,
5. emitted events,
6. exceptions,
7. boundary conditions.

For every proposed characterization test:

- reference the relevant source code,
- explain what behavior the test would protect,
- distinguish observed behavior from inferred behavior.

Do not invent expected values.
</code></pre>
<p>That final instruction matters: you want AI to help identify <strong>what to observe</strong>. You don't want it inventing what the software should do.</p>
<p>Another useful prompt is:</p>
<pre><code class="language-text">Review the existing test suite for this capability.

Compare the behaviors covered by tests with the
observable behaviors in the implementation.

List behavior that appears unprotected.

Do not generate tests yet.
</code></pre>
<p>This is often more valuable than immediately asking for test code.</p>
<p>First identify the gaps, and then decide which gaps matter.</p>
<h2 id="heading-dont-let-ai-invent-expected-behavior">Don't Let AI Invent Expected Behavior</h2>
<p>This is probably the most important rule when combining AI with characterization testing, and it's worth talking a bit more about.</p>
<p>Suppose AI reads:</p>
<pre><code class="language-typescript">if (customer.age &gt; 65) {
  discount = 0.2;
}
</code></pre>
<p>It may generate:</p>
<pre><code class="language-typescript">expect(calculateDiscount(65)).toBe(0.2);
</code></pre>
<p>because it assumes the intended business rule is:</p>
<blockquote>
<p>Customers aged 65 or older receive a discount.</p>
</blockquote>
<p>But that's not what the code says.</p>
<p>The existing behavior is:</p>
<pre><code class="language-text">65 -&gt; no discount
66 -&gt; discount
</code></pre>
<p>The expected values in characterization tests should come from evidence.</p>
<p>Useful evidence includes:</p>
<ul>
<li><p>running the current system</p>
</li>
<li><p>existing tests</p>
</li>
<li><p>fixtures</p>
</li>
<li><p>production-safe observations</p>
</li>
<li><p>documented examples</p>
</li>
<li><p>database state</p>
</li>
<li><p>historical behavior</p>
</li>
</ul>
<p>Don't derive expectations solely from what seems reasonable.</p>
<p>A better AI instruction is:</p>
<pre><code class="language-text">For each candidate test, tell me how I can obtain
the expected result from the current implementation.

Do not propose the expected result yourself unless it
can be directly derived from executable behavior
or an existing test.
</code></pre>
<p>This turns AI into an assistant for experiment design rather than an authority on business rules.</p>
<h2 id="heading-avoid-testing-implementation-details">Avoid Testing Implementation Details</h2>
<p>Characterization tests can become harmful if they freeze the current code structure.</p>
<p>Suppose the implementation is:</p>
<pre><code class="language-typescript">async function processOrder(order: Order) {
  validateOrder(order);
  calculatePrice(order);
  reserveInventory(order);
  saveOrder(order);
}
</code></pre>
<p>A brittle test might assert:</p>
<pre><code class="language-typescript">expect(validateOrder).toHaveBeenCalledBefore(calculatePrice);
expect(calculatePrice).toHaveBeenCalledBefore(reserveInventory);
expect(reserveInventory).toHaveBeenCalledBefore(saveOrder);
</code></pre>
<p>Maybe that order matters. Maybe it doesn't.</p>
<p>If consumers only care about:</p>
<pre><code class="language-text">correct total
inventory reserved
order persisted
</code></pre>
<p>then asserting the exact sequence unnecessarily constrains the refactor.</p>
<p>Prefer protecting externally meaningful behavior.</p>
<p>For example:</p>
<pre><code class="language-typescript">expect(savedOrder.total).toBe(9000);
expect(inventory.reserve).toHaveBeenCalledWith(
  "product-1",
  2
);
expect(repository.save).toHaveBeenCalled();
</code></pre>
<p>Characterization tests should create a safety net. They shouldn't turn the legacy implementation into a specification of every internal decision.</p>
<h2 id="heading-when-a-characterization-test-reveals-a-bug">When a Characterization Test Reveals a Bug</h2>
<p>Eventually you'll encounter behavior that appears clearly wrong.</p>
<p>For example:</p>
<pre><code class="language-typescript">function calculateFee(amount: number) {
  if (amount === 0) {
    return 100;
  }

  return amount * 0.02;
}
</code></pre>
<p>You confirm that zero-value transactions are charged a fixed fee. Everyone agrees this looks suspicious.</p>
<p>What should the characterization test do?</p>
<p>First, separate two questions:</p>
<ol>
<li><p>What does the system do today?</p>
</li>
<li><p>What should the system do?</p>
</li>
</ol>
<p>The characterization test answers the first.</p>
<pre><code class="language-typescript">it("currently charges 100 for a zero-value transaction", () =&gt; {
  expect(calculateFee(0)).toBe(100);
});
</code></pre>
<p>Then investigate whether this is intentional business behavior, a historical workaround, or an actual defect.</p>
<p>If the business confirms it is a bug, create a separate change.</p>
<p>For example:</p>
<pre><code class="language-typescript">it("does not charge a fee for a zero-value transaction", () =&gt; {
  expect(calculateFee(0)).toBe(0);
});
</code></pre>
<p>Then modify the production code.</p>
<p>This may sound overly formal for a small condition. But it creates a clean distinction between:</p>
<pre><code class="language-text">behavior discovered during refactoring
</code></pre>
<p>and:</p>
<pre><code class="language-text">behavior intentionally changed
</code></pre>
<p>That distinction becomes extremely valuable in large migrations.</p>
<h2 id="heading-how-much-behavior-should-you-characterize">How Much Behavior Should You Characterize</h2>
<p>You don't need to characterize everything. Trying to preserve every observed detail can create another form of paralysis.</p>
<p>Prioritize behavior with high change risk or high business impact.</p>
<p>I usually look first at:</p>
<ul>
<li><p>financial calculations</p>
</li>
<li><p>state transitions</p>
</li>
<li><p>authentication and authorization</p>
</li>
<li><p>external contracts</p>
</li>
<li><p>queue and event payloads</p>
</li>
<li><p>data transformations</p>
</li>
<li><p>retry behavior</p>
</li>
<li><p>idempotency</p>
</li>
<li><p>regulatory rules</p>
</li>
<li><p>critical error handling</p>
</li>
</ul>
<p>You may care less about:</p>
<ul>
<li><p>internal helper naming</p>
</li>
<li><p>private method structure</p>
</li>
<li><p>log wording that nobody consumes</p>
</li>
<li><p>temporary object shapes</p>
</li>
<li><p>implementation-specific call sequences</p>
</li>
</ul>
<p>A useful question is: If this behavior changed during refactoring, could somebody outside this function notice?</p>
<p>If the answer is yes, it's probably worth considering.</p>
<h2 id="heading-use-characterization-tests-during-the-refactor">Use Characterization Tests During the Refactor</h2>
<p>Once the characterization suite exists, keep the refactor small.</p>
<p>Suppose you begin with:</p>
<pre><code class="language-typescript">async function processOrder(order: Order) {
  // validation
  // pricing
  // inventory
  // persistence
  // event publishing
}
</code></pre>
<p>You might first extract pricing:</p>
<pre><code class="language-typescript">function calculateOrderTotal(order: Order) {
  let total = order.subtotal;

  if (order.customer.type === "PREMIUM") {
    total *= 0.9;
  }

  if (
    order.country === "AR" &amp;&amp;
    order.paymentMethod === "TRANSFER"
  ) {
    total -= 500;
  }

  return Math.max(total, 0);
}
</code></pre>
<p>Run the characterization suite. If everything still passes, continue.</p>
<p>Next isolate inventory and run it again.</p>
<p>Then persistence. Run it again.</p>
<p>This gives you a migration rhythm:</p>
<pre><code class="language-text">small structural change
↓
run tests
↓
observe
↓
continue
</code></pre>
<p>If something fails, the search space is small.</p>
<p>Compare that with rewriting 2,000 lines and then discovering 47 broken tests.</p>
<p>Small changes turn failures into useful feedback, while Large changes turn failures into archaeology.</p>
<p>Again.</p>
<h2 id="heading-a-practical-characterization-testing-workflow">A Practical Characterization Testing Workflow</h2>
<p>Here is the workflow I would use on an unfamiliar legacy capability.</p>
<h3 id="heading-1-map-the-capability">1. Map the Capability</h3>
<p>Identify:</p>
<pre><code class="language-text">entry point
business logic
state changes
side effects
external contracts
outputs
</code></pre>
<p>Don't refactor yet.</p>
<h3 id="heading-2-find-existing-tests">2. Find Existing Tests</h3>
<p>Search for tests that already describe the capability.</p>
<p>Look for:</p>
<pre><code class="language-text">happy paths
boundary cases
errors
historical bugs
integration behavior
</code></pre>
<p>Don't duplicate useful tests unnecessarily.</p>
<h3 id="heading-3-list-observable-behaviors">3. List Observable Behaviors</h3>
<p>Create a table such as:</p>
<table>
<thead>
<tr>
<th>Behavior</th>
<th>Evidence</th>
<th>Protected?</th>
</tr>
</thead>
<tbody><tr>
<td>Premium discount</td>
<td>Code + production example</td>
<td>No</td>
</tr>
<tr>
<td>Order event</td>
<td>Code</td>
<td>Yes</td>
</tr>
<tr>
<td>Transfer adjustment</td>
<td>Code</td>
<td>No</td>
</tr>
<tr>
<td>Negative total clamp</td>
<td>Code</td>
<td>No</td>
</tr>
<tr>
<td>Save status</td>
<td>Existing integration test</td>
<td>Yes</td>
</tr>
</tbody></table>
<p>Now you know where the risk is.</p>
<h3 id="heading-4-pick-the-test-boundary">4. Pick the Test Boundary</h3>
<p>Decide whether the useful boundary is:</p>
<pre><code class="language-text">function
service
module
endpoint
job
workflow
</code></pre>
<p>Choose based on confidence, not test ideology.</p>
<h3 id="heading-5-capture-current-behavior">5. Capture Current Behavior</h3>
<p>Run the existing system.</p>
<p>Use real observable outputs when possible.</p>
<p>Don't guess expectations.</p>
<h3 id="heading-6-add-critical-edge-cases">6. Add Critical Edge Cases</h3>
<p>Test:</p>
<pre><code class="language-text">thresholds
empty values
nulls
errors
duplicate operations
retry scenarios
</code></pre>
<p>especially around logic you intend to change.</p>
<h3 id="heading-7-capture-side-effects">7. Capture Side Effects</h3>
<p>Protect meaningful:</p>
<pre><code class="language-text">writes
events
messages
external calls
state transitions
</code></pre>
<p>not only function return values.</p>
<h3 id="heading-8-mark-uncertain-behavior">8. Mark Uncertain Behavior</h3>
<p>Use test names or documentation that clearly distinguishes:</p>
<pre><code class="language-text">confirmed business rule
</code></pre>
<p>from:</p>
<pre><code class="language-text">current observed behavior
</code></pre>
<h3 id="heading-9-refactor-incrementally">9. Refactor Incrementally</h3>
<p>Make one structural change.</p>
<p>Run the suite.</p>
<p>Repeat.</p>
<h3 id="heading-10-replace-characterization-with-intent-where-appropriate">10. Replace Characterization with Intent Where Appropriate</h3>
<p>As understanding improves, some characterization tests can evolve into true specification tests.</p>
<p>Instead of:</p>
<pre><code class="language-text">currently returns 0 for this input
</code></pre>
<p>you may eventually be able to say:</p>
<pre><code class="language-text">does not apply a discount below the premium threshold
</code></pre>
<p>That transition is useful. It means the system is becoming understood rather than merely preserved.</p>
<h2 id="heading-what-characterization-tests-cant-tell-you">What Characterization Tests Can't Tell You</h2>
<p>Characterization tests are powerful, but they have an important limitation.</p>
<p>They tell you what happened for the cases you observed. They don't automatically tell you why.</p>
<p>Suppose the test says:</p>
<pre><code class="language-text">Argentine transfer orders receive a 500-unit adjustment.
</code></pre>
<p>The test can protect that behavior.</p>
<p>It can't tell you whether the adjustment exists because of:</p>
<ul>
<li><p>a tax rule</p>
</li>
<li><p>a banking fee</p>
</li>
<li><p>an old promotion</p>
</li>
<li><p>a customer-specific workaround</p>
</li>
<li><p>a bug nobody removed</p>
</li>
</ul>
<p>For that, you still need other evidence:</p>
<ul>
<li><p>documentation</p>
</li>
<li><p>Git history</p>
</li>
<li><p>production telemetry</p>
</li>
<li><p>domain experts</p>
</li>
<li><p>incident records</p>
</li>
<li><p>external system contracts</p>
</li>
</ul>
<p>This is why characterization testing belongs after codebase understanding, not instead of it.</p>
<p>You first discover the behavior. Then you protect it. Then you continue investigating what it means.</p>
<h2 id="heading-characterization-tests-are-temporary-knowledge-infrastructure">Characterization Tests Are Temporary Knowledge Infrastructure</h2>
<p>There's another way I think about these tests.</p>
<p>Legacy systems contain knowledge that's often trapped inside implementation details. A characterization test moves some of that knowledge into an executable form.</p>
<p>Before:</p>
<pre><code class="language-text">Nobody knows what changing this condition will break.
</code></pre>
<p>After:</p>
<pre><code class="language-text">Changing this condition causes these four observable behaviors to change.
</code></pre>
<p>That's already progress.</p>
<p>The test suite becomes part of your understanding of the system. It creates a bridge between:</p>
<pre><code class="language-text">what the code currently does
</code></pre>
<p>and:</p>
<pre><code class="language-text">what we eventually want the system to do
</code></pre>
<p>You don't have to keep every characterization test forever.</p>
<p>Some will become proper specification tests.</p>
<p>Some will disappear when obsolete behavior is intentionally removed.</p>
<p>Some will remain as regression tests.</p>
<p>Their first job is simpler: <strong>make change safer while understanding is still incomplete.</strong></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>AI makes refactoring legacy code faster.</p>
<p>It can explain functions, generate candidate abstractions, extract interfaces, suggest module boundaries, and rewrite large sections of code in seconds.</p>
<p>That makes characterization testing more important, not less.</p>
<p>When the cost of producing a new implementation decreases, the risk shifts toward verifying that the new implementation still preserves the behavior that matters.</p>
<p>Before asking:</p>
<pre><code class="language-text">How should I refactor this?
</code></pre>
<p>ask:</p>
<pre><code class="language-text">What does this do today?
</code></pre>
<p>Then:</p>
<pre><code class="language-text">Which of those behaviors matter?
</code></pre>
<p>Then:</p>
<pre><code class="language-text">How can I prove they still work after the change?
</code></pre>
<p>That is what characterization tests give you.</p>
<p>They don't tell you that legacy behavior is correct. They give you evidence that it exists.</p>
<p>And once that evidence is executable, you can refactor with much more confidence.</p>
<p>The sequence becomes:</p>
<pre><code class="language-text">Understand
↓
Characterize
↓
Refactor
↓
Verify
</code></pre>
<p>AI can accelerate every step in that workflow. But the engineering judgment remains in deciding what behavior deserves to survive, what behavior should change, and when you have enough evidence to safely make that distinction.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test Conversational AI: A Practical Guide for QA Engineers ]]>
                </title>
                <description>
                    <![CDATA[ When I first started learning about conversational AI testing, one question kept bothering me: Where is the expected result? Coming from traditional software testing, I was used to a familiar pattern. ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-test-conversational-ai-practical-guide-for-qa-engineers/</link>
                <guid isPermaLink="false">6a8c5708cc6859117f26da4f</guid>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Quality Assurance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ai testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ QA engineering ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ GAYATHRI BOLINENI ]]>
                </dc:creator>
                <pubDate>Mon, 24 Aug 2026 14:36:56 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/61896a64-69bb-46cb-87dd-0076c4aa1b50.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When I first started learning about conversational AI testing, one question kept bothering me: <strong>Where is the expected result?</strong></p>
<p>Coming from traditional software testing, I was used to a familiar pattern.</p>
<p>A requirement tells us what the system should do. We create a test case, provide an input, define an expected result, execute the test, and compare the actual result with what we expected.</p>
<p>For example:</p>
<table>
<thead>
<tr>
<th>Test</th>
<th>Input</th>
<th>Expected Result</th>
</tr>
</thead>
<tbody><tr>
<td>Valid login</td>
<td>Correct username and password</td>
<td>User logs in</td>
</tr>
<tr>
<td>Invalid login</td>
<td>Incorrect password</td>
<td>Error message displayed</td>
</tr>
<tr>
<td>API request</td>
<td>Valid request payload</td>
<td>HTTP 200 with expected response</td>
</tr>
</tbody></table>
<p>Then I started learning conversational AI. Suddenly, the same approach didn't fit quite as neatly.</p>
<p>If I ask an AI agent "How can I reset my password?", it might answer, "You can reset your password using the Forgot Password option on the login page."</p>
<p>Ask the same question again and it might say: "Select Forgot Password from the sign-in screen and follow the instructions sent to your registered email."</p>
<p>The wording is different, but both responses may be perfectly acceptable. So how do we test something when the exact response can change?</p>
<p>That question changed the way I approached conversational AI testing.</p>
<p>In this article, I'll walk through the testing areas I found most important while learning how conversational systems behave, and show how traditional QA techniques can be adapted for AI agents.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-1-start-with-intent-not-exact-wording">1. Start With Intent, Not Exact Wording</a></p>
</li>
<li><p><a href="#heading-2-dont-use-exact-text-matching-for-every-response">2. Don't Use Exact Text Matching for Every Response</a></p>
</li>
<li><p><a href="#heading-3-evaluate-response-quality-across-multiple-dimensions">3. Evaluate Response Quality Across Multiple Dimensions</a></p>
</li>
<li><p><a href="#heading-4-test-the-conversation-not-just-the-response">4. Test the Conversation, Not Just the Response</a></p>
</li>
<li><p><a href="#heading-5-test-whether-the-ai-can-handle-corrections">5. Test Whether the AI Can Handle Corrections</a></p>
</li>
<li><p><a href="#heading-6-test-ambiguity">6. Test Ambiguity</a></p>
</li>
<li><p><a href="#heading-7-test-the-knowledge-behind-the-answer">7. Test the Knowledge Behind the Answer</a></p>
</li>
<li><p><a href="#heading-8-test-for-hallucinations">8. Test for Hallucinations</a></p>
</li>
<li><p><a href="#heading-9-test-fallback-behavior">9. Test Fallback Behavior</a></p>
</li>
<li><p><a href="#heading-10-test-human-escalation">10. Test Human Escalation</a></p>
</li>
<li><p><a href="#heading-11-test-integrations-like-you-would-in-any-other-application">11. Test Integrations Like You Would in Any Other Application</a></p>
</li>
<li><p><a href="#heading-12-build-a-golden-dataset">12. Build a Golden Dataset</a></p>
</li>
<li><p><a href="#heading-13-dont-only-measure-pass-rate">13. Don't Only Measure Pass Rate</a></p>
</li>
<li><p><a href="#heading-14-create-risk-based-conversational-tests">14. Create Risk-Based Conversational Tests</a></p>
</li>
<li><p><a href="#heading-15-a-practical-conversational-ai-test-strategy">15. A Practical Conversational AI Test Strategy</a></p>
</li>
<li><p><a href="#heading-what-traditional-qa-engineers-already-bring-to-ai-testing">What Traditional QA Engineers Already Bring to AI Testing</a></p>
</li>
<li><p><a href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ul>
<h2 id="heading-1-start-with-intent-not-exact-wording">1. Start With Intent, Not Exact Wording</h2>
<p>Consider these three messages:</p>
<ol>
<li><p>"How do I reset my password?"</p>
</li>
<li><p>"I can't access my account."</p>
</li>
<li><p>"Forgot password."</p>
</li>
</ol>
<p>They look different. But depending on the application, they may all represent the same underlying user goal:</p>
<p><strong>PASSWORD_RESET</strong></p>
<p>In conversational AI, the sentence a user types is often called an <strong>utterance</strong>, while the goal behind that message can be represented as an <strong>intent</strong>.</p>
<p>This creates an important testing question: Can the system understand the same intent when users express it differently?</p>
<p>A simple test set could look like this:</p>
<table>
<thead>
<tr>
<th>Utterance</th>
<th>Expected Intent</th>
</tr>
</thead>
<tbody><tr>
<td>I forgot my password</td>
<td>PASSWORD_RESET</td>
</tr>
<tr>
<td>How do I change my password?</td>
<td>PASSWORD_RESET</td>
</tr>
<tr>
<td>Can't get into my account</td>
<td>PASSWORD_RESET</td>
</tr>
<tr>
<td>Help me recover my login</td>
<td>PASSWORD_RESET</td>
</tr>
<tr>
<td>Password isn't working</td>
<td>PASSWORD_RESET</td>
</tr>
</tbody></table>
<p>Let's look at a small example:</p>
<pre><code class="language-python">test_cases = [ 
          { "message": "I forgot my password", 
            "expected_intent": "PASSWORD_RESET", 
          }, 
          { "message": "How do I change my password?", 
            "expected_intent": "PASSWORD_RESET", 
          }, 
          { "message": "Can't get into my account",  
            "expected_intent": "PASSWORD_RESET", 
          }, 
]

for test in test_cases: 
    response = ai_agent.send(test["message"])
    assert response.intent == test["expected_intent"]
</code></pre>
<p>Before running this test, you need to know the expected intent. It usually comes from the application's approved intent definitions or a reviewed test dataset. The automation isn't deciding what the correct intent should be. It's checking whether the AI agent classified the user's message according to the behavior the team already defined:</p>
<p><code>Input: "Can't get into my account"</code></p>
<p><code>Expected intent: PASSWORD_RESET Actual intent: PASSWORD_RESET</code></p>
<p><code>PASS</code></p>
<p>Don't stop with clean sentences.</p>
<p>Real users make spelling mistakes, use abbreviations, provide incomplete information, and sometimes type only a few words.</p>
<p>So I would also test:</p>
<p>"forgot pwd"</p>
<p>"cant login"</p>
<p>"password help"</p>
<p>"locked out"</p>
<p>This is where conversational AI testing starts becoming interesting. We're not testing whether the system recognizes one predefined sentence. We're testing whether it understands variations of a user's goal.</p>
<h2 id="heading-2-dont-use-exact-text-matching-for-every-response">2. Don't Use Exact Text Matching for Every Response</h2>
<p>One of the first habits I had to reconsider was comparing actual and expected responses word for word.</p>
<p>Suppose the expected answer is: "You can reset your password using the Forgot Password link."</p>
<p>But the AI responds: "Select Forgot Password on the login screen to begin resetting your password."</p>
<p>An exact string comparison fails. But from a user's perspective, the response may be completely correct.</p>
<p>Instead of defining the expected result as one sentence, define the <strong>properties a good response must contain</strong>.</p>
<p>For example, the response should:</p>
<ul>
<li><p>explain how to start the password-reset process</p>
</li>
<li><p>provide an actionable next step</p>
</li>
<li><p>not ask the user to reveal their password</p>
</li>
<li><p>avoid inventing account information</p>
</li>
<li><p>stay relevant to password recovery</p>
</li>
</ul>
<p>Now multiple responses can pass without being identical. This was one of the biggest changes for me.</p>
<p>For deterministic applications, expected output is often a value. For conversational AI, the expected result may need to be a <strong>set of evaluation criteria</strong>.</p>
<h2 id="heading-3-evaluate-response-quality-across-multiple-dimensions">3. Evaluate Response Quality Across Multiple Dimensions</h2>
<p>Correctness is important, but it shouldn't be the only thing you evaluate. I find it useful to break response quality into several dimensions.</p>
<ol>
<li><p>Accuracy: is the information correct? If the AI says customers can reset passwords through email when the actual process requires contacting support, the response fails even if it sounds convincing.</p>
</li>
<li><p>Relevance: did the AI answer what the user actually asked? A response can contain accurate information and still be irrelevant.</p>
</li>
<li><p>Completeness: did the response include the information required for the user to move forward?</p>
</li>
<li><p>Clarity: can the user easily understand the answer?</p>
</li>
<li><p>Helpfulness: does the response actually help the user accomplish their goal?</p>
</li>
</ol>
<p>A simple scoring rubric could look like this:</p>
<table>
<thead>
<tr>
<th>Criterion</th>
<th>Score</th>
</tr>
</thead>
<tbody><tr>
<td>Accuracy</td>
<td>0–2</td>
</tr>
<tr>
<td>Relevance</td>
<td>0–2</td>
</tr>
<tr>
<td>Completeness</td>
<td>0–2</td>
</tr>
<tr>
<td>Clarity</td>
<td>0–2</td>
</tr>
<tr>
<td>Helpfulness</td>
<td>0–2</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>0–10</strong></td>
</tr>
</tbody></table>
<p>You can then define a threshold appropriate for your application.</p>
<p>The exact scoring system isn't the important part. What's important is making the evaluation criteria explicit instead of relying on: "This answer looks good to me."</p>
<h2 id="heading-4-test-the-conversation-not-just-the-response">4. Test the Conversation, Not Just the Response</h2>
<p>Single-turn testing is useful, but users rarely interact with an AI agent using perfectly isolated questions.</p>
<p>Consider this conversation:</p>
<p><strong>User:</strong> I need to update my address.</p>
<p>The AI explains the process.</p>
<p>Then:</p>
<p><strong>User:</strong> Can I do that online?</p>
<p>What does <strong>"that"</strong> mean? The second message depends entirely on the first.</p>
<p>Now imagine:</p>
<p><strong>User:</strong> I need to update my address.</p>
<p><strong>AI:</strong> Sure. I can help with that.</p>
<p><strong>User:</strong> Actually, before that, can you tell me when my next payment is due?</p>
<p><strong>AI:</strong> Your next payment is due on September 15.</p>
<p><strong>User:</strong> Thanks. Now back to the address.</p>
<p>Can the system return to the original topic?</p>
<p>That's a different type of test.</p>
<p>Your multi-turn test suite should include scenarios such as:</p>
<ul>
<li><p>follow-up questions</p>
</li>
<li><p>references to earlier messages</p>
</li>
<li><p>topic switching</p>
</li>
<li><p>returning to a previous topic</p>
</li>
<li><p>user corrections</p>
</li>
<li><p>incomplete information</p>
</li>
<li><p>ambiguous questions</p>
</li>
<li><p>repeated questions</p>
</li>
</ul>
<p>This changed the unit of testing for me. Sometimes you're testing a response, while other times you're testing the entire conversation.</p>
<p>Here's an example:</p>
<pre><code class="language-python">ai_agent.send("My order number is A10245")
response = ai_agent.send("When will it arrive?")

assert response.order_id == "A10245"
</code></pre>
<p>The second message doesn't contain the order number. This check verifies that the AI agent retained information from the earlier turn instead of treating “When will it arrive?” as an unrelated question.</p>
<h2 id="heading-5-test-whether-the-ai-can-handle-corrections">5. Test Whether the AI Can Handle Corrections</h2>
<p>People change their minds and make mistakes.</p>
<p>For example:</p>
<p><strong>User:</strong> My account number ends in 4567.</p>
<p>Then:</p>
<p><strong>User:</strong> Sorry, I meant 4576.</p>
<p>What happens next?</p>
<p>The system should ideally use the corrected information rather than continuing with the original value.</p>
<p>The same principle applies to other conversational details.</p>
<p>"I'm traveling to Boston."</p>
<p>followed by:</p>
<p>"Actually, make that Chicago."</p>
<p>Or:</p>
<p>"I need the report for June."</p>
<p>followed by:</p>
<p>"Sorry, July."</p>
<p>These are useful tests because they expose whether the agent is genuinely maintaining conversational context or simply accumulating information without understanding which information is current.</p>
<h2 id="heading-6-test-ambiguity">6. Test Ambiguity</h2>
<p>Users don't always provide enough information.</p>
<p>Imagine someone types: "I want to change it."</p>
<p>Change what? Their address? Password? Payment method? Notification preference?</p>
<p>A poor conversational system may guess. A better one may ask: "What would you like to change?"</p>
<p>This gives us another important test category: <strong>clarification behavior.</strong></p>
<p>Create intentionally ambiguous utterances such as:</p>
<ul>
<li><p>"How do I update it?"</p>
</li>
<li><p>"It's not working."</p>
</li>
<li><p>"Can you change that?"</p>
</li>
<li><p>"I need help with my account."</p>
</li>
</ul>
<p>Then evaluate whether the AI recognizes that information is missing, avoids making unsupported assumptions, and asks an appropriate clarification question.</p>
<p>Sometimes the best AI response isn't an answer, it's another question.</p>
<h2 id="heading-7-test-the-knowledge-behind-the-answer">7. Test the Knowledge Behind the Answer</h2>
<p>At first, I focused almost entirely on what the AI said. Then I realized that a poor answer doesn't automatically mean the language model itself is the problem.</p>
<p>The system may be using a knowledge base, retrieval system, documentation, APIs, or other enterprise data sources.</p>
<p>If that information is wrong, incomplete, conflicting, or outdated, the AI may produce a poor response even when the model is behaving as designed.</p>
<p>Suppose the official policy says: Customers have 30 days to return a product. But an outdated knowledge article says that customers have 60 days.</p>
<p>If the AI retrieves the outdated article and confidently answers "60 days," the response is wrong.</p>
<p>But the investigation shouldn't end with: <strong>"The AI hallucinated."</strong> The tester needs to determine where the wrong information came from.</p>
<p>Questions I would investigate include:</p>
<ul>
<li><p>What source did the response use?</p>
</li>
<li><p>Was the source approved?</p>
</li>
<li><p>Is the information current?</p>
</li>
<li><p>Were multiple sources contradictory?</p>
</li>
<li><p>Did retrieval return the correct document?</p>
</li>
<li><p>Did the final response accurately represent the retrieved information?</p>
</li>
</ul>
<p>This becomes particularly important in systems using retrieval-augmented generation, or RAG.</p>
<h2 id="heading-8-test-for-hallucinations">8. Test for Hallucinations</h2>
<p>One of the most important conversational AI tests is surprisingly simple: <strong>Ask about something the system doesn't know.</strong></p>
<p>Suppose an internal support assistant contains documentation for Products A, B, and C. Ask: "What is the cancellation policy for Product Z?"</p>
<p>Product Z doesn't exist.</p>
<p>So what should happen? The worst outcome is for the system to confidently invent a cancellation policy.</p>
<p>Depending on the application, better behavior might be:</p>
<p>"I don't have information about Product Z."</p>
<p>or:</p>
<p>"I couldn't find that information. Would you like me to connect you with support?"</p>
<p>A useful hallucination test suite should include:</p>
<ul>
<li><p>nonexistent products</p>
</li>
<li><p>fake policy names</p>
</li>
<li><p>unsupported features</p>
</li>
<li><p>deliberately incorrect assumptions</p>
</li>
<li><p>questions outside the knowledge domain</p>
</li>
<li><p>requests for information unavailable to the system</p>
</li>
</ul>
<p>You're testing whether the AI knows when <strong>not</strong> to answer.</p>
<h2 id="heading-9-test-fallback-behavior">9. Test Fallback Behavior</h2>
<p>Every conversational system will eventually receive something it doesn't understand. But that isn't necessarily a failure.</p>
<p>The important question is what happens next?</p>
<p>Imagine a scenario like this:</p>
<p><strong>User:</strong> I need help with my ZXP adjustment.</p>
<p>The system doesn't recognize "ZXP."</p>
<p>A poor fallback might repeatedly say: "Sorry, I don't understand." A better fallback might ask: "Could you tell me a little more about what you mean by ZXP adjustment?"</p>
<p>If the system still can't understand the request, it may need to offer another path.</p>
<p>Fallback tests should cover:</p>
<ul>
<li><p>unknown intents</p>
</li>
<li><p>misspellings</p>
</li>
<li><p>incomplete requests</p>
</li>
<li><p>unsupported topics</p>
</li>
<li><p>conflicting requests</p>
</li>
<li><p>repeated misunderstanding</p>
</li>
</ul>
<p>Also test what happens after multiple failures. An AI agent shouldn't trap the user in an endless loop of: "Sorry, I didn't understand that."</p>
<h2 id="heading-10-test-human-escalation">10. Test Human Escalation</h2>
<p>Sometimes the AI agent should recognize that it can no longer handle the conversation reliably. This might happen when the user explicitly asks for a person, the request falls outside the agent's capabilities, or the situation requires human judgment. In those cases, continuing to generate answers may be worse than handing the conversation over</p>
<p>Consider situations involving:</p>
<ul>
<li><p>repeated misunderstanding</p>
</li>
<li><p>unsupported account problems</p>
</li>
<li><p>user requests for a human</p>
</li>
<li><p>sensitive workflows</p>
</li>
<li><p>exceptions the automated process cannot handle</p>
</li>
</ul>
<p>If escalation is part of the product design, test the entire transition.</p>
<p>For example:</p>
<p><strong>User:</strong> I want to speak to someone.</p>
<p>Does the AI recognize the request? Does it transfer the conversation correctly? Does the human agent receive the relevant conversation history? Does the user have to explain everything again? Does the AI continue trying to answer after escalation should have occurred?</p>
<p>A technically successful transfer can still create a poor experience if all the context is lost.</p>
<h2 id="heading-11-test-integrations-like-you-would-in-any-other-application">11. Test Integrations Like You Would in Any Other Application</h2>
<p>Conversational interfaces can make complex systems look simple.</p>
<p>The user sees:</p>
<p>"What's the status of my order?"</p>
<p>But behind that sentence, the agent might:</p>
<ol>
<li><p>identify the user's intent</p>
</li>
<li><p>authenticate the customer</p>
</li>
<li><p>call an order API</p>
</li>
<li><p>retrieve the order</p>
</li>
<li><p>interpret the response</p>
</li>
<li><p>generate a natural-language answer</p>
</li>
</ol>
<p>Traditional testing skills become extremely valuable here.</p>
<p>If the API returns:</p>
<pre><code class="language-json">{
  "order_id": "A10245",
  "status": "SHIPPED"
}
</code></pre>
<p>the AI shouldn't tell the user: "Your order is still processing."</p>
<p>To check these integrations, make sure you test:</p>
<ul>
<li><p>correct API mapping</p>
</li>
<li><p>authentication failures</p>
</li>
<li><p>timeouts</p>
</li>
<li><p>empty responses</p>
</li>
<li><p>malformed responses</p>
</li>
<li><p>unavailable services</p>
</li>
<li><p>incorrect status codes</p>
</li>
<li><p>partial data</p>
</li>
</ul>
<p>AI doesn't eliminate conventional integration testing. It adds another layer on top of it.</p>
<h2 id="heading-12-build-a-golden-dataset">12. Build a Golden Dataset</h2>
<p>Manual exploratory testing is useful when you're learning how an AI behaves, but eventually you need repeatability. This is where a <strong>golden dataset</strong> becomes useful.</p>
<p>A golden dataset is a curated collection of representative test inputs and expected behaviors that can be rerun as the system changes.</p>
<p>For example:</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>User Input</th>
<th>Expected Behavior</th>
</tr>
</thead>
<tbody><tr>
<td>INT-001</td>
<td>Forgot password</td>
<td>Identify password-reset intent</td>
</tr>
<tr>
<td>INT-002</td>
<td>Can't access account</td>
<td>Route to account-access flow</td>
</tr>
<tr>
<td>CTX-001</td>
<td>Can I do that online?</td>
<td>Resolve previous conversational context</td>
</tr>
<tr>
<td>AMB-001</td>
<td>I want to change it</td>
<td>Ask clarification</td>
</tr>
<tr>
<td>HAL-001</td>
<td>Policy for nonexistent Product Z</td>
<td>Don't invent policy</td>
</tr>
<tr>
<td>ESC-001</td>
<td>Let me talk to a person</td>
<td>Initiate escalation</td>
</tr>
<tr>
<td>KB-001</td>
<td>What is the return period?</td>
<td>Answer according to approved knowledge</td>
</tr>
</tbody></table>
<p>Then expand each important intent with paraphrases, edge cases, negative cases, and multi-turn scenarios.</p>
<p>Whenever prompts, knowledge, models, integrations, or conversation logic change, rerun the dataset.</p>
<p>Now you have something much closer to traditional regression testing.</p>
<h2 id="heading-13-dont-only-measure-pass-rate">13. Don't Only Measure Pass Rate</h2>
<p>Suppose you execute 1,000 conversational tests and 950 pass. A 95% pass rate sounds good.</p>
<p>But what failed? Five hundred harmless FAQ questions? Or five critical account-security scenarios?</p>
<p>Aggregate pass rate alone doesn't tell the whole story. Depending on the application, useful metrics might include:</p>
<ul>
<li><p><strong>Intent recognition accuracy:</strong> How often was the user's goal understood correctly?</p>
</li>
<li><p><strong>Fallback rate:</strong> How often did the system fail to understand the user?</p>
</li>
<li><p><strong>Task completion rate:</strong> How often did users successfully accomplish the intended task?</p>
</li>
<li><p><strong>Escalation success rate:</strong> When human help was required, did the transition succeed?</p>
</li>
<li><p><strong>Grounding failures:</strong> How often did responses conflict with approved knowledge?</p>
</li>
<li><p><strong>Context failures:</strong> How often did the system lose important information during multi-turn conversations?</p>
</li>
<li><p><strong>Critical hallucinations:</strong> How often did the system confidently provide unsupported information?</p>
</li>
</ul>
<p>The right metrics depend on the product and its risks. A customer-service FAQ bot and an AI system supporting financial decisions shouldn't necessarily have the same quality thresholds.</p>
<h2 id="heading-14-create-risk-based-conversational-tests">14. Create Risk-Based Conversational Tests</h2>
<p>This is another traditional QA principle that transfers very well.</p>
<p>Not every AI failure has the same impact. If an AI responds awkwardly to: "What are your business hours?", that's inconvenient.</p>
<p>If it gives incorrect information about a payment, account security, healthcare instruction, or financial policy, the impact could be much greater.</p>
<p>So categorize scenarios by risk.</p>
<p>For example:</p>
<table>
<thead>
<tr>
<th>Risk</th>
<th>Example</th>
<th>Testing Priority</th>
</tr>
</thead>
<tbody><tr>
<td>Low</td>
<td>General FAQ</td>
<td>Normal</td>
</tr>
<tr>
<td>Medium</td>
<td>Account navigation</td>
<td>High</td>
</tr>
<tr>
<td>High</td>
<td>Financial/account action</td>
<td>Very High</td>
</tr>
<tr>
<td>Critical</td>
<td>Security/privacy behavior</td>
<td>Mandatory regression</td>
</tr>
</tbody></table>
<p>Then concentrate regression coverage on the scenarios where incorrect AI behavior would cause the greatest harm.</p>
<h2 id="heading-15-a-practical-conversational-ai-test-strategy">15. A Practical Conversational AI Test Strategy</h2>
<p>If I were starting a conversational AI QA effort today, I would organize it into these layers:</p>
<h3 id="heading-layer-1-intent-testing">Layer 1: Intent Testing</h3>
<p>Can the system understand what the user wants despite variations in language?</p>
<h3 id="heading-layer-2-response-evaluation">Layer 2: Response Evaluation</h3>
<p>Are responses accurate, relevant, complete, clear, and useful?</p>
<h3 id="heading-layer-3-conversation-testing">Layer 3: Conversation Testing</h3>
<p>Can the system maintain context across multiple turns?</p>
<h3 id="heading-layer-4-knowledge-and-grounding">Layer 4: Knowledge and Grounding</h3>
<p>Are answers supported by approved and current information?</p>
<h3 id="heading-layer-5-negative-and-hallucination-testing">Layer 5: Negative and Hallucination Testing</h3>
<p>Does the system avoid confidently answering when it doesn't have enough information?</p>
<h3 id="heading-layer-6-fallback-and-escalation">Layer 6: Fallback and Escalation</h3>
<p>Can the system recover when it doesn't understand, and can it hand off to a human when necessary?</p>
<h3 id="heading-layer-7-integration-testing">Layer 7: Integration Testing</h3>
<p>Are APIs, authentication, databases, and downstream systems behaving correctly?</p>
<h3 id="heading-layer-8-regression-testing">Layer 8: Regression Testing</h3>
<p>Can important behaviors be rerun after model, prompt, knowledge, or application changes?</p>
<p>That gives QA teams a much more structured starting point than simply opening a chatbot and asking random questions.</p>
<h2 id="heading-what-traditional-qa-engineers-already-bring-to-ai-testing">What Traditional QA Engineers Already Bring to AI Testing</h2>
<p>When I first started learning conversational AI, I thought I needed to forget everything I knew about traditional testing. But I don't believe that anymore.</p>
<p>A lot of our existing skills transfer extremely well. We already know how to:</p>
<ul>
<li><p>question assumptions</p>
</li>
<li><p>explore edge cases</p>
</li>
<li><p>design negative tests</p>
</li>
<li><p>trace failures through multiple systems</p>
</li>
<li><p>validate integrations</p>
</li>
<li><p>prioritize by risk</p>
</li>
<li><p>build regression suites</p>
</li>
<li><p>investigate unexpected behavior</p>
</li>
</ul>
<p>What changes is the definition of the expected result.</p>
<p>For some AI scenarios, the expected result isn't:</p>
<p><strong>Response = X</strong></p>
<p>It's closer to:</p>
<p><strong>The response must satisfy X, Y, and Z while avoiding A and B.</strong></p>
<p>Once I understood that distinction, conversational AI testing started making much more sense to me.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>My first instinct when learning conversational AI was to search for the test cases.</p>
<p>Now I think that's the wrong place to start. Start with the user.</p>
<ul>
<li><p>What are they trying to accomplish?</p>
</li>
<li><p>What are the different ways they might ask for it?</p>
</li>
<li><p>What information does the AI need?</p>
</li>
<li><p>What would a useful answer contain?</p>
</li>
<li><p>What should the system never say?</p>
</li>
<li><p>What happens if the AI doesn't know?</p>
</li>
<li><p>What happens when the conversation changes direction?</p>
</li>
<li><p>What evidence would make you confident enough to release that experience to real users?</p>
</li>
</ul>
<p>Those questions eventually become your test cases.</p>
<p>Conversational AI may be less deterministic than the applications many QA engineers are used to testing. But that doesn't make it untestable. It simply means we need to move beyond asking:</p>
<p>"Did I get the exact output I expected?"</p>
<p>and start asking:</p>
<p>"Did the system behave correctly, safely, and usefully across the different ways a real person might interact with it?"</p>
<p>For me, that was the biggest shift. The tools are changing, and so is the interface. Even the definition of an expected result is changing. But the fundamental responsibility of quality engineering hasn't changed very much at all: understand how the system can fail before the user has to discover it for you.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What Modern QA Engineers Actually Do: It's More Than Finding Bugs ]]>
                </title>
                <description>
                    <![CDATA[ Ask someone what a QA engineer does, and you'll probably hear a familiar answer: "They test software and find bugs." It's a common perception, and to be fair, finding bugs is an important part of the  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-does-a-modern-qa-engineer-do/</link>
                <guid isPermaLink="false">6a722e60649a71090a9c2307</guid>
                
                    <category>
                        <![CDATA[ Quality Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ career advice ]]>
                    </category>
                
                    <category>
                        <![CDATA[ QAengineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Quality Assurance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ GAYATHRI BOLINENI ]]>
                </dc:creator>
                <pubDate>Tue, 04 Aug 2026 18:24:32 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/55c4125f-a946-42b9-a694-46ab4cb3f6d6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Ask someone what a QA engineer does, and you'll probably hear a familiar answer: <em>"They test software and find bugs."</em></p>
<p>It's a common perception, and to be fair, finding bugs is an important part of the job. But if you spend even a few weeks working on a modern software team, you'll quickly realize that's only a small part of what QA engineers actually do.</p>
<p>Software development has changed dramatically over the last decade. Teams no longer wait months to release new features. Many organizations deploy updates every week, every day, or even several times a day. Applications have become more complex, with cloud services, APIs, microservices, mobile apps, and third-party integrations all working together behind the scenes.</p>
<p>As software has evolved, the role of QA has evolved with it.</p>
<p>Today's QA engineers are involved long before a feature reaches testing. They help review requirements, identify risks, clarify business expectations, verify APIs and databases, automate repetitive tests, investigate production issues, and work closely with developers throughout the entire development lifecycle.</p>
<p>In other words, QA isn't just about finding problems after software has been built. It's about helping prevent those problems from happening in the first place.</p>
<p>Whether you're thinking about becoming a QA engineer, transitioning from manual testing into automation, or simply curious about what quality engineering looks like today, understanding how the role has changed is an important first step.</p>
<p>In this article, we'll explore what QA engineers actually do, why the profession has evolved, and the skills that have become essential for building reliable software in today's fast-moving development environments.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-how-the-qa-role-has-changed">How the QA Role Has Changed</a></p>
</li>
<li><p><a href="#heading-good-testing-starts-before-the-first-test-case">Good Testing Starts Before the First Test Case</a></p>
</li>
<li><p><a href="#heading-real-world-example">Real-world Example</a></p>
</li>
<li><p><a href="#heading-modern-qa-is-about-more-than-automation">Modern QA Is About More Than Automation</a></p>
</li>
<li><p><a href="#heading-where-different-types-of-testing-fit">Where Different Types of Testing Fit</a></p>
</li>
<li><p><a href="#heading-skills-every-qa-engineer-should-develop">Skills Every QA Engineer Should Develop</a></p>
</li>
<li><p><a href="#heading-common-misconceptions-about-qa">Common Misconceptions About QA</a></p>
</li>
<li><p><a href="#heading-if-youre-considering-a-career-in-qa">If You're Considering a Career in QA</a></p>
</li>
<li><p><a href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ul>
<h2 id="heading-how-the-qa-role-has-changed">How the QA Role Has Changed</h2>
<p>Many software usually teams followed a simple workflow. Business analysts gathered requirements, developers built the application, and once development was complete, the software was handed over to QA for testing. If defects were found, the application went back to the development team before eventually being released.</p>
<p>In that model, QA was often viewed as the final checkpoint before production. Testing happened after most of the important technical decisions had already been made.</p>
<p>That approach worked reasonably well when software releases happened only a few times a year. Teams had enough time to finish development, perform weeks of manual testing, fix defects, and prepare for a scheduled release.</p>
<p>Todays software development looks very different.</p>
<p>Many organizations use Agile methodologies, Continuous Integration (CI), and Continuous Delivery (CD). Instead of delivering software every few months, teams continuously add features, fix bugs, and release improvements in short development cycles.</p>
<p>Because development moves much faster, quality can no longer be treated as the final phase of a project.</p>
<p>Instead, QA engineers work alongside developers, product owners, business analysts, UX designers, and DevOps engineers from the beginning of the development process. They participate in sprint planning, review user stories, discuss acceptance criteria, identify potential risks, and help ensure new features are designed with testing in mind before development even starts.</p>
<p>This shift has changed the role of QA from <strong>testing completed software</strong> to <strong>helping teams build quality into the software from the start</strong>.</p>
<p>For example, imagine a team is building an online banking application that allows customers to transfer money between accounts. A traditional testing approach might focus on verifying whether the transfer succeeds after the feature has been developed.</p>
<p>An experienced QA engineer starts much earlier by asking questions such as:</p>
<ul>
<li><p>What happens if the network connection is interrupted during the transfer?</p>
</li>
<li><p>What should happen if the customer has insufficient funds?</p>
</li>
<li><p>Can the same transfer request be submitted twice accidentally?</p>
</li>
<li><p>How should the application respond if the receiving bank is temporarily unavailable?</p>
</li>
</ul>
<p>Questions like these help uncover potential problems before developers spend time writing code. Addressing these scenarios early is often much less expensive than discovering them during testing—or worse, after the application has been released to customers.</p>
<p>This is one of the biggest reasons QA has evolved from a role focused primarily on testing into one that contributes throughout the entire software development lifecycle.</p>
<p>Quality is no longer something that's checked at the end of a project.</p>
<p>It's something the entire team builds together, one decision at a time.</p>
<p><strong>Traditional Development</strong></p>
<p>Requirements -&gt; Development -&gt; QA Testing -&gt; Production</p>
<p><strong>Modern Development</strong></p>
<p>Requirement -&gt; DEV+QA+Product Owner -&gt; Continuous Development -&gt; Continuous Testing -&gt; Production</p>
<h2 id="heading-good-testing-starts-before-the-first-test-case">Good Testing Starts Before the First Test Case</h2>
<p>One of the biggest surprises for people entering software testing is discovering that a significant part of a QA engineer's work happens before a single test case is written.</p>
<p>Many people picture testing as something that begins only after developers finish building a feature. In reality, experienced QA engineers become involved much earlier because that's often where they can have the greatest impact.</p>
<p>Consider a simple requirement:</p>
<blockquote>
<p><strong>A user must create a password containing at least eight characters.</strong></p>
</blockquote>
<p>At first glance, the requirement seems complete. A developer can implement it, and a tester can verify that passwords shorter than eight characters are rejected.</p>
<p>But software requirements are rarely that straightforward.</p>
<p>A QA engineer naturally starts looking beyond the obvious by asking questions such as:</p>
<ul>
<li><p>Should spaces or count as characters? Or Should leading or trailing spaces be removed automatically?</p>
</li>
<li><p>Is there a maximum password length?</p>
</li>
<li><p>Are special or Unicode characters required?</p>
</li>
<li><p>Will the same validation rules apply on both the web application and the mobile app?</p>
</li>
<li><p>What error message should users see if the password is invalid?</p>
</li>
</ul>
<p>None of these questions are about trying to catch developers making mistakes.</p>
<p>They're about making sure the entire team has the same understanding of how the feature should work before development moves forward.</p>
<p>This process is often called <strong>requirement clarification</strong>, and it's one of the most valuable contributions a QA engineer can make.</p>
<p>Without these discussions, developers may implement one interpretation of the requirement while testers validate another. Business stakeholders may expect something entirely different. Even when everyone is working hard, unclear requirements can lead to unnecessary defects, rework, and frustration.</p>
<p>Asking thoughtful questions early helps avoid those situations.</p>
<p>It also saves time.</p>
<p>Finding an unclear requirement during a planning meeting usually takes a few minutes to resolve. Discovering the same issue after development, testing, and deployment can take hours—or even days—to investigate and fix.</p>
<p>Good QA isn't only about verifying that software works correctly.</p>
<p>It's also about helping ensure the team is building the right software in the first place.</p>
<h2 id="heading-real-world-example">Real-world Example</h2>
<p>Imagine an online shopping website that offers discount coupons.</p>
<p>The requirement says:</p>
<blockquote>
<p><strong>"Users can apply one coupon during checkout."</strong></p>
</blockquote>
<p>At first, it sounds simple.</p>
<p>A QA engineer might ask:</p>
<ul>
<li><p>What happens if the coupon has expired?</p>
</li>
<li><p>Can two browser tabs apply the same coupon simultaneously?</p>
</li>
<li><p>What if the customer removes an item after applying the coupon?</p>
</li>
<li><p>Can multiple coupons be combined?</p>
</li>
<li><p>What happens if the payment fails after the coupon has been applied?</p>
</li>
</ul>
<p>These questions often uncover situations that weren't considered during the initial discussion.</p>
<p>Instead of becoming production defects, they become design decisions that the team can address before writing code.</p>
<p>That's one of the reasons experienced QA engineers spend so much time asking questions. They're not slowing development down. They're helping the team avoid expensive surprises later.</p>
<h2 id="heading-modern-qa-is-about-more-than-automation">Modern QA Is About More Than Automation</h2>
<p>If you browse job postings for QA engineers, you'll probably notice one thing almost immediately.</p>
<p>Many of them mention automation tools such as Selenium, Plawright, Cypress, Appium, Javascript, Java, Python, and so on - the list goes on</p>
<p>Because of that, it's easy to assume that QA engineers spend their entire day writing automated tests. Automation is certainly an important part of the job, but it isn't the job itself.</p>
<p>Think about a new feature being added to a food delivery app. Customers can now save multiple delivery addresses and choose one during checkout. Before any automation script is written, a QA engineer is already involved.</p>
<p>They review the requirement to understand how the feature is expected to work. They discuss different scenarios with developers and product owners, such as what should happen if a customer deletes their default address or enters an invalid ZIP code. They identify edge cases that may not have been considered during planning and think about how the feature interacts with existing functionality.</p>
<p>Only after those discussions do they begin designing test scenarios.</p>
<p>Some of those tests may eventually become automated, especially if the feature will be used in future regression testing. Others may be better suited for exploratory testing because they require human observation and judgment.</p>
<p>QA engineers choose the right testing approach based on the problem they're trying to solve rather than trying to automate everything.</p>
<p>A typical week for a QA engineer might include a variety of responsibilities, such as:</p>
<ul>
<li><p>Reviewing new requirements before development begins.</p>
</li>
<li><p>Designing functional and edge-case test scenarios.</p>
</li>
<li><p>Verifying REST API responses using tools like Postman or Bruno.</p>
</li>
<li><p>Validating backend data with SQL queries.</p>
</li>
<li><p>Building or maintaining automated regression tests.</p>
</li>
<li><p>Investigating issues reported from production.</p>
</li>
<li><p>Working with developers to reproduce and understand defects.</p>
</li>
<li><p>Confirming bug fixes before a release.</p>
</li>
</ul>
<p>Some weeks involve more automation than others. Some involve more investigation, collaboration, or exploratory testing.</p>
<p>That's one of the reasons QA engineering is such a diverse field. No two projects are exactly the same, and the work changes depending on the product, the team, and the stage of development.</p>
<p>Automation simply helps QA engineers spend less time repeating predictable tasks so they can focus on the work that requires critical thinking.</p>
<p>It's a tool that supports quality engineering—not the definition of it.</p>
<h2 id="heading-where-different-types-of-testing-fit">Where Different Types of Testing Fit</h2>
<p>Another common misconception is that QA engineers only test the user interface. In reality, software can be tested at many different levels.</p>
<p>For example:</p>
<table>
<thead>
<tr>
<th>Type of Testing</th>
<th>Purpose</th>
</tr>
</thead>
<tbody><tr>
<td><strong>UI Testing</strong></td>
<td>Verifies that users can successfully interact with the application.</td>
</tr>
<tr>
<td><strong>API Testing</strong></td>
<td>Confirms that services communicate correctly and return the expected responses.</td>
</tr>
<tr>
<td><strong>Database Testing</strong></td>
<td>Validates that data is stored, updated, and retrieved accurately.</td>
</tr>
<tr>
<td><strong>Regression Testing</strong></td>
<td>Ensures that new changes haven't broken existing functionality.</td>
</tr>
<tr>
<td><strong>Exploratory Testing</strong></td>
<td>Helps uncover unexpected issues through human observation and creativity.</td>
</tr>
<tr>
<td><strong>Performance Testing</strong></td>
<td>Evaluates how the application behaves under different workloads.</td>
</tr>
</tbody></table>
<p>QA engineers don't necessarily perform all of these types of testing every day, but understanding when and why each approach is useful helps them choose the right strategy for different situations.</p>
<p>Quality isn't achieved by relying on a single testing technique.</p>
<p>It's achieved by combining multiple testing approaches to build confidence that the software behaves correctly under real-world conditions.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a680c143c3aac7c9e746cad/deb53c30-0cef-4708-ae8e-43ca86e03b0d.png" alt="A flowchart showing how a new software feature is validated through UI testing, API testing, and database testing before entering regression testing and finally being released to production" style="display: block;" width="1433" height="992" loading="lazy">

<h2 id="heading-skills-every-qa-engineer-should-develop">Skills Every QA Engineer Should Develop</h2>
<p>If you're just starting your QA journey, it's easy to focus on learning specific tools. While tools are important, they're only part of the picture. The most successful QA engineers build a combination of technical skills, problem-solving abilities, and effective communication.</p>
<p>Here are some of the core skills that will help you grow in QA role.</p>
<table>
<thead>
<tr>
<th><strong>Skill</strong></th>
<th><strong>Why It Matters</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Communication</strong></td>
<td>Helps clarify requirements, explain defects, and collaborate effectively with the team.</td>
</tr>
<tr>
<td><strong>Critical Thinking</strong></td>
<td>Identifies edge cases, risks, and scenarios that may not be immediately obvious.</td>
</tr>
<tr>
<td><strong>API Testing</strong></td>
<td>Verifies how applications communicate and ensures backend services behave correctly.</td>
</tr>
<tr>
<td><strong>SQL</strong></td>
<td>Confirms that data is stored, updated, and retrieved accurately from databases.</td>
</tr>
<tr>
<td><strong>Test Automation</strong></td>
<td>Reduces repetitive testing and improves regression testing efficiency.</td>
</tr>
<tr>
<td><strong>CI/CD Knowledge</strong></td>
<td>Helps integrate testing into modern software delivery pipelines.</td>
</tr>
<tr>
<td><strong>Continuous Learning</strong></td>
<td>Keeps your skills current as tools, technologies, and development practices evolve.</td>
</tr>
</tbody></table>
<p>We don't need to masters all of these skills overnight. The important thing is to build a strong foundation, stay curious, and keep learning as the industry evolves.</p>
<h2 id="heading-common-misconceptions-about-qa">Common Misconceptions About QA</h2>
<p>If you're new to software testing, you've probably heard some of these statements before. While they may sound reasonable, they don't reflect how modern QA teams actually work.</p>
<h3 id="heading-myth-qa-engineers-only-find-bugs">Myth: QA engineers Only Find Bugs</h3>
<p><strong>Reality:</strong> Finding bugs is only one part of the job. QA engineers also review requirements, identify risks, design test strategies, automate repetitive testing, validate APIs and databases, and help teams prevent defects before software reaches users.</p>
<h3 id="heading-myth-automation-will-replace-qa-engineers">Myth: Automation Will Replace QA Engineers.</h3>
<p><strong>Reality:</strong> Automation is a tool, not a replacement for human thinking. Automated tests can execute repetitive tasks, but they can't decide what should be tested, identify unclear requirements, or evaluate whether a feature delivers a good user experience.</p>
<h3 id="heading-myth-qa-is-responsible-for-quality">Myth: QA is Responsible for Quality.</h3>
<p><strong>Reality:</strong> Quality is a shared responsibility. Developers write reliable code, product owners define clear requirements, designers focus on usability, DevOps engineers build dependable deployment pipelines, and QA engineers help ensure everything works together as expected.</p>
<h3 id="heading-myth-manual-testing-is-no-longer-useful">Myth: Manual Testing is No Longer Useful.</h3>
<p><strong>Reality:</strong> Automation is excellent for repetitive regression testing, but manual testing remains valuable for exploratory testing, usability checks, and investigating unexpected behavior. The two approaches complement each other rather than compete.</p>
<h3 id="heading-myth-qa-is-easier-than-software-development">Myth: QA is Easier than Software Development.</h3>
<p><strong>Reality:</strong> Modern QA requires strong analytical thinking, technical knowledge, communication skills, and a solid understanding of how software systems work. While the responsibilities differ from software development, both roles play an equally important part in delivering high-quality software.</p>
<h2 id="heading-if-youre-considering-a-career-in-qa">If You're Considering a Career in QA</h2>
<p>If you're thinking about becoming a QA engineer, don't worry if you don't know every testing tool or automation framework yet. Every experienced QA professional started by learning the fundamentals.</p>
<p>Begin by understanding how software works. Learn how web applications communicate with APIs, how data is stored in databases, and how different components interact to deliver a feature. These concepts will help you understand <em>why</em> you're testing something, not just <em>how</em> to test it.</p>
<p>At the same time, practice thinking like a user. Ask questions, explore different scenarios, and look beyond the "happy path." Some of the most valuable defects are discovered simply because someone asked, <em>"What happens if this doesn't go as expected?"</em></p>
<p>As you grow, gradually build technical skills such as SQL, API testing, automation, version control, and CI/CD. Focus on continuous learning and improving one skill at a time.</p>
<p>Most importantly, remember that software quality isn't created by one person or one team. The best QA engineers work collaboratively, communicate effectively, and help everyone build better software together.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Software testing has come a long way from being viewed as the final step before a release.</p>
<p>Today's QA engineers contribute throughout the software development lifecycle. They ask thoughtful questions, clarify requirements, automate repetitive testing, validate APIs and databases, investigate production issues, and help teams deliver reliable software with confidence.</p>
<p>Finding bugs will always be an important part of the role, but it's no longer what defines a successful QA engineer.</p>
<p>What truly makes a difference is the ability to prevent problems before they happen, think critically about how software is used, and collaborate with the entire team to deliver a better product.</p>
<p>If you're just starting your journey in software quality, don't measure your progress by how many testing tools you've learned. Focus on building strong fundamentals, staying curious, and continuously improving your problem-solving skills.</p>
<p>Technology will continue to evolve, but those qualities will always be valuable.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Unit Testing in Go - A Beginner's Guide ]]>
                </title>
                <description>
                    <![CDATA[ If you're learning Go and you’re already familiar with the idea of unit testing, the main challenge is usually not why to test, but how to test in Go. Go takes a deliberately minimal approach to testing. There are no built-in assertions, no annotatio... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/unit-testing-in-go-a-beginners-guide/</link>
                <guid isPermaLink="false">696535ad7a48c374647910f2</guid>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ golang ]]>
                    </category>
                
                    <category>
                        <![CDATA[ unit testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gabor Koos ]]>
                </dc:creator>
                <pubDate>Mon, 12 Jan 2026 17:55:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768240528981/73c9c9f6-4942-4c39-9e62-87f540fd2233.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you're learning Go and you’re already familiar with the idea of <strong>unit testing</strong>, the main challenge is usually not <em>why</em> to test, but <em>how</em> to test in Go.</p>
<p>Go takes a deliberately minimal approach to testing. There are no built-in assertions, no annotations, and no special syntax. Instead, tests are written as regular Go code using a small standard library package, and run with a single command. This can feel unusual at first if you're coming from ecosystems with richer testing frameworks, but it quickly becomes predictable and easy to reason about.</p>
<p>In this article, we'll look at how unit testing works in Go in practice. We'll write a few small tests, run them from the command line, and cover the most common patterns you'll see in real Go codebases, such as table-driven tests and testing functions that return errors. We'll focus on the essentials and won't cover more advanced topics like mocks or external frameworks.</p>
<p>The goal is to show how familiar testing concepts translate into idiomatic Go. By the end, you should feel comfortable reading and writing basic unit tests and integrating them into your regular Go workflow.</p>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-writing-your-first-test">Writing Your First Test</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-running-your-test">Running Your Test</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-divide-by-zero">Divide by Zero</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-terrorf-vs-tfatalf">t.Errorf vs t.Fatalf</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-table-driven-tests">Table-Driven Tests</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-driven-add-test">Table-Driven Add Test</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-table-driven-divide-test">Table-Driven Divide Test</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-exercise">Exercise</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-functions-that-return-errors">Testing Functions That Return Errors</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-safe-divide-function">Safe Divide Function</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-writing-tests-for-safedivide">Writing Tests for SafeDivide()</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-exercise-1">Exercise</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-and-tips">Best Practices and Tips</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-name-tests-clearly">Name Tests Clearly</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-keep-tests-small-and-focused">Keep Tests Small and Focused</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-table-driven-tests-for-repetitive-cases">Use Table-Driven Tests for Repetitive Cases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-check-errors-explicitly">Check Errors Explicitly</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-avoid-panics-when-possible">Avoid Panics When Possible</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-run-tests-frequently">Run Tests Frequently</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-keep-tests-in-the-same-package">Keep Tests in the Same Package</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-tfatalf-vs-terrorf-appropriately">Use t.Fatalf vs t.Errorf Appropriately</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-solutions-to-exercises">Solutions to Exercises</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-subtract-function-and-tests">Subtract Function and Tests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-safesubtract-function-and-tests">SafeSubtract Function and Tests</a></p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start, you should be comfortable with:</p>
<ul>
<li><p>Writing and running basic Go programs</p>
</li>
<li><p>Defining and calling functions in Go</p>
</li>
<li><p>Understanding basic Go types (int, string, bool, and so on)</p>
</li>
<li><p>Using the Go command-line tool (go run, go build)</p>
</li>
<li><p>Basic understanding of unit tests: what a test is and why it's useful</p>
</li>
<li><p>Familiarity with Test-Driven Development concepts like testing before or alongside writing code</p>
</li>
<li><p>Awareness of common testing ideas such as assertions, test coverage, and checking error conditions</p>
</li>
</ul>
<p>You don't need prior experience with Go's <code>testing</code> package or Go-specific test patterns, as this guide will cover all of that.</p>
<h2 id="heading-writing-your-first-test">Writing Your First Test</h2>
<p>Let's start with a simple function to test. Imagine you have a small <code>calc</code> package with an <code>Add</code> function:</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc.go</span>
<span class="hljs-keyword">package</span> calc

<span class="hljs-comment">// Add returns the sum of two integers</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Add</span><span class="hljs-params">(a, b <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">int</span></span> {
    <span class="hljs-keyword">return</span> a + b
}
</code></pre>
<p>To test this function, create a new file named <code>calc_test.go</code> in the same package. In Go, test files must end with <code>_test.go</code> to be recognized by the testing tool.</p>
<p>Inside <code>calc_test.go</code>, you write a test function:</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc_test.go</span>
<span class="hljs-keyword">package</span> calc

<span class="hljs-keyword">import</span> <span class="hljs-string">"testing"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestAdd</span><span class="hljs-params">(t *testing.T)</span></span> {
    got := Add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>)
    want := <span class="hljs-number">5</span>
    <span class="hljs-keyword">if</span> got != want {
        t.Errorf(<span class="hljs-string">"Add(2, 3) = %d; want %d"</span>, got, want)
    }
}
</code></pre>
<p>Here's what's happening:</p>
<ul>
<li><p>The function name starts with <code>Test</code> and takes a single <code>*testing.T</code> parameter. Go automatically discovers and runs any function that follows this convention.</p>
</li>
<li><p>The <code>t.Errorf</code> call reports a test failure. Unlike some frameworks, Go doesn't provide special assertions – you simply check a condition and call <code>t.Errorf</code> or <code>t.Fatalf</code> if it fails.</p>
</li>
<li><p>Each test is a standalone function. You can write as many as you like, and Go will run them all.</p>
</li>
</ul>
<h3 id="heading-running-your-test">Running Your Test</h3>
<p>Once the file is saved, you can run your test with:</p>
<pre><code class="lang-bash">go <span class="hljs-built_in">test</span>
</code></pre>
<p>This runs tests for the current package (files ending with <code>_test.go</code>). If you want to run tests recursively in all subdirectories of your project, use:</p>
<pre><code class="lang-bash">go <span class="hljs-built_in">test</span> ./...
</code></pre>
<p>The <code>./...</code> pattern is shorthand for "run tests in this directory and all subdirectories". This is especially useful in larger projects where your code is spread across multiple packages.</p>
<p>If everything is working, you should see output indicating that the test passed:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span>
PASS
ok      _/C_/projects/Articles/Go_Testing       0.334s
</code></pre>
<p>You can add the <code>-v</code> flag for verbose output:</p>
<pre><code class="lang-bash">go <span class="hljs-built_in">test</span> -v
</code></pre>
<p>This will show you the names of the tests as they run:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span> -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      _/C_/projects/Articles/Go_Testing       0.356s
</code></pre>
<p>Not much difference for a single test, but it becomes useful as you add more tests.</p>
<p>Now let's see what happens if the test fails. Change the expected value in <code>calc_test.go</code> to an incorrect one:</p>
<pre><code class="lang-go">  ...
    want := <span class="hljs-number">6</span> <span class="hljs-comment">// Incorrect expected value</span>
  ...
</code></pre>
<p>Run the tests again:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span>
--- FAIL: TestAdd (0.00s)
    calc_test.go:9: Add(2, 3) = 5; want 6
FAIL
<span class="hljs-built_in">exit</span> status 1
FAIL    _/C_/projects/Articles/Go_Testing       0.340s
</code></pre>
<p>or with verbose output:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span> -v
=== RUN   TestAdd
    calc_test.go:9: Add(2, 3) = 5; want 6
--- FAIL: TestAdd (0.00s)
FAIL
<span class="hljs-built_in">exit</span> status 1
FAIL    _/C_/projects/Articles/Go_Testing       0.337s
</code></pre>
<p>Of course, your tests should always check for the correct expected values! A failing (but correct) test is a sign that your code needs to be fixed.</p>
<p>We only created one test file and one test function with one assertion here, but Go's testing tool can handle many files and functions at once. Behind the scenes, Go will automatically:</p>
<ul>
<li><p>Find <strong>all</strong> <code>_test.go</code> files in the specified packages (for example, current directory for <code>go test</code>, or recursively in all subdirectories with <code>go test ./...</code>).</p>
</li>
<li><p>Identify functions that start with <code>Test</code> and have the correct signature.</p>
</li>
<li><p>Compile them together with your package into a temporary test binary.</p>
</li>
<li><p>Execute each test function and report the results.</p>
</li>
</ul>
<p>To prove this, let's quickly add a <code>Divide</code> function to our package:</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc.go</span>
...
<span class="hljs-comment">// Divide returns the result of dividing a by b</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Divide</span><span class="hljs-params">(a, b <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">int</span></span> {
    <span class="hljs-keyword">return</span> a / b
}
</code></pre>
<p>(Note that this is an <strong>integer division</strong>, so fractional parts are discarded. <code>Divide(5, 2)</code> would return <code>2</code>.)</p>
<p>And another test file with a corresponding test:</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc_2_test.go</span>
<span class="hljs-keyword">package</span> calc

<span class="hljs-keyword">import</span> <span class="hljs-string">"testing"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestDivide</span><span class="hljs-params">(t *testing.T)</span></span> {
    got := Divide(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>)
    want := <span class="hljs-number">5</span>    
    <span class="hljs-keyword">if</span> got != want {
        t.Errorf(<span class="hljs-string">"Divide(10, 2) = %d; want %d"</span>, got, want)
    }    
}
</code></pre>
<p>Now when you run <code>go test</code>, both <code>TestAdd</code> and <code>TestDivide</code> will be executed:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span>
PASS
ok      _/C_/projects/Articles/Go_Testing       0.325s
</code></pre>
<p>Or:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span> -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestDivide
--- PASS: TestDivide (0.00s)
PASS
ok      _/C_/projects/Articles/Go_Testing       0.323s
</code></pre>
<h3 id="heading-divide-by-zero">Divide by Zero</h3>
<p>What happens if we try to <code>Divide</code> by zero? Let's add another test case for that:</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc_test.go</span>
...
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestDivideByZero</span><span class="hljs-params">(t *testing.T)</span></span> {
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r == <span class="hljs-literal">nil</span> { <span class="hljs-comment">// Check if a panic occurred</span>
            t.Errorf(<span class="hljs-string">"Divide did not panic on division by zero"</span>)
        }
    }()
    Divide(<span class="hljs-number">10</span>, <span class="hljs-number">0</span>) <span class="hljs-comment">// This should cause a panic</span>
}
</code></pre>
<p>This test checks that the <code>Divide</code> function panics when dividing by zero. When you run the tests again, you'll see that this new test also passes:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span> -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestDivide
--- PASS: TestDivide (0.00s)
=== RUN   TestDivideByZero
--- PASS: TestDivideByZero (0.00s)
PASS
ok      _/C_/projects/Articles/Go_Testing       0.312s
</code></pre>
<p>(Note that in real-world Go code, it's better to return <code>(int, error)</code> for unsafe operations instead of panicking.)</p>
<p>Feel free to experiment by adding more test cases, changing expected values, and exploring how Go's testing framework handles different scenarios.</p>
<h3 id="heading-terrorf-vs-tfatalf"><code>t.Errorf</code> vs <code>t.Fatalf</code></h3>
<p>In the examples above, we used <code>t.Errorf</code> to report test failures. This function logs the error but allows the test to continue running. This is useful when you want to check multiple conditions in a single test function.</p>
<p>In contrast, <code>t.Fatalf</code> logs the error and immediately stops the execution of the current test. Use <code>t.Fatalf</code> when continuing the test after a failure doesn't make sense or could cause misleading results.</p>
<p>For example, in the <code>TestDivideByZero</code> test, if the <code>Divide</code> function does not panic, we use <code>t.Errorf</code> to report the failure but continue to the end of the test. But if we had additional checks after the division, we might want to use <code>t.Fatalf</code> to stop execution immediately upon failure.</p>
<p>While <code>t.Errorf</code> and <code>t.Fatalf</code> use <code>fmt</code>-style formatting, for simple messages without formatting, you can also use <code>t.Error</code> and <code>t.Fatal</code>, respectively.</p>
<p>In the next section, we'll look at <em>table-driven tests</em>, a common Go pattern for testing multiple cases efficiently.</p>
<h2 id="heading-table-driven-tests">Table-Driven Tests</h2>
<p>In Go, it's common to want to run the same test logic for multiple inputs and expected outputs. Rather than writing a separate test function for each case, Go developers often use <strong>table-driven tests</strong>. This pattern keeps your tests concise, readable, and easy to extend.</p>
<h3 id="heading-table-driven-add-test">Table-Driven <code>Add</code> Test</h3>
<p>Let's rewrite our Add test using a table-driven approach (and delete <code>calc_2_test.go</code> for clarity):</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc_test.go</span>
<span class="hljs-keyword">package</span> calc

<span class="hljs-keyword">import</span> <span class="hljs-string">"testing"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestAddTableDriven</span><span class="hljs-params">(t *testing.T)</span></span> {
    tests := []<span class="hljs-keyword">struct</span> {<span class="hljs-comment">// Define a struct for each test case and create a slice of them</span>
        name <span class="hljs-keyword">string</span>
        a, b <span class="hljs-keyword">int</span>
        want <span class="hljs-keyword">int</span>
    }{
        {<span class="hljs-string">"both positive"</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">5</span>},
        {<span class="hljs-string">"positive + zero"</span>, <span class="hljs-number">5</span>, <span class="hljs-number">0</span>, <span class="hljs-number">5</span>},
        {<span class="hljs-string">"negative + positive"</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">3</span>},
        {<span class="hljs-string">"both negative"</span>, <span class="hljs-number">-2</span>, <span class="hljs-number">-3</span>, <span class="hljs-number">-5</span>},
    }

    <span class="hljs-keyword">for</span> _, tt := <span class="hljs-keyword">range</span> tests {<span class="hljs-comment">// Loop over each test case</span>
        t.Run(tt.name, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> {<span class="hljs-comment">// Run each case as a subtest</span>
            got := Add(tt.a, tt.b)
            <span class="hljs-keyword">if</span> got != tt.want {<span class="hljs-comment">// Check the result</span>
                t.Errorf(<span class="hljs-string">"Add(%d, %d) = %d; want %d"</span>, tt.a, tt.b, got, tt.want) <span class="hljs-comment">// Report failure if it doesn't match</span>
            }
        })
    }
}
</code></pre>
<p>Here's how it works:</p>
<ul>
<li><p>We define a <strong>slice of structs</strong>, each representing a test case.</p>
</li>
<li><p>Each struct contains the test name, input values, and the expected result.</p>
</li>
<li><p>We loop over the slice and call <code>t.Run(tt.name, func(t *testing.T) { ... })</code> to run each test as a <strong>subtest</strong>.</p>
</li>
<li><p>If a subtest fails, you can see which one by its name in the output.</p>
</li>
</ul>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span>
PASS
ok      _/C_/projects/Articles/Go_Testing       0.452s
</code></pre>
<p>Or to see detailed output:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span> -v
=== RUN   TestAddTableDriven
=== RUN   TestAddTableDriven/both_positive
=== RUN   TestAddTableDriven/positive_+_zero
=== RUN   TestAddTableDriven/negative_+_positive
=== RUN   TestAddTableDriven/both_negative
--- PASS: TestAddTableDriven (0.00s)
    --- PASS: TestAddTableDriven/both_positive (0.00s)
    --- PASS: TestAddTableDriven/positive_+_zero (0.00s)
    --- PASS: TestAddTableDriven/negative_+_positive (0.00s)
    --- PASS: TestAddTableDriven/both_negative (0.00s)
PASS
ok      _/C_/projects/Articles/Go_Testing       0.385s
</code></pre>
<h3 id="heading-table-driven-divide-test">Table-Driven Divide Test</h3>
<p>We can apply the same pattern to <code>Divide</code>, including checking for divide-by-zero:</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc_test.go</span>
...
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestDivideTableDriven</span><span class="hljs-params">(t *testing.T)</span></span> {
    tests := []<span class="hljs-keyword">struct</span> { <span class="hljs-comment">// Define test cases</span>
        name     <span class="hljs-keyword">string</span>
        a, b     <span class="hljs-keyword">int</span>
        want     <span class="hljs-keyword">int</span>
        wantPanic <span class="hljs-keyword">bool</span>
    }{
        {<span class="hljs-string">"normal division"</span>, <span class="hljs-number">10</span>, <span class="hljs-number">2</span>, <span class="hljs-number">5</span>, <span class="hljs-literal">false</span>},
        {<span class="hljs-string">"division by zero"</span>, <span class="hljs-number">10</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">true</span>},
    }

    <span class="hljs-keyword">for</span> _, tt := <span class="hljs-keyword">range</span> tests { <span class="hljs-comment">// Loop over</span>
        t.Run(tt.name, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> { <span class="hljs-comment">// Run subtest</span>
            <span class="hljs-keyword">if</span> tt.wantPanic { <span class="hljs-comment">// Check for expected panic</span>
                <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">// Recover from panic</span>
                    <span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r == <span class="hljs-literal">nil</span> {
                        t.Errorf(<span class="hljs-string">"Divide(%d, %d) did not panic"</span>, tt.a, tt.b)
                    }
                }()
            }
            got := Divide(tt.a, tt.b) <span class="hljs-comment">// Tests that do not panic</span>
            <span class="hljs-keyword">if</span> !tt.wantPanic &amp;&amp; got != tt.want {
                t.Errorf(<span class="hljs-string">"Divide(%d, %d) = %d; want %d"</span>, tt.a, tt.b, got, tt.want)
            }
        })
    }
}
</code></pre>
<p>This example shows how to handle both normal and panic cases in a single table-driven test:</p>
<ul>
<li><p>The <code>wantPanic</code> field tells the test whether we expect a panic.</p>
</li>
<li><p>We use <code>defer</code> and <code>recover</code> to check for a panic when needed.</p>
</li>
<li><p>Normal test cases still check the result as usual.</p>
</li>
</ul>
<p>Run all tests as before:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span> -v
=== RUN   TestAddTableDriven
=== RUN   TestAddTableDriven/both_positive
=== RUN   TestAddTableDriven/positive_+_zero
=== RUN   TestAddTableDriven/negative_+_positive
=== RUN   TestAddTableDriven/both_negative
--- PASS: TestAddTableDriven (0.00s)
    --- PASS: TestAddTableDriven/both_positive (0.00s)
    --- PASS: TestAddTableDriven/positive_+_zero (0.00s)
    --- PASS: TestAddTableDriven/negative_+_positive (0.00s)
    --- PASS: TestAddTableDriven/both_negative (0.00s)
=== RUN   TestDivideTableDriven
=== RUN   TestDivideTableDriven/normal_division
=== RUN   TestDivideTableDriven/division_by_zero
--- PASS: TestDivideTableDriven (0.00s)
    --- PASS: TestDivideTableDriven/normal_division (0.00s)
    --- PASS: TestDivideTableDriven/division_by_zero (0.00s)
PASS
ok      _/C_/projects/Articles/Go_Testing       0.321s
</code></pre>
<p>Subtest names make it easy to see which case passed or failed.</p>
<h3 id="heading-exercise">Exercise</h3>
<p>Try creating your own table-driven test for a new function, <code>Subtract(a, b int) int</code>. Include at least four test cases:</p>
<ul>
<li><p>Both positive numbers</p>
</li>
<li><p>Positive minus zero</p>
</li>
<li><p>Negative minus positive</p>
</li>
<li><p>Both negative</p>
</li>
</ul>
<p>Then run your tests and verify the output.</p>
<h2 id="heading-testing-functions-that-return-errors">Testing Functions That Return Errors</h2>
<p>Many Go functions return an error as the last return value. Writing tests for these functions is slightly different from testing pure functions like our <code>Add</code> or <code>Divide</code>, because you need to check both the result and whether an error occurred.</p>
<h3 id="heading-safe-divide-function">Safe Divide Function</h3>
<p>Let's add a <code>SafeDivide</code> function to return an error instead of panicking:</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc.go</span>
...
<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
...
<span class="hljs-comment">// SafeDivide returns the result of dividing a by b.</span>
<span class="hljs-comment">// It returns an error if b is zero.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SafeDivide</span><span class="hljs-params">(a, b <span class="hljs-keyword">int</span>)</span> <span class="hljs-params">(<span class="hljs-keyword">int</span>, error)</span></span> {
    <span class="hljs-keyword">if</span> b == <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, fmt.Errorf(<span class="hljs-string">"cannot divide by zero"</span>)
    }
    <span class="hljs-keyword">return</span> a / b, <span class="hljs-literal">nil</span>
}
</code></pre>
<h3 id="heading-writing-tests-for-safedivide">Writing Tests for <code>SafeDivide()</code></h3>
<p>We can use a table-driven test again:</p>
<pre><code class="lang-go"><span class="hljs-comment">// calc_test.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestSafeDivide</span><span class="hljs-params">(t *testing.T)</span></span> {
    tests := []<span class="hljs-keyword">struct</span> {
        name      <span class="hljs-keyword">string</span>
        a, b      <span class="hljs-keyword">int</span>
        want      <span class="hljs-keyword">int</span>
        wantError <span class="hljs-keyword">bool</span>
    }{
        {<span class="hljs-string">"normal division"</span>, <span class="hljs-number">10</span>, <span class="hljs-number">2</span>, <span class="hljs-number">5</span>, <span class="hljs-literal">false</span>},
        {<span class="hljs-string">"division by zero"</span>, <span class="hljs-number">10</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">true</span>},
    }

    <span class="hljs-keyword">for</span> _, tt := <span class="hljs-keyword">range</span> tests {
        t.Run(tt.name, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> {
            got, err := SafeDivide(tt.a, tt.b)
            <span class="hljs-keyword">if</span> tt.wantError {
                <span class="hljs-keyword">if</span> err == <span class="hljs-literal">nil</span> {
                    t.Errorf(<span class="hljs-string">"SafeDivide(%d, %d) expected error, got nil"</span>, tt.a, tt.b)
                }
                <span class="hljs-keyword">return</span> <span class="hljs-comment">// stop here, no need to check `got`</span>
            }
            <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
                t.Errorf(<span class="hljs-string">"SafeDivide(%d, %d) unexpected error: %v"</span>, tt.a, tt.b, err)
            }
            <span class="hljs-keyword">if</span> got != tt.want {
                t.Errorf(<span class="hljs-string">"SafeDivide(%d, %d) = %d; want %d"</span>, tt.a, tt.b, got, tt.want)
            }
        })
    }
}
</code></pre>
<p>What's happening here:</p>
<ul>
<li><p>We added a <code>wantError</code> field to indicate whether the test expects an error.</p>
</li>
<li><p>If an error is expected, we check that <code>err != nil</code>. If not (that is, <code>err == nil</code>), we fail the test.</p>
</li>
<li><p>If no error is expected, we check both the returned value (<code>got</code>) and that <code>err == nil</code>.</p>
</li>
<li><p>Using <code>t.Run</code> subtests keeps everything organized and readable.</p>
</li>
</ul>
<p>Running the tests again:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span> -v
...
=== RUN   TestSafeDivide
=== RUN   TestSafeDivide/normal_division
=== RUN   TestSafeDivide/division_by_zero
--- PASS: TestSafeDivide (0.00s)
    --- PASS: TestSafeDivide/normal_division (0.00s)
    --- PASS: TestSafeDivide/division_by_zero (0.00s)
PASS
ok      _/C_/projects/Articles/Go_Testing       0.323s
</code></pre>
<p>Showing that both normal and error cases are handled correctly.</p>
<h3 id="heading-exercise-1">Exercise</h3>
<p>Update your <code>Subtract(a, b int) int</code> function to a <code>SafeSubtract(a, b int) (int, error)</code> variant that returns an error if the result would be negative. Then write a table-driven test that covers:</p>
<ul>
<li><p>A positive result</p>
</li>
<li><p>Zero result</p>
</li>
<li><p>A negative result (should return an error)</p>
</li>
</ul>
<h2 id="heading-best-practices-and-tips">Best Practices and Tips</h2>
<p>Writing tests in Go is straightforward, but there are a few conventions and tips that make your tests more readable, maintainable, and idiomatic:</p>
<h3 id="heading-name-tests-clearly">Name Tests Clearly</h3>
<p>First, make sure you use descriptive names for test functions and subtests. A good name explains what you're testing and under what conditions.</p>
<p>Here’s an example:</p>
<pre><code class="lang-go">t.Run(<span class="hljs-string">"Divide positive numbers"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> { ... })
t.Run(<span class="hljs-string">"Divide by zero returns error"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> { ... })
</code></pre>
<h3 id="heading-keep-tests-small-and-focused">Keep Tests Small and Focused</h3>
<p>Each subtest should verify one thing, and each test function should cover a single function or method.</p>
<p>Try to avoid combining multiple unrelated checks in the same test function, and use table-driven tests help keep multiple similar checks concise without losing clarity.</p>
<h3 id="heading-use-table-driven-tests-for-repetitive-cases">Use Table-Driven Tests for Repetitive Cases</h3>
<p>If you find yourself writing multiple similar test functions, switch to a table-driven pattern. It makes it easier to add new cases, reduces duplicated code, and keeps output organized with <code>t.Run</code>.</p>
<h3 id="heading-check-errors-explicitly">Check Errors Explicitly</h3>
<p>In Go, functions often return <code>error</code>. So make sure you always check for errors in tests, even if you expect <code>nil</code>.</p>
<p>You can use the <code>wantError</code> pattern in table-driven tests for clarity.</p>
<pre><code class="lang-go"><span class="hljs-keyword">if</span> tt.wantError {
    <span class="hljs-keyword">if</span> err == <span class="hljs-literal">nil</span> {
        t.Errorf(<span class="hljs-string">"expected error, got nil"</span>)
    }
}
</code></pre>
<h3 id="heading-avoid-panics-when-possible">Avoid Panics When Possible</h3>
<p>Panics are fine for some internal checks, but in production code, prefer returning an error.</p>
<p>Your tests can check for panics using <code>defer</code> and <code>recover</code>, but this should be the exception rather than the norm.</p>
<h3 id="heading-run-tests-frequently">Run Tests Frequently</h3>
<p>Try to make running tests a habit: <code>go test -v ./...</code>. Frequent testing helps catch mistakes early and reinforces TDD practices.</p>
<h3 id="heading-keep-tests-in-the-same-package">Keep Tests in the Same Package</h3>
<p>By convention, tests live in the same package as the code they test. You can create <code>_test.go</code> files for testing, and Go automatically recognizes them.</p>
<p>Only use a separate <code>package calc_test</code> if you want to test your code from the outside, like a consumer. External test packages (just like every other external package) cannot access unexported identifiers.</p>
<h3 id="heading-use-tfatalf-vs-terrorf-appropriately">Use t.Fatalf vs t.Errorf Appropriately</h3>
<ul>
<li><p><code>t.Errorf</code> reports a failure but continues running the test.</p>
</li>
<li><p><code>t.Fatalf</code> stops the test immediately, which is useful if subsequent code depends on successful setup.</p>
</li>
</ul>
<p>These tips will help you write clean, maintainable, and idiomatic Go tests that are easy to read and extend. Following these practices early in your Go journey will make testing less intimidating and more effective.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Unit testing in Go may feel different at first, especially if you're coming from ecosystems with heavy frameworks and assertions. But the simplicity of Go's testing tools is one of its strengths: once you understand the conventions, writing, running, and organizing tests becomes predictable and intuitive.</p>
<p>In this guide, you've seen how to:</p>
<ul>
<li><p>Write basic test functions with the testing package</p>
</li>
<li><p>Run tests from the command line and interpret the results</p>
</li>
<li><p>Use table-driven tests to cover multiple cases efficiently</p>
</li>
<li><p>Handle functions that return errors and check for expected failures</p>
</li>
</ul>
<p>Beyond these fundamentals, testing is not just about verifying correctness, it's also about confidence. Well-tested code allows you to refactor, experiment, and add new features with less fear of breaking existing functionality.</p>
<p>As you continue writing Go code, try to integrate testing early, follow the idiomatic patterns you've learned, and explore more advanced topics such as:</p>
<ul>
<li><p>Using <em>mocks</em> or <em>interfaces</em> to isolate dependencies</p>
</li>
<li><p>Benchmark tests with <code>testing.B</code></p>
</li>
<li><p>Coverage analysis with <code>go test -cover</code></p>
</li>
</ul>
<p>The key takeaway is that testing in Go is accessible, flexible, and powerful, even without fancy frameworks. By building these habits now, you'll write code that's more reliable, maintainable, and enjoyable to work with.</p>
<h2 id="heading-solutions-to-exercises">Solutions to Exercises</h2>
<h3 id="heading-subtract-function-and-tests">Subtract Function and Tests</h3>
<pre><code class="lang-go"><span class="hljs-comment">// calc.go</span>
<span class="hljs-keyword">package</span> calc

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Subtract</span><span class="hljs-params">(a, b <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">int</span></span> {
    <span class="hljs-keyword">return</span> a - b
}
</code></pre>
<pre><code class="lang-go"><span class="hljs-comment">// calc_test.go</span>
<span class="hljs-keyword">package</span> calc

<span class="hljs-keyword">import</span> <span class="hljs-string">"testing"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestSubtractTableDriven</span><span class="hljs-params">(t *testing.T)</span></span> {
    tests := []<span class="hljs-keyword">struct</span> {
        name <span class="hljs-keyword">string</span>
        a, b <span class="hljs-keyword">int</span>
        want <span class="hljs-keyword">int</span>
    }{
        {<span class="hljs-string">"both positive"</span>, <span class="hljs-number">5</span>, <span class="hljs-number">3</span>, <span class="hljs-number">2</span>},
        {<span class="hljs-string">"positive minus zero"</span>, <span class="hljs-number">5</span>, <span class="hljs-number">0</span>, <span class="hljs-number">5</span>},
        {<span class="hljs-string">"negative minus positive"</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">-5</span>},
        {<span class="hljs-string">"both negative"</span>, <span class="hljs-number">-3</span>, <span class="hljs-number">-2</span>, <span class="hljs-number">-1</span>},
    }

    <span class="hljs-keyword">for</span> _, tt := <span class="hljs-keyword">range</span> tests {
        t.Run(tt.name, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> {
            got := Subtract(tt.a, tt.b)
            <span class="hljs-keyword">if</span> got != tt.want {
                t.Errorf(<span class="hljs-string">"Subtract(%d, %d) = %d; want %d"</span>, tt.a, tt.b, got, tt.want)
            }
        })
    }
}
</code></pre>
<h3 id="heading-safesubtract-function-and-tests">SafeSubtract Function and Tests</h3>
<pre><code class="lang-go"><span class="hljs-comment">// calc.go</span>
<span class="hljs-keyword">package</span> calc

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">SafeSubtract</span><span class="hljs-params">(a, b <span class="hljs-keyword">int</span>)</span> <span class="hljs-params">(<span class="hljs-keyword">int</span>, error)</span></span> {
    result := a - b
    <span class="hljs-keyword">if</span> result &lt; <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, fmt.Errorf(<span class="hljs-string">"result would be negative"</span>)
    }
    <span class="hljs-keyword">return</span> result, <span class="hljs-literal">nil</span>
}
</code></pre>
<pre><code class="lang-go"><span class="hljs-comment">// calc_test.go</span>
<span class="hljs-keyword">package</span> calc

<span class="hljs-keyword">import</span> <span class="hljs-string">"testing"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestSafeSubtract</span><span class="hljs-params">(t *testing.T)</span></span> {
    tests := []<span class="hljs-keyword">struct</span> {
        name      <span class="hljs-keyword">string</span>
        a, b      <span class="hljs-keyword">int</span>
        want      <span class="hljs-keyword">int</span>
        wantError <span class="hljs-keyword">bool</span>
    }{
        {<span class="hljs-string">"positive result"</span>, <span class="hljs-number">5</span>, <span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-literal">false</span>},
        {<span class="hljs-string">"zero result"</span>, <span class="hljs-number">3</span>, <span class="hljs-number">3</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">false</span>},
        {<span class="hljs-string">"negative result"</span>, <span class="hljs-number">2</span>, <span class="hljs-number">5</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">true</span>},
    }

    <span class="hljs-keyword">for</span> _, tt := <span class="hljs-keyword">range</span> tests {
        t.Run(tt.name, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> {
            got, err := SafeSubtract(tt.a, tt.b)
            <span class="hljs-keyword">if</span> tt.wantError {
                <span class="hljs-keyword">if</span> err == <span class="hljs-literal">nil</span> {
                    t.Errorf(<span class="hljs-string">"SafeSubtract(%d, %d) expected error, got nil"</span>, tt.a, tt.b)
                }
                <span class="hljs-keyword">return</span>
            }
            <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
                t.Errorf(<span class="hljs-string">"SafeSubtract(%d, %d) unexpected error: %v"</span>, tt.a, tt.b, err)
            }
            <span class="hljs-keyword">if</span> got != tt.want {
                t.Errorf(<span class="hljs-string">"SafeSubtract(%d, %d) = %d; want %d"</span>, tt.a, tt.b, got, tt.want)
            }
        })
    }
}
</code></pre>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Scale TestOps for Global Software Development Teams ]]>
                </title>
                <description>
                    <![CDATA[ Imagine that your software team is spread across the globe—developers in the US, testers in Asia, and managers in Europe. Exciting, right? But managing this setup is no walk in the park. Coordinating testing across time zones, tools, and workflows ca... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/scale-testops-for-global-software-development-teams/</link>
                <guid isPermaLink="false">6801211371255f2b14ea901b</guid>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ testops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nazneen Ahmad ]]>
                </dc:creator>
                <pubDate>Thu, 17 Apr 2025 15:41:07 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744904445449/18f469d0-b066-4709-a463-4f378802615d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine that your software team is spread across the globe—developers in the US, testers in Asia, and managers in Europe. Exciting, right? But managing this setup is no walk in the park. Coordinating testing across time zones, tools, and workflows can be challenging.</p>
<p>That is where TestOps comes in. It blends testing with operational efficiency, creating a streamlined approach to quality assurance. Scaling TestOps for global teams means setting up processes that work smoothly across continents, delivering speed and consistency without compromising on quality.</p>
<p>The challenges are real: communication gaps, tool compatibility issues, and cultural differences. But the payoff is worth it. A well-structured TestOps framework helps teams collaborate easily, automate testing, and produce software that meets global expectations.</p>
<p>This guide will walk you through overcoming these challenges, adopting practical strategies, and turning your global TestOps into a hub for innovation and quality.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-understanding-testops">Understanding TestOps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-limitations-of-scaling-testops">Limitations of Scaling TestOps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-strategies-for-scaling-testops">Strategies for Scaling TestOps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-testops-into-global-devops-pipelines">How to Integrate TestOps into Global DevOps Pipelines</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-ai-and-analytics-in-testops">How to Use AI and Analytics in TestOps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-future-of-testops-in-global-development">Future of TestOps in Global Development</a></p>
</li>
</ol>
<h2 id="heading-understanding-testops">Understanding TestOps</h2>
<p>TestOps is all about using automation to make software testing smoother and more efficient. It brings together scattered teams and processes into a unified system, helping you deliver better software faster and with fewer bugs. But what does it actually do?</p>
<p>TestOps makes testing easier to manage, run, and review. It keeps the testing process organized, consistent, and team-friendly. By using automation and central tools, TestOps helps you avoid mistakes, save time, and deliver better-quality software.</p>
<p>Here are the four central components of TestOps:</p>
<ul>
<li><p><strong>Planning</strong>: This step focuses on deciding what needs to be tested, how it will be tested (including the test environment), when testing will happen, and who will handle it.</p>
</li>
<li><p><strong>Management</strong>: This ensures testing is efficient and scalable by using tools that improve teamwork and visibility.</p>
</li>
<li><p><strong>Execution</strong>: This is the actual process of running tests on the software.</p>
</li>
<li><p><strong>Analysis</strong>: This step involves reviewing test performance, diagnosing issues, and finding ways to improve the overall testing process.</p>
</li>
</ul>
<p>At scale, TestOps focuses on:</p>
<ul>
<li><p><strong>Standardization</strong>: Setting up consistent testing methods and tools that everyone can use across teams and projects.</p>
</li>
<li><p><strong>Automation</strong>: Increasing the use of automated tests to handle more tasks quickly and accurately.</p>
</li>
<li><p><strong>Collaboration</strong>: Improving how teams work together, even if they are spread out in different locations.</p>
</li>
<li><p><strong>Scalability</strong>: Making sure testing systems and processes can grow as needs increase.</p>
</li>
<li><p><strong>Insights</strong>: Using data from large-scale testing to make better decisions and improve how things work.</p>
</li>
</ul>
<h2 id="heading-limitations-of-scaling-testops">Limitations of Scaling TestOps</h2>
<p>Scaling TestOps for global software teams comes with its fair share of challenges. While the advantages of smooth, integrated testing are clear, getting there requires careful planning.</p>
<p>Here are some key obstacles:</p>
<ul>
<li><p><strong>Communication barriers</strong>: With teams spread across different time zones, keeping communication clear and timely can be tough. Delays or misunderstandings can slow progress and affect the quality of testing.</p>
</li>
<li><p><strong>Tool compatibility</strong>: Teams may use different testing tools, leading to inefficiencies and fragmentation. It's important to make sure all tools can work together and are compatible across different environments.</p>
</li>
<li><p><strong>Cultural and organizational differences</strong>: Teams from various regions may have different work cultures, priorities, and expectations. Finding common ground without creating friction is essential for smooth collaboration.</p>
</li>
<li><p><strong>Time zone management</strong>: Coordinating meetings or ensuring real-time review of test results becomes difficult with global teams in different time zones.</p>
</li>
<li><p><strong>Quality consistency</strong>: Ensuring consistent testing standards across multiple locations can be tricky. Without centralized control, practices can vary, which may lead to missed defects and unreliable releases.</p>
</li>
</ul>
<p>Overcoming these challenges requires a well-thought-out strategy, effective communication, and the right tools to align teams and processes across the globe.</p>
<h2 id="heading-strategies-for-scaling-testops">Strategies for Scaling TestOps</h2>
<p>Scaling TestOps for global teams requires smart strategies to address communication issues, tool mismatches, and operational challenges. Here are some key approaches to make scaling work:</p>
<h3 id="heading-standardize-testing-processes"><strong>Standardize Testing Processes</strong></h3>
<p>Set up clear, consistent testing protocols and tools across all teams to ensure everyone is on the same page.</p>
<p>For example, you can standardize testing using frameworks like <strong>Jest</strong> to ensure consistency across teams.</p>
<pre><code class="lang-bash">bashCopy codenpm install --save-dev jest
</code></pre>
<p>In your <strong>package.json</strong>:</p>
<pre><code class="lang-json">jsonCopy code{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"jest"</span>
  }
}
</code></pre>
<h3 id="heading-use-cloud-based-tools"><strong>Use Cloud-Based Tools</strong></h3>
<p>Choose cloud tools that allow teams to collaborate smoothly, provide real-time feedback, and access testing environments from anywhere.</p>
<p>For example, cloud tools like <strong>LambdaTest</strong> enable remote testing across browsers and devices.</p>
<pre><code class="lang-javascript">javascriptCopy codeconst { remote } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webdriverio'</span>);

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">runTest</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> remote({
    <span class="hljs-attr">capabilities</span>: {
      <span class="hljs-attr">browserName</span>: <span class="hljs-string">'chrome'</span>,
      <span class="hljs-attr">platform</span>: <span class="hljs-string">'Windows 10'</span>,
      <span class="hljs-attr">version</span>: <span class="hljs-string">'latest'</span>,
      <span class="hljs-string">'build'</span>: <span class="hljs-string">'TestOps Scaling Build'</span>,
      <span class="hljs-string">'name'</span>: <span class="hljs-string">'Test Parallel Execution'</span>,
    },
    <span class="hljs-attr">host</span>: <span class="hljs-string">'hub.lambdatest.com'</span>,
    <span class="hljs-attr">port</span>: <span class="hljs-number">80</span>,
    <span class="hljs-attr">user</span>: <span class="hljs-string">'your_username'</span>,
    <span class="hljs-attr">key</span>: <span class="hljs-string">'your_access_key'</span>
  });

  <span class="hljs-keyword">await</span> browser.url(<span class="hljs-string">'https://www.yoursite.com'</span>);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">await</span> browser.getTitle());
  <span class="hljs-keyword">await</span> browser.deleteSession();
}

runTest();
</code></pre>
<h3 id="heading-automate-testing"><strong>Automate Testing</strong></h3>
<p>Integrate automated tests into CI/CD pipelines to reduce manual work, speed up feedback, and improve test coverage.</p>
<p>For example, you can use <strong>GitHub Actions</strong> for CI/CD test automation.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">yamlCopy codename:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Tests</span>

<span class="hljs-attr">on:</span> [<span class="hljs-string">push</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">tests</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>
</code></pre>
<h3 id="heading-use-centralized-reporting-tools"><strong>Use Centralized Reporting Tools</strong></h3>
<p>Use dashboards to give everyone real-time updates on testing progress, keeping all teams and stakeholders in the loop.</p>
<p>Here’s an example of integrating with <strong>TestRail</strong> for centralized reporting.</p>
<pre><code class="lang-javascript">javascriptCopy codeconst axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);

<span class="hljs-keyword">const</span> result = {
  <span class="hljs-string">"status"</span>: <span class="hljs-string">"passed"</span>, 
  <span class="hljs-string">"test_case_id"</span>: <span class="hljs-number">123</span>,
  <span class="hljs-string">"run_id"</span>: <span class="hljs-number">456</span>
};

axios.post(<span class="hljs-string">'https://your-testrail-instance/api/v2/add_result_for_case/1/123'</span>, result, {
  <span class="hljs-attr">auth</span>: { <span class="hljs-attr">username</span>: <span class="hljs-string">'your_email'</span>, <span class="hljs-attr">password</span>: <span class="hljs-string">'your_password'</span> }
})
.then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Test result posted successfully'</span>))
.catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error:'</span>, error));
</code></pre>
<h3 id="heading-encourage-cross-regional-collaboration"><strong>Encourage Cross-Regional Collaboration</strong></h3>
<p>Use collaboration tools and hold regular meetings to bridge time zone and cultural differences between teams.</p>
<p>You can use <strong>Slack</strong> or similar tools for real-time communication and alerts.</p>
<pre><code class="lang-javascript">javascriptCopy codeconst slackMessage = { <span class="hljs-attr">text</span>: <span class="hljs-string">"Test Execution Completed: All tests have passed successfully!"</span> };

axios.post(<span class="hljs-string">'https://hooks.slack.com/services/your-webhook-url'</span>, slackMessage)
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Slack message sent'</span>))
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error:'</span>, error));
</code></pre>
<h3 id="heading-create-a-continuous-feedback-loop"><strong>Create a Continuous Feedback Loop</strong></h3>
<p>Set up systems that provide immediate feedback and allow for quick action, ensuring quality isn’t delayed.</p>
<p>For example, you can trigger feedback loops with <strong>Slack</strong> for an immediate response.</p>
<pre><code class="lang-javascript">javascriptCopy codeconst slackMessage = { <span class="hljs-attr">text</span>: <span class="hljs-string">"Alert: Test failure detected!"</span> };

axios.post(<span class="hljs-string">'https://hooks.slack.com/services/your-webhook-url'</span>, slackMessage)
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Alert sent to Slack'</span>))
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error:'</span>, error));
</code></pre>
<h3 id="heading-upskill-teams"><strong>Upskill Teams</strong></h3>
<p>Offer training so all team members know how to use TestOps tools effectively.</p>
<p>Try providing training through GitHub repositories with testing best practices.</p>
<pre><code class="lang-plaintext">markdownCopy code# Automated Testing Guide

## Steps:
1. Clone repo
2. Install dependencies: `npm install`
3. Run tests: `npm test`
4. Review TestRail dashboard
</code></pre>
<h3 id="heading-adapt-to-time-zones"><strong>Adapt to Time Zones</strong></h3>
<p>Organize workflows and shifts that allow for continuous testing, helping teams overcome time zone challenges.</p>
<p>You can schedule tests using <strong>Jenkins</strong>, for example, to accommodate global teams.</p>
<pre><code class="lang-plaintext">groovyCopy codepipeline {
    agent any
    triggers {
        cron('H 0 * * *')
    }
    stages {
        stage('Run Tests') {
            steps {
                sh 'npm test'
            }
        }
    }
}
</code></pre>
<h2 id="heading-how-to-integrate-testops-into-global-devops-pipelines">How to Integrate TestOps into Global DevOps Pipelines</h2>
<p>Integrating TestOps into global DevOps pipelines is crucial for maintaining software quality across distributed teams. This integration makes testing a seamless and automated part of the software delivery process, helping improve and release software quickly.</p>
<p>Tools like containerization and orchestration platforms play a big role in scaling TestOps across global pipelines. Here’s how to do it effectively:</p>
<h3 id="heading-test-early-and-continuously"><strong>Test Early and Continuously</strong></h3>
<p>When you start testing early in the development cycle, you catch issues before they reach production. This early approach allows developers to fix bugs while the changes are still fresh. It also prevents those issues from becoming bigger later.</p>
<p>Continuous testing means tests run automatically whenever code changes. These are usually triggered during the Continuous Integration (CI) process. Since the tests run right after a change is made, feedback is quick.</p>
<p>This quick feedback helps reduce debugging time. It also supports teams working from different regions, since they can move ahead without waiting on others. And because tests fail fast, blockers are identified early and resolved quickly.</p>
<p><strong>Example:</strong><br>A global logistics company uses GitHub Actions to run unit and integration tests whenever a developer submits a pull request. The setup alerts developers immediately if any test fails. Since the teams are based in India, the US, and Germany, this system helps them work independently. It also avoids delays that often happen due to time zone differences.</p>
<h3 id="heading-automate-test-execution"><strong>Automate Test Execution</strong></h3>
<p>Using test automation frameworks lets you run tests automatically across different stages of development. Tools like TestNG, Playwright, and Cypress can help you do this easily. These tools are great for saving time and reducing human error.</p>
<p>By automating the test process, you avoid the need for manual execution. This also makes regression testing more manageable, especially in large applications. It gives your team more confidence to release code frequently.</p>
<p>As tests run on every code change, any new issues are quickly caught. And because automation supports repeatability, it keeps testing consistent across teams.</p>
<p><strong>Example:</strong><br>A healthcare software company uses Cypress for automating UI tests. These tests are connected with GitLab CI and run whenever someone updates a feature branch. The tests run in parallel containers, which helps speed up the process. This setup ensures key features are always verified before merging code. Even when several features are being developed at once, their system keeps everything on track.</p>
<p><strong>Sample Cypress Test:</strong></p>
<pre><code class="lang-javascript">javascriptCopy codedescribe(<span class="hljs-string">'Login Functionality'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should log in with valid credentials'</span>, <span class="hljs-function">() =&gt;</span> {
    cy.visit(<span class="hljs-string">'https://app.healthcare-demo.com/login'</span>)
    cy.get(<span class="hljs-string">'input[name=email]'</span>).type(<span class="hljs-string">'testuser@demo.com'</span>)
    cy.get(<span class="hljs-string">'input[name=password]'</span>).type(<span class="hljs-string">'securePassword123'</span>)
    cy.get(<span class="hljs-string">'button[type=submit]'</span>).click()
    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/dashboard'</span>)
    cy.contains(<span class="hljs-string">'Welcome back'</span>)
  })
})
</code></pre>
<p><strong>GitLab CI Configuration (gitlab-ci.yml):</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">yamlCopy codestages:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">test</span>

<span class="hljs-attr">cypress_tests:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">test</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">cypress/browsers:node-18.12.0-chrome-106</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">npx</span> <span class="hljs-string">cypress</span> <span class="hljs-string">run</span>
  <span class="hljs-attr">artifacts:</span>
    <span class="hljs-attr">when:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">cypress/videos/</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">cypress/screenshots/</span>
  <span class="hljs-attr">only:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">merge_requests</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">branches</span>
</code></pre>
<p>This code demonstrates how Cypress runs UI tests, and how GitLab CI automatically triggers those tests when a new branch is pushed or a merge request is created. It reflects the kind of scalable, repeatable test execution process that supports global software teams.</p>
<h3 id="heading-use-containerization-for-environment-consistency"><strong>Use Containerization for Environment Consistency</strong></h3>
<p>When you use Docker, you create containers that include your application and everything it needs to run. These containers can be shared and used anywhere. That means every developer and tester uses the same environment.</p>
<p>This removes the “it works on my machine” issue. It also helps create identical setups across development, staging, QA, and production. Since everyone works with the same tools and settings, there are fewer environment-related bugs.</p>
<p>With containerization, it becomes easier to test on different systems without needing to reconfigure anything. And it helps teams in different locations stay in sync.</p>
<p><strong>Example:</strong><br>A fintech startup packages its API and testing framework using Docker. These containers are then used inside Azure DevOps pipelines. The same tests run across staging, QA, and production environments. Since the containers do not change, the results are always reliable and consistent.</p>
<p><strong>Sample Dockerfile for API Testing:</strong></p>
<pre><code class="lang-dockerfile">DockerfileCopy code<span class="hljs-comment"># Use official Node.js image</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>

<span class="hljs-comment"># Set working directory</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /usr/src/app</span>

<span class="hljs-comment"># Copy package files</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>

<span class="hljs-comment"># Install dependencies</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-comment"># Copy test files and configuration</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-comment"># Run tests (can be overridden in CI/CD)</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"test"</span>]</span>
</code></pre>
<p><strong>Example docker-compose.yml:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">yamlCopy codeversion:</span> <span class="hljs-string">'3.8'</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">api:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">fintech-api:latest</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NODE_ENV=test</span>

  <span class="hljs-attr">tests:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">./tests</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">api</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test</span>
</code></pre>
<p>This setup builds two containers – one for the API and one for the test runner. It mirrors how the startup runs its tests inside Azure DevOps pipelines. Developers, QA, and staging environments all use the same containers, reducing variability and giving more predictable test results.</p>
<h3 id="heading-enable-scalable-test-orchestration"><strong>Enable Scalable Test Orchestration</strong></h3>
<p>When tests are run in parallel across different environments, they finish faster. Tools like Selenium Grid, and LambdaTest help you do this. These platforms allow you to test across various browsers, operating systems, and devices.</p>
<p>By running tests this way, you save time and cover more scenarios at once. This is especially useful when your product needs to work globally. It ensures users across different regions have the same experience.</p>
<p>Parallel testing also helps teams working in different time zones. While one team sleeps, another team can pick up where they left off.</p>
<p><strong>Example:</strong><br>A retail company uses LambdaTest to run regression tests every night. These tests cover Chrome, Firefox, and Safari, both on desktop and mobile. Since the tests run in parallel, the UK team finishes validation before the US team starts their workday. This keeps their pipeline flowing without delays.</p>
<p><strong>Sample Setup for Parallel Execution with Selenium Grid (Docker-based):</strong></p>
<pre><code class="lang-yaml"><span class="hljs-string">yamlCopy</span> <span class="hljs-string">code#</span> <span class="hljs-string">docker-compose.yml</span>
<span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">selenium-hub:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">selenium/hub:4.18.1</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4442:4442"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4443:4443"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4444:4444"</span>

  <span class="hljs-attr">chrome:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">selenium/node-chrome:4.18.1</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">selenium-hub</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">SE_EVENT_BUS_HOST=selenium-hub</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">SE_EVENT_BUS_PUBLISH_PORT=4442</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">SE_EVENT_BUS_SUBSCRIBE_PORT=4443</span>

  <span class="hljs-attr">firefox:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">selenium/node-firefox:4.18.1</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">selenium-hub</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">SE_EVENT_BUS_HOST=selenium-hub</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">SE_EVENT_BUS_PUBLISH_PORT=4442</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">SE_EVENT_BUS_SUBSCRIBE_PORT=4443</span>
</code></pre>
<p>This creates a Selenium Grid with Chrome and Firefox nodes that connect to the central hub. It allows you to distribute your tests in parallel across browsers.</p>
<p><strong>Sample Java Test (TestNG) for Running in Parallel:</strong></p>
<pre><code class="lang-java">javaCopy codepublic <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ParallelTest</span> </span>{
    WebDriver driver;

    <span class="hljs-meta">@Parameters("browser")</span>
    <span class="hljs-meta">@BeforeMethod</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">(String browser)</span> <span class="hljs-keyword">throws</span> MalformedURLException </span>{
        DesiredCapabilities capabilities = <span class="hljs-keyword">new</span> DesiredCapabilities();

        <span class="hljs-keyword">if</span> (browser.equalsIgnoreCase(<span class="hljs-string">"chrome"</span>)) {
            capabilities.setBrowserName(<span class="hljs-string">"chrome"</span>);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (browser.equalsIgnoreCase(<span class="hljs-string">"firefox"</span>)) {
            capabilities.setBrowserName(<span class="hljs-string">"firefox"</span>);
        }

        driver = <span class="hljs-keyword">new</span> RemoteWebDriver(<span class="hljs-keyword">new</span> URL(<span class="hljs-string">"http://localhost:4444/wd/hub"</span>), capabilities);
    }

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">runTest</span><span class="hljs-params">()</span> </span>{
        driver.get(<span class="hljs-string">"https://retail-demo.com"</span>);
        Assert.assertTrue(driver.getTitle().contains(<span class="hljs-string">"Retail"</span>));
    }

    <span class="hljs-meta">@AfterMethod</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">tearDown</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">if</span> (driver != <span class="hljs-keyword">null</span>) {
            driver.quit();
        }
    }
}
</code></pre>
<p><strong>TestNG Parallel Configuration (testng.xml):</strong></p>
<pre><code class="lang-xml">xmlCopy code<span class="hljs-tag">&lt;<span class="hljs-name">suite</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ParallelTests"</span> <span class="hljs-attr">parallel</span>=<span class="hljs-string">"tests"</span> <span class="hljs-attr">thread-count</span>=<span class="hljs-string">"2"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">test</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ChromeTest"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"browser"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"chrome"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">classes</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">class</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ParallelTest"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">classes</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">test</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">test</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"FirefoxTest"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">parameter</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"browser"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"firefox"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">classes</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">class</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ParallelTest"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">classes</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">test</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">suite</span>&gt;</span>
</code></pre>
<p>This setup allows tests to run in parallel on both Chrome and Firefox, using Selenium Grid hosted locally with Docker. It mirrors how global teams can scale their test execution and speed up feedback loops.</p>
<h3 id="heading-role-of-containerization-and-orchestration-tools"><strong>Role of Containerization and Orchestration Tools</strong></h3>
<p>Tools like Docker and Kubernetes are essential for integrating TestOps into global DevOps pipelines.</p>
<ul>
<li><p><strong>Docker</strong> creates lightweight, repeatable test environments that can be quickly set up or shut down. By packaging apps and their dependencies into containers, Docker ensures tests run consistently, whether on local machines or in the cloud.</p>
</li>
<li><p><strong>Kubernetes</strong> manages the deployment and scaling of containerized apps. In TestOps, Kubernetes automates test execution across multiple containers, speeding up testing in global pipelines. It helps scale testing to handle large volumes of tests in different environments and locations.</p>
</li>
</ul>
<p>Containerization with Docker and orchestration with Kubernetes can streamline the testing process, especially for global DevOps pipelines.</p>
<h4 id="heading-step-1-docker-for-repeatable-test-environments">Step 1: Docker for Repeatable Test Environments</h4>
<p>You can use Docker to package your application along with its dependencies into a container, making it easy to run tests consistently across various environments.</p>
<h5 id="heading-example-dockerfile-for-test-environment"><strong>Example Dockerfile for Test Environment:</strong></h5>
<pre><code class="lang-dockerfile">dockerfileCopy codeFROM node:<span class="hljs-number">14</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># Install dependencies</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-comment"># Copy application code</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-comment"># Run tests</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"test"</span>]</span>
</code></pre>
<p>This Dockerfile sets up a container to install dependencies and run the tests using <code>npm test</code>.</p>
<h4 id="heading-step-2-kubernetes-for-orchestration">Step 2: Kubernetes for Orchestration</h4>
<p>You can use Kubernetes to scale the test execution across multiple containers. Kubernetes can manage the deployment of containers and automatically distribute them across nodes, enabling parallel testing.</p>
<h5 id="heading-example-kubernetes-deployment-yaml"><strong>Example Kubernetes Deployment YAML:</strong></h5>
<pre><code class="lang-yaml"><span class="hljs-attr">yamlCopy codeapiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">test-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>  <span class="hljs-comment"># Number of containers to run in parallel</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">test-app</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">test-app</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">test-container</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">my-app-test-env:latest</span>  <span class="hljs-comment"># Docker image built earlier</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<p>This Kubernetes deployment configuration specifies that 3 replicas (containers) of the test environment will be created and run in parallel. This helps speed up the testing process by distributing the workload.</p>
<h4 id="heading-step-3-running-tests-in-kubernetes">Step 3: Running Tests in Kubernetes</h4>
<p>Once you have the containers and Kubernetes deployment configured, you can integrate this setup into your CI/CD pipeline. Kubernetes can handle scaling the test execution, making it ideal for global pipelines where tests need to run across different environments.</p>
<h5 id="heading-example-kubernetes-command-to-deploy"><strong>Example Kubernetes Command to Deploy:</strong></h5>
<pre><code class="lang-bash">bashCopy codekubectl apply -f test-deployment.yaml
</code></pre>
<p>This command deploys the test containers to the Kubernetes cluster, ensuring that your tests run across multiple containers, in parallel, at scale.</p>
<h3 id="heading-continuous-monitoring-and-feedback"><strong>Continuous Monitoring and Feedback</strong></h3>
<p>TestOps relies on continuous monitoring to provide real-time insights into test results, performance, and system health.</p>
<p>Kubernetes helps manage testing resources and spot problems quickly. Real-time feedback from automated tests lets developers fix issues immediately, improving software quality.</p>
<h3 id="heading-cross-tool-integration"><strong>Cross-Tool Integration</strong></h3>
<p>TestOps works well with different DevOps tools, creating a smooth feedback loop. It connects test management platforms (like TestRail) with CI/CD tools (like Jenkins or GitLab CI) and uses containerized environments to run tests consistently. Kubernetes ensures testing resources scale automatically to meet the needs of global teams.</p>
<h3 id="heading-shift-left-testing"><strong>Shift-Left Testing</strong></h3>
<p>TestOps follows a <a target="_blank" href="https://www.freecodecamp.org/news/what-is-shift-left-in-software/">shift-left approach</a>, which means integrating testing earlier in the pipeline to catch issues right away. Running tests in containerized environments speeds up testing and allows teams to find problems earlier in the development process, reducing risks and improving quality.</p>
<h4 id="heading-shift-left-testing-with-docker-and-ci">Shift-Left Testing with Docker and CI</h4>
<p>Shift-left testing integrates tests early in the development pipeline to catch issues sooner. Using Docker in a CI pipeline automates test execution in a consistent environment.</p>
<h4 id="heading-dockerfile-example"><strong>Dockerfile Example:</strong></h4>
<pre><code class="lang-dockerfile">dockerfileCopy codeFROM node:<span class="hljs-number">14</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"test"</span>]</span>
</code></pre>
<h4 id="heading-jenkinsfile-example"><strong>Jenkinsfile Example:</strong></h4>
<pre><code class="lang-plaintext">groovyCopy codepipeline {
    agent any
    environment {
        DOCKER_IMAGE = 'my-app-test-env'
    }
    stages {
        stage('Checkout') {
            steps {
                git 'https://github.com/your-repository/my-app.git'
            }
        }
        stage('Build Docker Image') {
            steps {
                script {
                    sh 'docker build -t $DOCKER_IMAGE .'
                }
            }
        }
        stage('Run Tests') {
            steps {
                script {
                    sh 'docker run --rm $DOCKER_IMAGE'
                }
            }
        }
    }
    post {
        always {
            sh 'docker rmi $DOCKER_IMAGE'
        }
    }
}
</code></pre>
<h3 id="heading-scalability-and-flexibility"><strong>Scalability and Flexibility</strong></h3>
<p>Global teams need to manage large test environments. Containerization and Kubernetes provide the scalability needed to run thousands of tests across different regions at once. Containers package tests into small, isolated environments, while Kubernetes automates their scaling and management, keeping testing efficient as the pipeline grows.</p>
<h2 id="heading-how-to-use-ai-and-analytics-in-testops">How to Use AI and Analytics in TestOps</h2>
<p>When you bring AI and analytics into TestOps, it helps simplify complex testing tasks. It reduces manual work, improves accuracy, and supports better decision-making. Since DevOps teams often work across regions, this becomes even more important.</p>
<p>AI helps reduce repetitive testing tasks, while analytics turns test data into clear insights. Together, they create smarter and faster testing pipelines. And because these pipelines are shared globally, consistency is key.</p>
<h3 id="heading-what-tools-can-you-use">What Tools Can You Use?</h3>
<p>There are different tools that support AI and analytics in TestOps. Some focus on automation with intelligence, while others give you clear visibility into your test data.</p>
<p>AI-powered testing tools like Mabl, Testim, and Functionize use machine learning. These tools help create, run, and even repair test cases when the app changes. Since applications change frequently, these tools help keep your tests up to date.</p>
<p>They also save time on maintenance, since the tests adjust themselves when needed. And because the tools learn from patterns, they help teams catch issues faster.</p>
<p>Analytics and observability platforms such as TestRail Analytics, Xray, Grafana, and Kibana focus on trends. They turn raw test results into visual dashboards and alerts.</p>
<p>These platforms connect with CI/CD tools, so you get real-time updates on test quality. This makes it easier for teams to stay on top of what matters, even when they are spread across locations.</p>
<h4 id="heading-example-running-a-working-test-on-lambdatest">Example – Running a Working Test on LambdaTest</h4>
<p>LambdaTest lets you execute real browser tests in the cloud, making it easy to scale your testing across browsers and OS combinations. Here is a working example using Python and Selenium, which opens a page, checks the title, and closes the browser:</p>
<pre><code class="lang-python">pythonCopy codefrom selenium <span class="hljs-keyword">import</span> webdriver
<span class="hljs-keyword">from</span> selenium.webdriver.common.by <span class="hljs-keyword">import</span> By

<span class="hljs-comment"># Define LambdaTest capabilities</span>
capabilities = {
  <span class="hljs-string">"browserName"</span>: <span class="hljs-string">"Chrome"</span>,
  <span class="hljs-string">"browserVersion"</span>: <span class="hljs-string">"latest"</span>,
  <span class="hljs-string">"LT:Options"</span>: {
    <span class="hljs-string">"platformName"</span>: <span class="hljs-string">"Windows 11"</span>,
    <span class="hljs-string">"build"</span>: <span class="hljs-string">"TestOps Working Demo"</span>,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Title Verification Test"</span>,
    <span class="hljs-string">"selenium_version"</span>: <span class="hljs-string">"4.8.0"</span>,
    <span class="hljs-string">"w3c"</span>: <span class="hljs-literal">True</span>
  }
}

<span class="hljs-comment"># Replace with your LambdaTest username and access key</span>
USERNAME = <span class="hljs-string">"your_username"</span>
ACCESS_KEY = <span class="hljs-string">"your_access_key"</span>

<span class="hljs-comment"># Connect to LambdaTest cloud grid</span>
driver = webdriver.Remote(
    command_executor=<span class="hljs-string">f"https://<span class="hljs-subst">{USERNAME}</span>:<span class="hljs-subst">{ACCESS_KEY}</span>@hub.lambdatest.com/wd/hub"</span>,
    desired_capabilities=capabilities
)

<span class="hljs-keyword">try</span>:
    <span class="hljs-comment"># Step 1: Navigate to the app under test</span>
    driver.get(<span class="hljs-string">"https://www.lambdatest.com/selenium-playground/"</span>)

    <span class="hljs-comment"># Step 2: Interact with the page (click a link)</span>
    driver.find_element(By.LINK_TEXT, <span class="hljs-string">"Simple Form Demo"</span>).click()

    <span class="hljs-comment"># Step 3: Enter message and verify output</span>
    message_box = driver.find_element(By.ID, <span class="hljs-string">"user-message"</span>)
    message_box.send_keys(<span class="hljs-string">"TestOps in action!"</span>)
    driver.find_element(By.ID, <span class="hljs-string">"showInput"</span>).click()

    output = driver.find_element(By.ID, <span class="hljs-string">"message"</span>).text
    <span class="hljs-keyword">assert</span> output == <span class="hljs-string">"TestOps in action!"</span>, <span class="hljs-string">"Message output did not match input."</span>

    print(<span class="hljs-string">"✅ Test Passed: Message displayed correctly."</span>)
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
    print(<span class="hljs-string">"❌ Test Failed:"</span>, e)
<span class="hljs-keyword">finally</span>:
    driver.quit()
</code></pre>
<p>What this test does:</p>
<ul>
<li><p>Launches a browser on LambdaTest cloud</p>
</li>
<li><p>Navigates to their Selenium Playground</p>
</li>
<li><p>Fills out a form and clicks a button</p>
</li>
<li><p>Verifies that the output matches the input</p>
</li>
<li><p>Logs the result and closes the session</p>
</li>
</ul>
<p>Once the test finishes, you can view detailed logs, screenshots, and video recordings on the LambdaTest Automation Dashboard, which also includes AI-based debugging info.</p>
<h3 id="heading-what-types-of-problems-can-ml-help-solve-and-how">What Types of Problems Can ML Help Solve — and How?</h3>
<p>Machine learning can solve several pain points in testing. It looks at patterns in your data and helps identify things that manual checks might miss.</p>
<h4 id="heading-flaky-tests"><strong>Flaky Tests:</strong></h4>
<p>ML helps detect tests that pass and fail randomly across different builds. It finds patterns in those failures and flags the ones that are unstable. And by doing this early, it prevents teams from wasting time chasing false bugs.</p>
<h4 id="heading-test-prioritization"><strong>Test Prioritization:</strong></h4>
<p>ML studies your past test results and recent code changes. It then ranks your tests based on risk and importance. So, the most critical ones run first. This way, your pipeline moves faster without skipping key checks.</p>
<h4 id="heading-failure-prediction"><strong>Failure Prediction:</strong></h4>
<p>ML uses logs, crash reports, and previous outcomes to predict where failures may happen. If it finds something risky, it warns the team in advance. This gives them time to fix problems before they grow bigger.</p>
<h4 id="heading-root-cause-clustering"><strong>Root Cause Clustering:</strong></h4>
<p>When many tests fail at once, ML groups them by shared failure reasons. It helps you understand whether the issue is with one module or across several. That means your team can solve the actual root problem quicker.</p>
<h4 id="heading-anomaly-detection"><strong>Anomaly Detection:</strong></h4>
<p>ML tracks things like test duration and system behavior. If something suddenly changes, like a test taking too long or using too much memory, it flags it. These alerts help teams spot performance dips early.</p>
<h3 id="heading-what-types-of-analytics-tools-can-you-use">What Types of Analytics Tools Can You Use?</h3>
<p>Analytics tools help you turn your test results into useful information. They highlight patterns, gaps, and areas that need your attention. And because these insights are visual, they are easier to act on.</p>
<p>These tools can show how your pass/fail rates have changed over time. They also help you check which parts of your app are not covered by tests. If some tests are being skipped or are too flaky, the tools will highlight that too.</p>
<p>They also measure how long tests take to run and where your pipeline slows down. This helps teams reduce bottlenecks and improve efficiency.</p>
<p>Some platforms include dashboards that link test quality to deployment status. This gives you a clear picture of whether your product is ready for release.</p>
<p>They also track failures by environment—like which browsers or regions face more issues. This helps teams debug faster and improve global reliability.</p>
<p>All of these insights help QA and DevOps teams improve their strategies. They let you remove unnecessary tests, fix flaky ones, and focus where testing matters the most.</p>
<h3 id="heading-example-using-grafana-and-kibana-for-test-analytics"><strong>Example – Using Grafana and Kibana for Test Analytics</strong></h3>
<p>Analytics tools help your teams understand test trends, flakiness, coverage gaps, and slowdowns in the CI/CD pipeline. Here is how you can set them up to actually <em>work</em> with your test data.</p>
<h4 id="heading-example-1-visualizing-test-results-in-grafana-using-influxdb">Example 1: Visualizing Test Results in Grafana using InfluxDB</h4>
<p>Grafana is commonly paired with InfluxDB to display metrics such as pass/fail rates, test durations, and failure frequencies. Here’s how you can push your test results into InfluxDB and visualize them in Grafana.</p>
<h5 id="heading-step-by-step-setup"><strong>Step-by-step setup:</strong></h5>
<ol>
<li><p>Push test results to InfluxDB after each test run. This can be done from Jenkins, GitHub Actions, or any test automation framework that generates test results.</p>
</li>
<li><p>Query and visualize data in Grafana using InfluxDB as the data source.</p>
</li>
</ol>
<h5 id="heading-python-script-to-send-test-metrics-to-influxdb"><strong>Python script to send test metrics to InfluxDB:</strong></h5>
<pre><code class="lang-python">pythonCopy codefrom influxdb <span class="hljs-keyword">import</span> InfluxDBClient
<span class="hljs-keyword">import</span> time

<span class="hljs-comment"># Create an InfluxDB client to send data</span>
client = InfluxDBClient(host=<span class="hljs-string">'localhost'</span>, port=<span class="hljs-number">8086</span>)
client.switch_database(<span class="hljs-string">'test_metrics'</span>)  <span class="hljs-comment"># Switch to your specific database</span>

<span class="hljs-comment"># Example test result data</span>
json_body = [
    {
        <span class="hljs-string">"measurement"</span>: <span class="hljs-string">"test_results"</span>,
        <span class="hljs-string">"tags"</span>: {
            <span class="hljs-string">"test_suite"</span>: <span class="hljs-string">"login_tests"</span>,  <span class="hljs-comment"># Name of the test suite</span>
            <span class="hljs-string">"environment"</span>: <span class="hljs-string">"staging"</span>  <span class="hljs-comment"># Environment like 'production', 'staging', etc.</span>
        },
        <span class="hljs-string">"time"</span>: time.strftime(<span class="hljs-string">'%Y-%m-%dT%H:%M:%SZ'</span>),  <span class="hljs-comment"># Timestamp for the test execution</span>
        <span class="hljs-string">"fields"</span>: {
            <span class="hljs-string">"pass"</span>: <span class="hljs-number">10</span>,        <span class="hljs-comment"># Number of tests that passed</span>
            <span class="hljs-string">"fail"</span>: <span class="hljs-number">2</span>,         <span class="hljs-comment"># Number of tests that failed</span>
            <span class="hljs-string">"skipped"</span>: <span class="hljs-number">1</span>,      <span class="hljs-comment"># Number of tests that were skipped</span>
            <span class="hljs-string">"duration"</span>: <span class="hljs-number">12.5</span>   <span class="hljs-comment"># Duration of the test run in seconds</span>
        }
    }
]

<span class="hljs-comment"># Write the data points to InfluxDB</span>
client.write_points(json_body)
</code></pre>
<h5 id="heading-grafana-setup">Grafana setup:</h5>
<ul>
<li><p><strong>Data Source</strong>: In Grafana, connect to your InfluxDB instance.</p>
</li>
<li><p><strong>Dashboard</strong>: Create a dashboard that queries the <code>test_results</code> measurement and display:</p>
<ul>
<li><p>Line charts for pass/fail trends over time.</p>
</li>
<li><p>Pie charts for distribution of test results.</p>
</li>
<li><p>Table showing tests and their durations.</p>
</li>
</ul>
</li>
</ul>
<p>This approach helps you track key metrics and trends for each test suite and environment.</p>
<h4 id="heading-example-2-debugging-failures-by-environment-with-kibana-and-elasticsearch">Example 2: Debugging Failures by Environment with Kibana and Elasticsearch</h4>
<p>If your testing framework logs results into Elasticsearch, you can use Kibana to analyze and visualize those logs. For example, you can track which browsers or regions are facing more issues and display the results in Kibana.</p>
<h5 id="heading-elasticsearch-data-model"><strong>Elasticsearch Data Model:</strong></h5>
<p>First, let’s assume that test results are logged in Elasticsearch with the following format:</p>
<pre><code class="lang-json">jsonCopy code{
  <span class="hljs-attr">"timestamp"</span>: <span class="hljs-string">"2025-04-16T14:00:00Z"</span>,
  <span class="hljs-attr">"test_name"</span>: <span class="hljs-string">"checkout_flow_mobile"</span>,  # Name of the test
  <span class="hljs-attr">"status"</span>: <span class="hljs-string">"fail"</span>,  # Pass or fail status
  <span class="hljs-attr">"browser"</span>: <span class="hljs-string">"Safari"</span>,  # Browser used for the test
  <span class="hljs-attr">"region"</span>: <span class="hljs-string">"EU-West"</span>,  # Region where the test was run
  <span class="hljs-attr">"error"</span>: <span class="hljs-string">"Element not visible"</span>,  # Error message in case of failure
  <span class="hljs-attr">"duration"</span>: <span class="hljs-number">9.8</span>,  # Duration of the test in seconds
  <span class="hljs-attr">"env"</span>: <span class="hljs-string">"QA"</span>  # Environment where the test ran
}
</code></pre>
<h5 id="heading-kibana-setup"><strong>Kibana setup:</strong></h5>
<ol>
<li><p><strong>Data Ingestion</strong>: Your CI pipeline or test scripts push results to Elasticsearch after each run.</p>
</li>
<li><p><strong>Create Visualizations</strong>: In Kibana, create visualizations like:</p>
<ul>
<li><p><strong>Pie Chart</strong>: Show failure rates by browser type (e.g., Chrome, Firefox, Safari).</p>
</li>
<li><p><strong>Line Chart</strong>: Track test durations over time for a specific test suite.</p>
</li>
<li><p><strong>Table</strong>: Display flaky tests that fail repeatedly by region or environment.</p>
</li>
</ul>
</li>
</ol>
<p>Example of a Kibana query that you might use to filter failures by browser:</p>
<pre><code class="lang-json">jsonCopy code{
  <span class="hljs-attr">"query"</span>: {
    <span class="hljs-attr">"bool"</span>: {
      <span class="hljs-attr">"must"</span>: [
        { <span class="hljs-attr">"match"</span>: { <span class="hljs-attr">"status"</span>: <span class="hljs-string">"fail"</span> }},
        { <span class="hljs-attr">"match"</span>: { <span class="hljs-attr">"browser"</span>: <span class="hljs-string">"Safari"</span> }}
      ]
    }
  }
}
</code></pre>
<p>This will show all test failures in Safari, helping you identify browser-specific issues.</p>
<h3 id="heading-why-this-matters"><strong>Why This Matters</strong></h3>
<p>Using Grafana and Kibana with your test results helps your team gain valuable insights:</p>
<ul>
<li><p>Identify flaky tests and prioritize them for maintenance.</p>
</li>
<li><p>Track performance trends, including test duration and failure rates.</p>
</li>
<li><p>Debug faster by identifying failure patterns tied to specific browsers, environments, or regions.</p>
</li>
</ul>
<p>With these analytics in place, teams can make data-driven decisions to improve test coverage, reduce bottlenecks, and ensure better product quality.</p>
<h2 id="heading-future-of-testops-in-global-development">Future of TestOps in Global Development</h2>
<p>As global software delivery keeps growing more complex, TestOps is changing fast. It is no longer just a supporting function. Instead, it is becoming a central part of DevOps strategies. And as development moves faster, this shift is only expected to continue.</p>
<p>Here are some of the key changes that are shaping the future of TestOps. You will need to watch for them and prepare ahead.</p>
<h3 id="heading-ai-driven-decision-making-in-testops">AI-Driven Decision-Making in TestOps</h3>
<p>AI in TestOps is moving beyond just automating test cases or running scripts. It is starting to take on decision-making roles within the testing process. For instance, orchestration tools powered by AI will soon go further.</p>
<p>They will decide which tests should run, based on recent code changes and how those changes affect the business. They will also predict which parts of the system are more likely to break in the next release.</p>
<p>And as these tools learn from real-time usage patterns, they will suggest ways to improve your test strategy. That means TestOps professionals will not only need to know how to use these tools. They will also need to understand how to read the insights and make smart choices with them.</p>
<h3 id="heading-cloud-native-and-edge-testing">Cloud-Native and Edge Testing</h3>
<p>While teams are already embracing cloud-based testing, the next step is even more distributed. This is where edge-aware TestOps comes in. It focuses on testing software where it is used, not just where it is built.</p>
<p>That means tests will run closer to the user, in specific networks or regional setups. It will also mean checking how systems perform in places with different latency or unreliable connectivity.</p>
<p>And as data laws vary across regions, teams will need to manage test data carefully and securely. Because TestOps now stretches across countries and cloud platforms, it must adapt to decentralized architectures.</p>
<h3 id="heading-testops-as-a-unifier-of-observability-and-automation">TestOps as a Unifier of Observability and Automation</h3>
<p>TestOps will not only be about running tests anymore. It will play a larger role in bringing together testing, monitoring, and automation across the pipeline. This will create a more complete view of system health and product quality.</p>
<p>Tools under TestOps will start using production monitoring data to improve test design. If something fails in production, it can guide what should be tested next.</p>
<p>Real-time behavior from users can even trigger specific regression tests. This helps teams fix issues faster and smarter. As a result, TestOps will create a feedback loop between pre-release and post-release stages.</p>
<p>That means teams will not only rely on TestOps for test automation. They will also use it to see how everything connects—from development to operations.</p>
<h3 id="heading-skill-shifts-in-testops-teams">Skill Shifts in TestOps Teams</h3>
<p>As testing tools become more advanced, the skills that TestOps teams need will change too. There will be less demand for manual testing roles. But there will be more need for engineers who think strategically about quality.</p>
<p>You will see more roles focused on site reliability, automation frameworks, and test strategy. These roles will require knowledge of cloud infrastructure and continuous delivery.</p>
<p>And instead of working in silos, testers will work closely with developers and ops teams. This shift will require people who can think across functions and understand how everything fits together.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Scaling TestOps for global software development teams is essential in today’s fast-moving, distributed work environment. By using best practices like standardizing tools, automating tests, promoting collaboration, and taking advantage of cloud and AI solutions, teams can ensure smooth, high-quality software delivery across different regions and time zones.</p>
<p>As TestOps evolves with advances in automation, AI, and cloud technology, it will make the testing process even more efficient. Teams will be able to respond faster, predict problems before they happen, and maintain high-quality standards.</p>
<p>The future of TestOps looks even more promising with smarter tools, better collaboration, and more automation, driving success for global development teams and improving the entire software development process.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use AI to Automate Unit Testing with TestGen-LLM and Cover-Agent ]]>
                </title>
                <description>
                    <![CDATA[ It's important to write clear and efficient unit tests that actually work during the software development process. Unit tests separate out individual code elements and confirm that they work as intended. Effective unit tests not only catch errors but... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/automated-unit-testing-with-testgen-llm-and-cover-agent/</link>
                <guid isPermaLink="false">66d4608a246e57ac83a2c7bb</guid>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ LLM&#39;s  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwadamisi Samuel ]]>
                </dc:creator>
                <pubDate>Mon, 03 Jun 2024 14:45:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/06/Final-X3-Cover-FFC-Cover-Agent.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>It's important to write clear and efficient unit tests that actually work during the software development process. Unit tests separate out individual code elements and confirm that they work as intended.</p>
<p>Effective unit tests not only catch errors but also help you be confident that your code can be maintained and is dependable. But it takes time and resources to manually create an extensive suite of unit tests.</p>
<p>There have been some recent developments in artificial intelligence that promise to help automate the unit test development processes. In February, researchers at Meta released a paper on <a target="_blank" href="https://arxiv.org/abs/2402.09171">Automated Unit Test Improvement using Large Language Models</a>. This introduced an innovative method for automating unit testing.</p>
<p>Their research focuses on a new tool called <code>TestGen-LLM</code>, which explores the possibilities of using LLMs to analyze already-existing unit tests and improving on them to increase code coverage.</p>
<p>Although the code for the TestGen-LLM was not released, I will introduce an open-source alternative inspired by their research in this article. You'll learn how it generates test suites, why it's better than most LLMs, and where to get your hands on this technology and start trying it out.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-metas-testgen-llm">Meta’s TestGen-LLM</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-testgen-llm-work">How does TestGen-LLM work?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-open-source-implementation-cover-agent">Open-Source Implementation (Cover-Agent)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-cover-agent-work">How does Cover-Agent work?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-cover-agent">How to Use Cover-Agent</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-of-the-open-source-cover-agent">Benefits of the Open-Source Cover-Agent</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-can-you-contribute-to-this-technology">How can you Contribute to this Technology?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-metas-testgen-llm">Meta’s TestGen-LLM</h2>
<p>Meta's TestGen-LLM tackles the time-consuming task of unit test writing by leveraging the power of Large Language Models (LLMs). General-purpose LLMs like Gemini or ChatGPT might struggle with the specific domain of unit test code, testing syntax, and generating tests that don't add value. But TestGen-LLM is specifically tailored for unit testing.</p>
<p>This specialization allows it to understand the intricacies of code structure and test logic, leading to more targeted test suites and generate tests that actually add value and increase code coverage.</p>
<p>TestGen-LLM is able to evaluate unit tests and identify areas for improvements. It achieves this through its understanding of common testing patterns which it has been trained with. But generating tests alone is insufficient for proper code coverage.</p>
<p>Meta's researchers implemented safeguards within TestGen-LLM to ensure the effectiveness of the tests it writes. These safeguards, referred to as <code>filters</code>, act as a quality control mechanism. They eliminate suggestions that:</p>
<ul>
<li><p>wouldn't compile</p>
</li>
<li><p>fail consistently, or</p>
</li>
<li><p>fail to actually improve code coverage (suggestions that are already covered by other tests).</p>
</li>
</ul>
<h2 id="heading-how-does-testgen-llm-work">How Does TestGen-LLM Work?</h2>
<p>TestGen-LLM uses an approach called "Assured LLM-based Software Engineering" (Assured LLMSE). TestGen-LLM simply augments an existing test class with additional test cases, retaining all existing test cases and thereby guaranteeing that there will be no regression.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/img-testgen-llm-paper.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Test generation workflow(</em><a target="_blank" href="https://arxiv.org/abs/2402.09171"><em>From TestGen_LLM paper</em></a><em>)</em></p>
<p>The TestGen-LLM generates a bunch of tests, then filters out the tests that don’t run and drops any that don’t pass. Finally, it discards those that don't increase the code coverage.</p>
<p>After using the TestGen-LLM to automate a test suite, Meta used a human reviewer to accept or reject tests where the generated tests had an acceptance rate of 73% in their best reported cases.</p>
<p>According to the paper, TestGen-LLM generates a single test on each run that is then added to an existing test suite which was written previously by a developer. But it doesn’t necessarily generate tests for any given test suite.</p>
<p>The effectiveness of TestGen-LLM was demonstrated at Meta's internal test-a-thons. Here, the tool was used to analyze existing test suites and suggest improvements. The results were promising:</p>
<blockquote>
<p><em>“75% of TestGen-LLM's test cases built correctly, 57% passed reliably, and 25% increased coverage. During Meta's Instagram and Facebook test-a-thons, it improved 11.5% of all classes to which it was applied, with 73% of its recommendations being accepted for production deployment by Meta software engineers”</em>.</p>
</blockquote>
<p>Also, the recommendations from TestGen-LLM were deemed useful and relevant by the developers who took part in the test-a-thons.</p>
<h2 id="heading-open-source-implementation-cover-agent">Open-Source Implementation (Cover-Agent)</h2>
<p>The TestGen-LLM research from Meta has a lot of potential to change unit testing and automated test generation. The tool will likely help improve code coverage and speed up test creation by utilizing LLMs particularly trained on code. But this technology is not available for use by just anyone, since the code for the TestGen-LLM was not released.</p>
<p>Developers who have taken an interest in this technology are probably frustrated by the lack of publicly available code. After all, Meta's TestGen-LLM study provides a glimpse into the future of what automated testing can be.</p>
<p>It's quite appealing to be able to dive into the newest technology’s internal workings, comprehend its decision-making procedures, and maybe even help shape its evolution. But while the absence of Meta's code is a hindrance, there's an open-source implementation called <code>Cover-Agent</code> which can serve as a helpful alternative.</p>
<p><code>CodiumAI's Cover-Agent</code> is the first open-source implementation of an automated testing tool based on TestGen-LLM. Inspired by Meta's research, Cover-Agent is now at the forefront of developments in open-source AI-driven unit testing as a result.</p>
<h3 id="heading-why-are-specific-testing-focused-llms-necessary">Why are specific testing-focused LLMs necessary?</h3>
<p>Since most LLMs (like ChatGPT and Gemini) are capable of generating tests, then why bother with a new technology?</p>
<p>Well, Cover-Agent and TestGen-LLM were created to be the next step in the evolution of efficient unit testing. Their aim is to avoid common pitfalls that developers run into when generating tests with LLMs such as:</p>
<ul>
<li><p>LLM Hallucination</p>
</li>
<li><p>Generating tests that do not add value</p>
</li>
<li><p>Generating tests that omit some parts of the code, resulting in low code coverage</p>
</li>
</ul>
<p>To overcome such challenges (specifically for regression unit tests) the TestGen-LLM researchers came up with the following criteria which the generated tests must meet before the test can be accepted:</p>
<ul>
<li><p>Does the generated test compile and run properly?</p>
</li>
<li><p>Does the test increase code coverage?</p>
</li>
<li><p>Does it add value?</p>
</li>
<li><p>Does it meet any additional requirements that we may have?</p>
</li>
</ul>
<p>These are fundamental questions and issues that the generated test must solve before it is deemed an upgrade on the existing technology. Cover-Agent provides tests that answer these questions to an amazingly high degree.</p>
<h2 id="heading-how-does-cover-agent-work">How Does Cover-Agent Work?</h2>
<p>Cover-Agent is part of a broader suite of utilities designed to automate the creation of unit tests for software projects. Utilizing the TestGen-LLM Generative AI model, it aims to simplify and expedite the testing process, ensuring high-quality software development.</p>
<p>The system is made up of several components:</p>
<ul>
<li><p><strong>Test Runner</strong>: Executes the command or scripts to run the test suite and generate code coverage reports.</p>
</li>
<li><p><strong>Coverage Parser:</strong> Validates that code coverage increases as tests are added, ensuring that new tests contribute to the overall test effectiveness.</p>
</li>
<li><p><strong>Prompt Builder:</strong> Gathers necessary data from the codebase and constructs the prompt to be passed to the Large Language Model (LLM).</p>
</li>
<li><p><strong>AI Caller:</strong> Interacts with the LLM to generate tests based on the prompt provided.</p>
</li>
</ul>
<p>These components work together with TestGen-LLM to generate only tests that are guaranteed to improve the existing code base.</p>
<h2 id="heading-how-to-use-cover-agent">How to Use Cover-Agent</h2>
<h3 id="heading-requirements">Requirements</h3>
<p>You need to have the following requirements before you can begin using Cover-Agent:</p>
<ul>
<li><p><code>OPENAI_API_KEY</code> set in your environment variables, which is required for calling the <code>OpenAI API</code>.</p>
</li>
<li><p>Code Coverage tool: A Cobertura XML code coverage report is required for the tool to function correctly. For example, in Python you could use <code>pytest-cov.</code> Add the <code>--cov-report=xml</code> option when running Pytest.</p>
</li>
</ul>
<h3 id="heading-installation">Installation</h3>
<p>If you're running Cover-Agent directly from the repository, you will also need:</p>
<ul>
<li><p>Python installed on your system.</p>
</li>
<li><p>Poetry installed for managing Python package dependencies. You can find installation instructions for Poetry <a target="_blank" href="https://python-poetry.org/docs/">here</a>.</p>
</li>
</ul>
<h4 id="heading-standalone-runtime">Standalone Runtime</h4>
<p>You can install Cover-Agent as a Python Pip package or run it as a standalone executable.</p>
<h4 id="heading-python-pip">Python Pip</h4>
<p>To install the Python Pip package directly via GitHub, run the following command:</p>
<pre><code class="lang-py">pip install git+https://github.com/Codium-ai/cover-agent.git
</code></pre>
<h4 id="heading-binary">Binary</h4>
<p>You can run the binary without any Python environment installed on your system (for example, within a Docker container that does not contain Python). You can download the release for your system by navigating to the project's <a target="_blank" href="https://github.com/Codium-ai/cover-agent/releases">release page</a>.</p>
<h4 id="heading-repository-setup">Repository Setup</h4>
<p>Run the following command to install all the dependencies and run the project from source:</p>
<pre><code class="lang-py">poetry install
</code></pre>
<h4 id="heading-running-the-code">Running the Code</h4>
<p>After downloading the executable or installing the Pip package, you can now run Cover-Agent to generate and validate unit tests.</p>
<p>Execute it from the command line by using the following command:</p>
<pre><code class="lang-py">cover-agent \
--source-file-path <span class="hljs-string">"path_to_source_file"</span> \
--test-file-path <span class="hljs-string">"path_to_test_file"</span> \
--code-coverage-report-path <span class="hljs-string">"path_to_coverage_report.xml"</span> \
--test-command <span class="hljs-string">"test_command_to_run"</span> \
--test-command-dir <span class="hljs-string">"directory_to_run_test_command/"</span> \
--coverage-type <span class="hljs-string">"type_of_coverage_report"</span> \
--desired-coverage <span class="hljs-string">"desired_coverage_between_0_and_100"</span> \
--max-iterations <span class="hljs-string">"max_number_of_llm_iterations"</span> \
 --included-files <span class="hljs-string">"&lt;optional_list_of_files_to_include&gt;"</span>
</code></pre>
<p>You can use the example projects within this repository to run this code as a test.</p>
<h4 id="heading-command-arguments">Command Arguments</h4>
<ul>
<li><p><strong>source-file-path:</strong> Path of the file containing the functions or block of code we want to test for.</p>
</li>
<li><p><strong>test-file-path:</strong> Path of the file where the tests will be written by the agent. It’s best to create a skeleton of this file with at least one test and the necessary import statements.</p>
</li>
<li><p><strong>code-coverage-report-path:</strong> Path where the code coverage report is saved.</p>
</li>
<li><p><strong>test-command:</strong> Command to run the tests (for example pytest).</p>
</li>
<li><p><strong>test-command-dir</strong>: Directory where the test command should run. Set this to the root or the location of your main file to avoid issues with relative imports.</p>
</li>
<li><p><strong>coverage-type:</strong> Type of coverage to use. Cobertura is a good default.</p>
</li>
<li><p><strong>desired-coverage:</strong> Coverage goal. Higher is better, though 100% is often impractical.</p>
</li>
<li><p><strong>max-iterations:</strong> Number of times the agent should retry to generate test code. More iterations may lead to higher OpenAI token usage.</p>
</li>
<li><p><strong>additional-instructions:</strong> Prompts to ensure the code is written in a specific way. For example, here we specify that the code should be formatted to work within a test class.</p>
</li>
</ul>
<p>On running the command, the agent starts writing and iterating on the tests.</p>
<h3 id="heading-how-to-use-cover-agent-1">How to Use Cover-Agent</h3>
<p>It's time test out Cover-Agent. We will use a simple calculator.py app to compare the code coverage for manual and automated testing.</p>
<h4 id="heading-manual-testing">Manual Testing</h4>
<pre><code class="lang-py"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">return</span> a + b

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">subtract</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">return</span> a - b

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">multiply</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">return</span> a * b

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">divide</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">if</span> b == <span class="hljs-number">0</span>:
        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Cannot divide by zero"</span>)
    <span class="hljs-keyword">return</span> a / b
</code></pre>
<p>This is the test_calculator.py placed in the test folder.</p>
<pre><code class="lang-py"><span class="hljs-comment"># tests/test_calculator.py</span>
<span class="hljs-keyword">from</span> calculator <span class="hljs-keyword">import</span> add, subtract, multiply, divide

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestCalculator</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_add</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">assert</span> add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">5</span>
</code></pre>
<p>To see the test coverage, we need to install <code>pytest-cov</code>, a pytest extension for coverage reporting stated earlier.</p>
<pre><code class="lang-py">pip install pytest-cov
</code></pre>
<p>Run the coverage analysis with:</p>
<pre><code class="lang-py">pytest --cov=calculator
</code></pre>
<p>The output shows:</p>
<pre><code class="lang-py">Name            Stmts   Miss  Cover
-----------------------------------
calculator.py      <span class="hljs-number">10</span>      <span class="hljs-number">5</span>    <span class="hljs-number">50</span>%
-----------------------------------
TOTAL              <span class="hljs-number">10</span>      <span class="hljs-number">5</span>    <span class="hljs-number">50</span>%
</code></pre>
<p>The output above shows that 5 of the 10 statements in calculator.py are not executed, resulting in just 50% code coverage. For a larger code base, this will become a serious issue and lead to setbacks.</p>
<p>Now let's see if cover-agent can do better.</p>
<h4 id="heading-automated-testing-with-cover-agent">Automated Testing with Cover-Agent</h4>
<p>To set up Codium's Cover-Agent, follow these steps:</p>
<p>First, install Cover-Agent:</p>
<pre><code class="lang-py">pip install git+https://github.com/Codium-ai/cover-agent.git
</code></pre>
<p>Make sure that your OPENAI_API_KEY is set in your environment variables, as it is required for the OpenAI API.</p>
<p>Next, write the commands to start generating tests in the terminal:</p>
<pre><code class="lang-py">cover-agent \
--source-file-path <span class="hljs-string">"calculator.py"</span> \
--test-file-path <span class="hljs-string">"tests/test_calculator.py"</span> \
--code-coverage-report-path <span class="hljs-string">"coverage.xml"</span> \
--test-command <span class="hljs-string">"pytest --cov=. --cov-report=xml --cov-report=term"</span> \
--test-command-dir <span class="hljs-string">"./"</span> \
--coverage-type <span class="hljs-string">"cobertura"</span> \
--desired-coverage <span class="hljs-number">80</span> \
--max-iterations <span class="hljs-number">3</span> \
--openai-model <span class="hljs-string">"gpt-4o"</span> \
--additional-instructions <span class="hljs-string">"Since I am using a test class, each line of code (including the first line) needs to be prepended with 4 whitespaces. This is extremely important to ensure that every line returned contains that 4 whitespace indent; otherwise, my code will not run."</span>
</code></pre>
<p>This generates the following code:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> pytest
<span class="hljs-keyword">from</span> calculator <span class="hljs-keyword">import</span> add, subtract, multiply, divide

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestCalculator</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_add</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">assert</span>(add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_subtract</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Test subtracting two numbers.
        """</span>
        <span class="hljs-keyword">assert</span> subtract(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">2</span>
        <span class="hljs-keyword">assert</span> subtract(<span class="hljs-number">3</span>, <span class="hljs-number">5</span>) == <span class="hljs-number">-2</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_multiply</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Test multiplying two numbers.
        """</span>
        <span class="hljs-keyword">assert</span> multiply(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">6</span>
        <span class="hljs-keyword">assert</span> multiply(<span class="hljs-number">-2</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">-6</span>
        <span class="hljs-keyword">assert</span> multiply(<span class="hljs-number">2</span>, <span class="hljs-number">-3</span>) == <span class="hljs-number">-6</span>
        <span class="hljs-keyword">assert</span> multiply(<span class="hljs-number">-2</span>, <span class="hljs-number">-3</span>) == <span class="hljs-number">6</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_divide</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Test dividing two numbers.
        """</span>
        <span class="hljs-keyword">assert</span> divide(<span class="hljs-number">6</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">2</span>
        <span class="hljs-keyword">assert</span> divide(<span class="hljs-number">-6</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">-2</span>
        <span class="hljs-keyword">assert</span> divide(<span class="hljs-number">6</span>, <span class="hljs-number">-3</span>) == <span class="hljs-number">-2</span>
        <span class="hljs-keyword">assert</span> divide(<span class="hljs-number">-6</span>, <span class="hljs-number">-3</span>) == <span class="hljs-number">2</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_divide_by_zero</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Test dividing by zero, should raise ValueError.
        """</span>
        <span class="hljs-keyword">with</span> pytest.raises(ValueError, match=<span class="hljs-string">"Cannot divide by zero"</span>):
            divide(<span class="hljs-number">5</span>, <span class="hljs-number">0</span>)
</code></pre>
<p>You can see that the agent also wrote tests that check for errors for any edge cases.</p>
<p>Now it's time to test the coverage again:</p>
<pre><code class="lang-py">pytest --cov=calculator
</code></pre>
<p>Output:</p>
<pre><code class="lang-py">Name            Stmts   Miss  Cover
-----------------------------------
calculator.py      <span class="hljs-number">10</span>      <span class="hljs-number">0</span>   <span class="hljs-number">100</span>%
-----------------------------------
TOTAL              <span class="hljs-number">10</span>      <span class="hljs-number">0</span>   <span class="hljs-number">100</span>%
</code></pre>
<p>In this example we reached 100% code coverage. For larger code bases, the procedure is relatively the same. You can read through <a target="_blank" href="https://dev.to/oluwadamisisamuel1/how-to-automate-test-generation-with-ai-using-codiumai-cover-agent-1kep">this guide</a> for a walkthrough on a larger code base.</p>
<p>While Cover-Agent represents a significant step forward, it's important to note that this technology is still in its early stages. Continued research and development are crucial for further refinement and broader adoption and codiumAI invites you to make your contributions to this open source tool.</p>
<h2 id="heading-benefits-of-the-open-source-cover-agent">Benefits of the Open Source Cover-Agent</h2>
<p>The open source nature of Cover-Agent offers several advantages that should help propel the technology forward. Among them are:</p>
<ul>
<li><p><strong>Accessibility:</strong> Its open source nature enables LLM-based testing experimentation and it is accessible to developers with varying backgroundsThis will increase the number of users and lead to the development of a better technology and more applications..</p>
</li>
<li><p><strong>Cooperation:</strong> Developers are able to make contributions, suggest enhancements, propose new features and report issues. Cover-Agent will grow and develop quickly into a project perfect for developers..</p>
</li>
<li><p><strong>Transparency:</strong> Information about the internal operations are available and this promotes trust and will ultimately increase the potential for the technology.</p>
</li>
</ul>
<p>In addition to it’s open-source advantages, Cover-Agent provides developers with a strong set of benefits of its own:</p>
<ul>
<li><p><strong>Simple Access:</strong> Developers can easily install and experiment with LLM-based testing. This allows for firsthand and immediate exploration of the technology's capabilities and with little to no disruption in their workflow.</p>
</li>
<li><p><strong>Customization for Specific Needs:</strong> Cover-Agent's open-source nature allows developers to adapt the tool to their specific project requirements. This could involve modifying the LLM model used, adjusting training data to better reflect their codebase, or integrating Cover-Agent with existing testing frameworks. This level of customization empowers developers to leverage the power of LLM-based testing in a way that aligns with their project needs.</p>
</li>
<li><p><strong>Easy Integration:</strong> It is easily integrated with VSCode (a popular code editor) which makes integration with existing workflows a breeze. You can also easily integrate it with existing human-written tests.</p>
</li>
</ul>
<h2 id="heading-how-can-you-contribute-to-cover-agent">How Can You Contribute to Cover-Agent?</h2>
<p>Cover-Agent source code is publicly available through <a target="_blank" href="https://github.com/Codium-ai/cover-agent">this GitHub repo</a>. They encourage developers of all backgrounds to test their product and make contributions to further improve and grow this new technology.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>LLM-based test improvement tools hold immense potential for revolutionizing the way developers approach unit testing. By leveraging the power of large language models specifically trained on code, these tools can streamline test creation, improve code coverage, and ultimately enhance software quality.</p>
<p>While Meta's research with TestGen-LLM offers valuable insights, the lack of publicly available code hinders wider adoption and ongoing development. Fortunately, Cover-Agent has provided a readily accessible and customizable solution. It empowers developers to experiment with LLM-based testing and contribute to its evolution.</p>
<p>The potential for the TestGen-LLM and Cover-Agent is immense, and further development through contributions by developers will lead to a revolutionary tool that will transform automated test generation forever.</p>
<ul>
<li><p><a target="_blank" href="https://arxiv.org/abs/2402.09171">Meta’s Research Paper</a></p>
</li>
<li><p><a target="_blank" href="https://www.codium.ai/blog/we-created-the-first-open-source-implementation-of-metas-testgen-llm/">Cover-Agent Blog for further study</a></p>
</li>
<li><p><a target="_blank" href="https://dev.to/oluwadamisisamuel1/how-to-automate-test-generation-with-ai-using-codiumai-cover-agent-1kep">Article for installation process and use cases</a></p>
</li>
</ul>
<p>Connect with me on <a target="_blank" href="http://www.linkedin.com/in/samuel-oluwadamisi-01b3a4236">LinkedIn</a> and <a target="_blank" href="https://twitter.com/Data_Steve_">Twitter</a> if you found this helpful.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Cypress for End-to-End Testing Your React Apps ]]>
                </title>
                <description>
                    <![CDATA[ React is a popular framework for building web applications. It's is one of the best options for frontend engineering because of its declarative approach to user interface design and component-based architecture. But it can be difficult to make sure y... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/cypress-for-end-to-end-testing-react-apps/</link>
                <guid isPermaLink="false">66d4608affe6b1f641b5fa4f</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Okosa Leonard ]]>
                </dc:creator>
                <pubDate>Wed, 01 Nov 2023 22:45:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/1_PfsGOHgjLPh3EFOkxCRmjw.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>React is a popular framework for building web applications. It's is one of the best options for frontend engineering because of its declarative approach to user interface design and component-based architecture.</p>
<p>But it can be difficult to make sure your React application functions as intended in various different scenarios.</p>
<p>Cypress is an end-to-end testing framework that you can use to test your React apps. And in this tutorial, I'll explain how to create efficient end-to-end tests for your web application using Cypress and React.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-cypress">What is Cypress?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-your-environment">How to Set Up Your Environment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-write-your-first-cypress-test">How to Write Your First Cypress Test</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-interact-with-react-components">How to Interact with React Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cypress-queries">Cypress Queries</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cypress-assertions">Cypress Assertions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cypress-actions">Cypress Actions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-organize-and-run-your-cypress-tests">How to Organize and Run Your Cypress Tests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-plugins">Plugins</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-describe-blocks">Describe Blocks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-custom-commands">Custom Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cypress-dashboard">Cypress Dashboard</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-debug-your-code-with-cypress">How to Debug Your Code with Cypress</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-continuous-integration">Continuous Integration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-is-cypress">What is Cypress?</h2>
<p>Cypress is a straightforward open-source end-to-end testing framework designed for modern web development. It's based on JavaScript.</p>
<p>The tool operates within the browser, which sets it apart from other testing tools like Selenium. The concise, uncomplicated API that Cypress provides for interacting with your application makes it easy to create and manage tests.</p>
<p>You can create and manage tests that mimic user interactions with Cypress. You can also examine your web application for expected behavior and troubleshoot any concerns that arise.</p>
<h2 id="heading-how-to-set-up-your-environment">How to Set Up Your Environment</h2>
<p>Before we begin writing tests, you'll want to set up your environment. You must have Node.js and npm (Node Package Manager) installed in order to use Cypress with your React application.</p>
<p>You can visit <a target="_blank" href="https://nodejs.org/en/">https://nodejs.org/en/</a> to download to install Node.js on your computer if it isn't already installed. To get started using Cyprus after installing Node, you can follow these steps:</p>
<h3 id="heading-1-create-a-react-application">1) Create a React Application</h3>
<p>We'll utilize Vite to develop our React application, as it helps us quickly and efficiently create web apps.</p>
<p>To get started, create a React app on Vite by entering the following code in your console:</p>
<pre><code class="lang-js">npm create vite@latest
</code></pre>
<p>Vite will ask you to give your project a name. Choose whatever name you like.</p>
<p>Following that, Vite will give you the choice of framework. Just make sure you select React for this lesson.</p>
<p>It'll then ask you to select between TypeScript and JavaScript when you select React. Pick JavaScript.</p>
<p>Then go to your project directory and choose your project.</p>
<p>Finally, type <code>npm install</code> after that, and then <code>npm start dev</code> to launch your project.</p>
<p>Your project will be hosted by Vite at <code>http://localhost:5173/</code>. To view your project in a browser, click the link that's provided.</p>
<h3 id="heading-2-install-cypress">2) Install cypress</h3>
<p>You can install Cypress in your project by using this code <code>install cypress –-save-dev</code> in your terminal. This code installs Cypress in your React application.</p>
<h3 id="heading-3-initialize-cypress">3) Initialize Cypress</h3>
<p>Run <code>npx Cypress open</code> from the directory of your React project. This command should launch Cypress Test Runner, a graphical tool for creating and managing tests. You will now have two alternatives for automated testing:</p>
<ul>
<li><p>End-to-End Testing: End-to-end testing involves a browser using an automated tool like Cypress, so you can make tests consisting of functions like pressing buttons, typing in inputs, and so on. This helps make sure everything’s working perfectly.</p>
</li>
<li><p>Component Testing: This involves testing individual components instead of the entire React application.</p>
</li>
</ul>
<h4 id="heading-what-is-the-cypress-test-runner">What is the Cypress Test Runner?</h4>
<p>The test runner opens up once you pass in <code>npx cypress open</code>. It is a graphical tool for creating and managing tests, it also allows you to write and execute your test cases in Cypress.</p>
<p>After selecting what type of testing you want to do, the next section of the test runner helps you specify what script you want to execute, and gives you the option to select the browser you want to use and validate your tests as expected.</p>
<h3 id="heading-4-folder-structure">4) Folder Structure</h3>
<p>Cypress will automatically save its test files and configurations inside of your project in a subfolder named <code>cypress</code>.</p>
<h2 id="heading-how-to-write-your-first-cypress-test">How to Write Your First Cypress Test</h2>
<p>We'll use JavaScript to write our Cypress tests which are executed within the browser. Here's how to create your React application's first test.</p>
<p>Suppose we wish to check to see if the Document Object Model (DOM) displays our h2 element. To accomplish this using Cypress, take the following actions:</p>
<p><strong>Step 1</strong>: In your Cypress folder, go to the e2e file and there you’ll see a line of code similar to this:</p>
<pre><code class="lang-javascript">describe(<span class="hljs-string">'template spec'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'passes'</span>, <span class="hljs-function">() =&gt;</span> {
        cy.visit(<span class="hljs-string">'https://example.cypress.io'</span>)
    })
    })
</code></pre>
<p><strong>Step 2</strong>: Now, in <code>cy.visit</code>, update the default http address to your React app's address (<a target="_blank" href="http://localhost:5173/">http://localhost:5173/</a>, if you recall).</p>
<p><strong>Step 3</strong>: We wish to check to see if the DOM has our h2 element. Navigate to the element you wish to test and add <code>data-testid</code> as an attribute to accomplish this. Next, assign a name to the attribute. Example: <code>&lt;h2 data-testid="cypress-h1"&gt; Leo Website &lt;/h2&gt;</code>.</p>
<p><strong>Step 4</strong>: Make a space and add this piece of code to your cypress folder to see if the attribute exists.</p>
<pre><code class="lang-js">cy.get(‘[data-testid=”cypress-header”]’).should(“exists”):
</code></pre>
<p>Since Cypress is configured to execute tests upon save, as soon as you save anything in your code editor, the test ought to instantly start in Cypress.</p>
<h2 id="heading-how-to-interact-with-react-components">How to Interact With React Components</h2>
<p>You'll want to be familiar with the vast array of commands used to imitate user interactions in order to write more insightful tests.</p>
<p>With its extensive command set for interacting with and testing online elements, Cypress is designed specifically for webpages and web applications.</p>
<p>To imitate user interactions and assert certain behaviors of the application, test scripts employ these instructions. Because Cypress maintains a promise chain on your behalf, you can chain commands together. Until the chain breaks and an error occurs, each command yields a "subject" to the following command.</p>
<p>Cypress commands can be classified into the following groups: queries, assertions, actions, and others.</p>
<h3 id="heading-cypress-queries">Cypress Queries</h3>
<p>These are Cypress commands that retrieve your application's current status. They retry as necessary to ensure that the DOM element or other data they offer is constantly current. They return a subject for additional commands to act upon or assert.</p>
<p>Here are some ways you can organize and run tests:</p>
<h4 id="heading-using-the-children-method">Using the <code>.children()</code> method</h4>
<p>To choose and work with a parent element's direct child elements in Cypress, use the <code>.children()</code> method. It enables you to access particular child elements of a given parent element by navigating the DOM tree.</p>
<p>Here are some examples of how to use <code>.children()</code> in Cypress:</p>
<p>Example 1: Assuming you have this sort HTML structure in your <code>jsx</code> file:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"parent-element"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"child"</span>&gt;</span>Child 1<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"child"</span>&gt;</span>Child 2<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"child"</span>&gt;</span>Child 3<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>
</code></pre>
<p>To choose and work with the child elements inside the <code>.parent</code> element, use the <code>.children()</code> function like this:</p>
<pre><code class="lang-javascript">cy.get(<span class="hljs-string">'.parent'</span>).children(<span class="hljs-string">'.child'</span>).should(<span class="hljs-string">'have.length'</span>, <span class="hljs-number">3</span>);

<span class="hljs-comment">// Click on the second child element</span>
cy.get(<span class="hljs-string">'.parent'</span>).children(<span class="hljs-string">'.child'</span>).eq(<span class="hljs-number">1</span>).click();
</code></pre>
<p>Example 2: you can perform a whole range of tasks using <code>.children()</code> combined with other Cypress functions. You can, for instance, recognize certain child objects and define their properties:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Ensure that the first child element contains the text "Child 1"</span>
cy.get(<span class="hljs-string">'.parent'</span>).children(<span class="hljs-string">'.child'</span>).first().should(<span class="hljs-string">'contain'</span>, <span class="hljs-string">'Child 1'</span>);

<span class="hljs-comment">// Verify that all child elements have a specific class</span>
cy.get(<span class="hljs-string">'.parent'</span>).children(<span class="hljs-string">'.child'</span>).each(<span class="hljs-function">(<span class="hljs-params">$child</span>) =&gt;</span> {
  cy.wrap($child).should(<span class="hljs-string">'have.class'</span>, <span class="hljs-string">'child'</span>);
});
</code></pre>
<p>Example 3: Many Cypress instructions may be chained together to perform more complex interactions. Using <code>.children()</code>, you can select the parent element, select its child elements, and then operate with the child elements as demonstrated by this example:</p>
<pre><code class="lang-javascript">cy.get(<span class="hljs-string">'.parent'</span>)
  .children(<span class="hljs-string">'.child'</span>)
  .first()
  .click()
  .next()
  .type(<span class="hljs-string">'I'</span>m Leo<span class="hljs-string">');</span>
</code></pre>
<p>To interact with the next child element in this example, we first pick the <code>.parent</code> element, then use <code>.children('.child')</code> to select its child elements, click on the first child element, and then chain more instructions to type "I'm Leo."</p>
<p>These examples show you how to pick and work with child items inside of a parent element in Cypress using the <code>.children()</code> function. Depending on your unique HTML structure, you can modify the CSS selector inside <code>.children()</code> to match the child elements you wish to target.</p>
<p>Now you should have an have an idea of how <code>.children()</code> works and how you can use queries as a type of Cypress command. There are more query commands but you can always refer to the documentation on <a target="_blank" href="https://www.freecodecamp.org/news/p/ee697448-b73d-43dd-85de-d31f1e1005c6/cypress.io">cypress.io</a> to learn more on them.</p>
<h4 id="heading-using-the-cyget-command">Using the cy.get() command</h4>
<p><code>cy.get()</code> is a fundamental command in the Cypress framework. You use it to choose and perform actions on DOM elements inside a web application, including those created using React.</p>
<p>To choose one or more DOM elements from your React application for testing, use the <code>cy.get()</code> command. It lets you pick elements with CSS selectors or other Cypress-compatible methods.</p>
<p>The fundamental syntax of <code>cy.get()</code> is <code>cy.get(selector)</code>.</p>
<p><code>selector</code> is an instance of a CSS selector. There are alternative ways for choosing components based on attributes like ID, class name, or data.</p>
<p>Suppose you have a React component like this:</p>
<pre><code class="lang-javascript">&lt;button data-testid=<span class="hljs-string">"Leo-button"</span>&gt;Click&lt;/button&gt;
</code></pre>
<p>You can use <code>cy.get()</code> to select the button element:</p>
<pre><code class="lang-javascript">cy.get(‘[data-testid=”Leo-button”]’);
</code></pre>
<p><code>cy.get('#Leo-button')</code> in this case chooses the button element with ID "Leo-button."</p>
<h3 id="heading-cypress-assertions">Cypress Assertions</h3>
<p>These are Cypress commands that make assertions about the state of your application. They stop your test when they reach the time limit or the specified condition is fulfilled.</p>
<p>There are only two types of assertion commands: <code>.and()</code> and <code>.should()</code>. I'll discuss <code>.should()</code> here.</p>
<p>In Cypress, statements concerning the states of particular elements are made using the <code>.should()</code> command. It enables you to specify requirements that components must fulfill. In the event that the requirements are not satisfied, the test will fail.</p>
<h4 id="heading-fundamental-use-of-should">Fundamental Use of <code>.should()</code></h4>
<p>The basic syntax of <code>.should()</code> is as follows:</p>
<pre><code class="lang-javascript">cy.get(<span class="hljs-string">'selector'</span>).should(<span class="hljs-string">'condition'</span>, expectedValue);
</code></pre>
<ul>
<li><p>The DOM element(s) that you wish to make assertions against are chosen by <code>cy.get('selector')</code>.</p>
</li>
<li><p><code>.should('condition', expectedValue)</code> indicates what conditions and values the chosen element(s) must satisfy.</p>
</li>
</ul>
<p>You can also chain <code>.should()</code> assertions. Chained <code>.should()</code> statements are used to verify different conditions on the same element:</p>
<pre><code class="lang-javascript">cy.get(<span class="hljs-string">'input#username'</span>)
  .should(<span class="hljs-string">'be.visible'</span>)
  .should(<span class="hljs-string">'have.attr'</span>, <span class="hljs-string">'placeholder'</span>, <span class="hljs-string">'Enter your username'</span>)
</code></pre>
<p>In this example, we verify that the username input element is there and that the placeholder content is appropriate.</p>
<p><code>.should()</code> also allows you to use custom assertions by passing a callback function as the expected value:</p>
<pre><code class="lang-javascript">cy.get(<span class="hljs-string">'.my-element'</span>).should(<span class="hljs-function">(<span class="hljs-params">$element</span>) =&gt;</span> {
  expect($element).to.have.class(<span class="hljs-string">'active'</span>);
  expect($element).to.have.css(<span class="hljs-string">'color'</span>, <span class="hljs-string">'rgb(255, 0, 0)'</span>);
});
</code></pre>
<p>In this code, we're using custom assertions within <code>.should()</code> to check if the element has the class "active" and if its text color is red.</p>
<h4 id="heading-negating-assertions-with-should">Negating assertions with .should()</h4>
<p><code>.should('not.condition', expectedValue)</code> can be used to refute the following claims:</p>
<pre><code class="lang-javascript">cy.get(<span class="hljs-string">'#error-message'</span>).should(<span class="hljs-string">'not.exist'</span>);
</code></pre>
<p>This code verifies that there isn't an element with the ID "error-message".</p>
<h4 id="heading-combining-should-with-other-commands">Combining .should() with Other Commands</h4>
<p>To create testing scenarios with greater complexity, you can combine <code>.should()</code> with other Cypress commands. For instance, you can use it to assert the element's final state following a <code>.click()</code> or <code>.type()</code> command.</p>
<pre><code class="lang-javascript">cy.get(<span class="hljs-string">'button'</span>).click().should(<span class="hljs-string">'be.disabled'</span>);
cy.get(‘[data-testid=”search-input”]’).type(<span class="hljs-string">'Leonard'</span>).should(<span class="hljs-string">'have.value'</span>, <span class="hljs-string">'Leonard'</span>);
</code></pre>
<p>In these instances, we interact with the elements first (by clicking a button or entering text into an input box), and then we use the resultant states of the elements' states as determined by using <code>.should()</code>.</p>
<p>When constructing relevant assertions to confirm the behavior and state of items in your web application during end-to-end testing, Cypress's <code>.should()</code> command is a potent tool.</p>
<h3 id="heading-cypress-actions">Cypress Actions</h3>
<p>Cypress commands that interact with your program like a user would are called actions. They do not interact with the page until components or elements are actionable. There are numerous examples, but for now I'll use the <code>.click()</code> command.</p>
<p>To mimic a click on a DOM element in Cypress, use the <code>.click()</code> command. It's among the most typical and basic things you'll do in your end-to-end tests.</p>
<p>Let's look at an example of some code to demonstrate how to use <code>.click()</code>:</p>
<h4 id="heading-basic-click-usage">Basic <code>.Click()</code> Usage</h4>
<p>The following is the syntax of <code>.click()</code>:</p>
<pre><code class="lang-javascript">cy.get(<span class="hljs-string">'selector'</span>).click();
</code></pre>
<p>To choose the DOM element you want to click, use <code>cy.get('selector')</code>. <code>.click()</code> replicates a click event on the element that is chosen.</p>
<h4 id="heading-managing-clicks-during-user-interaction">Managing Clicks During User Interaction</h4>
<p>Sometimes, you may need to interact with things before clicking. For instance, you may want to complete a form field and press the submit button afterward. You can use <code>.type()</code> to communicate with input fields before continuing on to the submit button, <code>.click()</code>:</p>
<pre><code class="lang-javascript">cy.get(‘[data-testid=”username”]’).type(<span class="hljs-string">'okosaleo'</span>);
cy.get(‘[data-testid=”password”]’).type(<span class="hljs-string">'password'</span>);
cy.get(‘[data-testid=”Leo-button”]’).click();
</code></pre>
<p>First, in this case we employ <code>.type()</code> to input values in the password and username fields, and then click <code>.click()</code> to click the login button and submit the form.</p>
<h4 id="heading-managing-dynamic-elements">Managing Dynamic Elements</h4>
<p>You can utilize <code>.click()</code> while working with dynamic elements, or elements that emerge following an action. In order to make sure the element is there and prepared for interaction, you can use <code>.click()</code> in conjunction with other commands.</p>
<p>For instance, clicking a dynamic element. Assume you have a list of items and that you wish to click on a certain item that pops up following a certain amount of user interaction:</p>
<pre><code class="lang-javascript">cy.get(‘[data-testid=”Leo-button”]’).click();
cy.get(<span class="hljs-string">'.dynamic-item'</span>).should(<span class="hljs-string">'have.length.gt'</span>, <span class="hljs-number">5</span>);
cy.get(<span class="hljs-string">'.dynamic-item'</span>).eq(<span class="hljs-number">4</span>).click();
</code></pre>
<p>In this example, to load new things dynamically, we first click the <code>"Leo-button"</code> button. After making sure that at least six things are shown using <code>.should('have.length.gt', 5)</code>, we click the fifth item using <code>.eq(4)</code>.</p>
<h3 id="heading-other-commands">Other commands</h3>
<p>Cypress has many other commands which we haven't discussed here but that you can use to write other tests. You can check them out in the <a target="_blank" href="https://docs.cypress.io/api/table-of-contents">Cypress docs here</a>.</p>
<h2 id="heading-how-to-organize-and-run-your-cypress-tests">How to Organize and Run Your Cypress Tests</h2>
<p>Your application may accumulate a number of tests over time. Cypress offers several methods for setting them up and managing them effectively.</p>
<h3 id="heading-plugins">Plugins</h3>
<p>To assist with tasks such as creating code coverage reports and simulating APIs, Cypress offers a number of extensions and plugins.</p>
<p>Plugins are specially designed JavaScript modules that let you modify and add new features to the Cypress testing framework. With the use of plugins, you can increase Cypress's capabilities and customize it to suit your unique requirements.</p>
<p>Cypress plugins include:</p>
<ol>
<li><p>Integrations: Cypress may be integrated with other programs, services, and systems by the creation of plugins. Examples of these include deployment pipelines, version control systems, and platforms for continuous integration (CI).</p>
</li>
<li><p>Custom Commands: To encapsulate and reuse routine test procedures or user interactions with your application, you may define custom commands using plugins. This improves the organization and maintainability of your test code.</p>
</li>
</ol>
<p>There are many more plugin features that Cypress offers. Plugins are a flexible way to customize your testing framework to the specific needs of your project, increase the power of your testing, and organize and maintain your test code better.</p>
<h3 id="heading-describe-blocks">Describe Blocks</h3>
<p>The Mocha testing framework, which is integrated with Cypress to help you organize and structure your test suites, includes describe blocks. These describe blocks provide your tests a clear structure and help you group related test cases.</p>
<p>You can use describe blocks to categorize and explain the functionality and behavior of the program you are evaluating.</p>
<h4 id="heading-purpose-of-describe-blocks-in-cypress">Purpose of Describe Blocks in Cypress</h4>
<ul>
<li><p>Organize Test Cases: You can organize relevant test cases together by using the describe blocks. A description block can be made for a particular application feature, part, or functionality.</p>
</li>
<li><p>Enhance Test framework: They give your tests a hierarchical framework that makes the test suite easier to explore and comprehend. Developers and testers can find certain tests more quickly with the aid of this structure.</p>
</li>
<li><p>Clarity and Documentation: You can give titles for your tests that are both human-readable and descriptive by using describe blocks. This functions as a type of test documentation.</p>
</li>
</ul>
<p>A <code>describe</code> block is structured like this:</p>
<pre><code class="lang-javascript">describe(<span class="hljs-string">'Description of the Test Suite or Feature'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Your test cases (it blocks) go here</span>
});
</code></pre>
<ul>
<li><p><code>"Description of the Test Suite or Feature"</code>: This is a string that contains a description of the test suite or feature. It acts as the group of test cases title.</p>
</li>
<li><p><code>function () { /*... */ }</code>: This function holds the various test cases that are related to this <code>describe</code> block and are written within <code>it</code> blocks.</p>
</li>
</ul>
<h4 id="heading-how-to-use-describe-blocks-custom-commands">How to Use Describe Blocks Custom Commands</h4>
<p>Let's say you are evaluating a web application's login functionality. The following is how you could structure your tests using describe blocks:</p>
<pre><code class="lang-javascript">describe(<span class="hljs-string">'Login Feature'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// This is the outermost describe block for the Login Feature</span>

  it(<span class="hljs-string">'should display the login form'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Test case logic to check if the login form is displayed</span>
  });

  it(<span class="hljs-string">'should show an error message on invalid login'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Test case logic to check error message display</span>
  });

  it(<span class="hljs-string">'should log in successfully with valid credentials'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Test case logic to check successful login</span>
  });

  describe(<span class="hljs-string">'Password Reset'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Nested describe block for Password Reset functionality</span>

    it(<span class="hljs-string">'should allow users to request a password reset'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Test case logic for password reset request</span>
    });

    it(<span class="hljs-string">'should reset the password with a valid reset token'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Test case logic for password reset with a valid token</span>
    });
  });
});
</code></pre>
<p>In this instance:</p>
<ol>
<li><p>All of the test cases for the login feature are grouped together in the outermost describe block.</p>
</li>
<li><p>Tests pertaining to the password reset feature are grouped together in a describe block nestled within it.</p>
</li>
</ol>
<p>You can easily identify related tests thanks to this hierarchical structure, which also aids in clarifying the goals of each set of test cases.</p>
<p>Effective use of <code>describe</code> blocks in Cypress allows you to write well-structured and easily accessible test suites, which will facilitate test management and maintenance as your project expands.</p>
<h3 id="heading-custom-commands">Custom Commands</h3>
<p>To simplify repetitive tasks and improve the readability of your tests, you can create custom Cypress commands.</p>
<p>To encapsulate and reuse common test processes or interactions with the application being tested, you may develop and add custom commands—user-defined JavaScript functions—to your Cypress test suite. These can help augment the built-in Cypress commands and improve the readability, organization, and maintainability of your test code.</p>
<p>You need to define your custom command in one of your test files, usually the <code>commands.js</code> file. Often, you may find this file in the <code>cypress/support</code> directory.</p>
<p>On the other hand, you can arrange your customized commands in different files or folders according to the features of your program.</p>
<p>Here's an example:</p>
<pre><code class="lang-javascript">Cypress.Commands.add(<span class="hljs-string">'customCommandName'</span>, <span class="hljs-function">(<span class="hljs-params">arg0, arg1, ...</span>) =&gt;</span> {
  <span class="hljs-comment">// Define the custom command logic here</span>
});
</code></pre>
<ul>
<li><p><code>customCommandName</code>: This is the name that you'll use in your test scripts to identify your custom command.</p>
</li>
<li><p><code>(arg0, arg1,...)</code>: These are the parameters or arguments that you can give to the custom command in order to alter how the program behaves.</p>
</li>
<li><p><code>// Custom command logic }</code>: Here you provide the precise operations you wish to be carried out by your custom command.</p>
</li>
</ul>
<h4 id="heading-how-to-use-custom-commands">How to Use Custom Commands</h4>
<p>Once your custom command is defined, you can use it in your test scripts in the same way as any other Cypress command that is built-in.</p>
<p>Using the following syntax, you can invoke a custom command:</p>
<pre><code class="lang-javascript">cy.customCommandName(arg0, arg1, ...);
</code></pre>
<h4 id="heading-advantages-of-custom-commands">Advantages of Custom Commands</h4>
<p>Cypress custom commands have a number of advantages:</p>
<ol>
<li><p>Reusability: You can encapsulate intricate or commonly used interactions with your application using custom commands. This lessens redundancy in your test scripts and encourages the reuse of code.</p>
</li>
<li><p>Readability: Your test scripts become more accessible and understandable when typical tasks are abstracted into custom commands. Your test code will be more self-explanatory with custom commands.</p>
</li>
<li><p>Maintenance: Rather than having to look for and update each instance of an action in several test scripts, you just need to change the custom command once when you need to update a common action or interaction.</p>
</li>
<li><p>Consistency: Using custom commands guarantees that your test suite's common interactions are carried out in a consistent manner. This keeps the testing process consistent.</p>
</li>
</ol>
<p>This is a basic illustration of a custom command that allows a user to log in by filling out a login form with their username and password:</p>
<pre><code class="lang-javascript">Cypress.Commands.add(<span class="hljs-string">'login'</span>, <span class="hljs-function">(<span class="hljs-params">username, password</span>) =&gt;</span> {
  cy.get(‘[username-input”]’).type(username);
  cy.get(‘[data-testid=”password-input”]’).type(password);
  cy.get(‘[data-testid=”Leo-button”]’).click();
});
</code></pre>
<h3 id="heading-cypress-dashboard">Cypress Dashboard</h3>
<p>This tool lets you monitor films of text executors, organize test runs, and discuss outcomes with your team. You can find the Cypress dashboard in the test runner window.</p>
<p>To see the history of your test runs, click the "Runs" tab. The Runs tab will include a "View Dashboard" link at the top. When you click the "View Dashboard" link, your web browser will launch the Cypress Dashboard.</p>
<p>Cypress offers the dashboard as a web-based service. The goal of the Cypress Dashboard is to improve and streamline the web application testing and test management process. It provides a single platform to monitor, analyze, and manage your test runs using Cypress.</p>
<p>The Cypress Dashboard's main attributes and capabilities are as follows:</p>
<ol>
<li><p>Real-Time Test Execution: You can watch the test execution process in real time when you use the Cypress Dashboard service to execute Cypress tests. It offers a real-time view of your testing, including with videos and live browser screenshots. This tool is useful for tracking test progress and troubleshooting problems.</p>
</li>
<li><p>Collaboration and Sharing: The development and QA teams can work together thanks to the dashboard. Test results may be accessed and reviewed by developers, testers, and other stakeholders with ease when test runs are shared with team members.</p>
</li>
<li><p>Centralized Test Management: You can store and manage your Cypress test results centrally using the Cypress Dashboard. Test runs can be accessed and arranged from a single location.</p>
</li>
<li><p>The Cypress Dashboard facilitates the concurrent running of tests across many computers and web browsers. This can speed up the test execution process considerably and provide you the results you need more quickly.</p>
</li>
<li><p>Test Insights and Analytics: By tracking test results over time, you can learn more about your test runs. Comprehensive analytics, including execution times, pass/fail statuses, and more, are available in the Cypress Dashboard. You can use this data to find trends and patterns in your test results.</p>
</li>
<li><p>Test Automation and Scheduling: You can automate the testing process by scheduling tests to run at predetermined times or intervals. Running regression tests and maintaining ongoing test coverage can both benefit from this.</p>
</li>
</ol>
<p>These are some of the functions and attributes the Cypress dashboard offers.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/dashboard-analytics-overview.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Here's an example of what the cypress dashboard looks like</em></p>
<h2 id="heading-how-to-debug-your-code-with-cypress">How to Debug Your Code With Cypress</h2>
<p>It's simple to debug your tests with Cypress. To halt test execution and view an application's status in the browser, add <code>cy.debug()</code>. Let's look at an example of how to use <code>cy.debug</code> in Cypress:</p>
<pre><code class="lang-javascript">describe(<span class="hljs-string">'Cypress Debug Example'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'performs a login and checks the user profile'</span>, <span class="hljs-function">() =&gt;</span> {
    cy.visit(<span class="hljs-string">'/login'</span>);
    cy.get(<span class="hljs-string">'#username-input'</span>).type(<span class="hljs-string">'testuser'</span>);

    <span class="hljs-comment">// Pause the test execution to inspect the application state</span>
    cy.pause();

    cy.get(<span class="hljs-string">'#password-input'</span>).type(<span class="hljs-string">'password123'</span>);
    cy.get(<span class="hljs-string">'#login-button'</span>).click();

    <span class="hljs-comment">// Continue with assertions</span>
    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/profile'</span>);
    cy.get(<span class="hljs-string">'.user-info'</span>).should(<span class="hljs-string">'contain'</span>, <span class="hljs-string">'Welcome, testuser'</span>);
  });
});
</code></pre>
<p>In this example, the test will halt when it reaches the <code>cy.pause()</code> command, and the Cypress Test Runner will prompt you to view the DevTools console.</p>
<p>After that, you may interact with the program, examine the DOM, verify variables, and execute more Cypress commands interactively for debugging reasons via the console.</p>
<h4 id="heading-time-travel-debugging">Time Travel Debugging</h4>
<p>Cypress also has a powerful capability known as "Time Travel Debugging." This feature allows you to see the current state of your application at any time while it is being tested. This greatly simplifies the diagnosis process.</p>
<p>Using this feature, you can record and replay test execution stages to troubleshoot your Cypress tests. By giving you a visual depiction of the test execution process step-by-step, it assists you in identifying and resolving problems with your tests and application. Using Test Driven Development (TTD), you may see your application's state interactively at various stages of the test run.</p>
<p>This is how Cypress's Time Travel Debugging works:</p>
<p>You must first record a test run in order to use TTD. You may do this using the Cypress Dashboard service or by running your tests locally with the <code>--record</code> parameter.</p>
<p>This is an example of how to conduct testing while recording:</p>
<pre><code class="lang-bash">npx cypress run --record
</code></pre>
<p>The test is launched in the Electron browser by default.</p>
<p>Then you can use the Cypress Dashboard web interface once the test run is finished and the results are submitted to the Cypress Dashboard.</p>
<p>Next, find the test run you wish to debug in the Cypress Dashboard and click on it. This will direct you to the page with test specifics.</p>
<p>You may see a timeline representation of your application's state at each Cypress command execution on the test details page.</p>
<p>To see the state of the application at a certain moment in time, click on a snapshot on the timeline. You may browse console logs, work with the DOM, and even run Cypress commands in the context of that snapshot.</p>
<p>To examine the condition of the application at various test phases, you can either go to particular points in the timeline or walk through each command in the timeline one at a time.</p>
<p>This is an illustration of how to apply TTD in a Cypress test:</p>
<pre><code class="lang-javascript">describe(<span class="hljs-string">'Time Travel Debugging Example'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'performs a login and checks the user profile'</span>, <span class="hljs-function">() =&gt;</span> {
    cy.visit(<span class="hljs-string">'/login'</span>);
    cy.get(‘[username-input”]’).type(<span class="hljs-string">'testuser'</span>);
    cy.get(‘[password-input”]’).type(<span class="hljs-string">'password'</span>);
    cy.get(‘[login-input”]’).click();

    <span class="hljs-comment">// At this point, we may want to perform TTD to inspect the application's state</span>
    <span class="hljs-comment">// and ensure the user is returned to the profile page.</span>

    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/profile'</span>);
    cy.get(<span class="hljs-string">'.user-info'</span>).should(<span class="hljs-string">'contain'</span>, <span class="hljs-string">'Welcome, testuser'</span>);
  });
});
</code></pre>
<p>In this scenario, you may run into a problem that you wish to diagnose after entering your login information and hitting the login button.</p>
<p>To make sure the user is on the profile page and that their name is shown appropriately, you may use TTD to halt at the <code>cy.url()</code> assertion and verify the status of the application.</p>
<p>With TTD, you can examine the status of the application at different times to interactively debug and troubleshoot your Cypress tests, which will help you find and address problems more efficiently.</p>
<h2 id="heading-continuous-integration">Continuous Integration</h2>
<p>Cypress tests can be integrated into your continuous integration (CI) workflow using solutions like as Travis CI, GitHub actions, Jenkins, or CircleCI. Let's talk about continuous integration using Github actions:</p>
<h3 id="heading-cypress-continuous-integration-using-github-actions">Cypress Continuous Integration Using GitHub Actions</h3>
<p>You can automate the testing of your web apps with Cypress as part of your development workflow with Continuous Integration (CI) using GitHub Actions.</p>
<p>You can create workflows using GitHub Actions that automate a variety of processes, such as Cypress test runs, whenever changes are made to your source. Let's walk through how to get it set up.</p>
<p><strong>Prerequisites:</strong></p>
<ol>
<li><p>GitHub Repository: The Cypress tests and application code should be located in a GitHub repository.</p>
</li>
<li><p>Cypress Setup: Verify that Cypress is installed in your project and that your Cypress test configurations are accurate. Cypress Open may be used to set up and execute your tests locally.</p>
</li>
</ol>
<h4 id="heading-step-1-establish-a-workflow-configuration">Step 1: Establish a Workflow Configuration:</h4>
<p>Create a file in your GitHub repository called workflow configuration. The CI workflow is defined in this file, which is usually named <code>.github/workflows/cypress.yml</code>. It also provides instructions on when and how to execute Cypress tests.</p>
<p>This is an example of a simple configuration file:</p>
<pre><code class="lang-javascript">name: Cypress Tests

<span class="hljs-attr">on</span>:
  push:
    branches:
      - main # Align to your repository<span class="hljs-string">'s default branch

jobs:
  cypress:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v2

    - name: Install dependencies
      run: npm install

    - name: Run Cypress tests
      run: npm run cypress:run</span>
</code></pre>
<p>Every time a push is made to the <code>main</code> branch, a process to execute Cypress tests is established by this configuration file. This may be altered to meet your needs.</p>
<h4 id="heading-step-2-describe-the-npm-scripts">Step 2: Describe the <code>npm</code> Scripts:</h4>
<p>Define <code>npm</code> scripts in your <code>package.json</code> file so that Cypress tests may be executed. For instance:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"scripts"</span>: {
  <span class="hljs-string">"cypress:run"</span>: <span class="hljs-string">"cypress run"</span>,
  <span class="hljs-string">"cypress:open"</span>: <span class="hljs-string">"cypress open"</span>
}
</code></pre>
<p>While <code>cypress:open</code> is used to run tests interactively during development, <code>cypress:run</code> is used to execute Cypress tests in headless mode.</p>
<h4 id="heading-step-3-add-to-github-repo">Step 3: Add to GitHub Repo</h4>
<p>Add your application code and the GitHub Actions configuration file (cypress.yml) to your GitHub repository. Send the modifications to GitHub.</p>
<h4 id="heading-step-4-switch-on-github-actions">Step 4: Switch on GitHub Actions:</h4>
<p>If you haven't already, go to your GitHub repository, choose the "Actions" tab, and activate GitHub Actions for your repository.</p>
<h4 id="heading-step-5-manage-your-process">Step 5: Manage Your Process:</h4>
<p>GitHub Actions will launch the specified process, which includes your Cypress tests, automatically each time you post changes to your repository.</p>
<h4 id="heading-step-6-examine-the-results">Step 6: Examine the Results:</h4>
<p>The "Actions" page on your GitHub repository is where you can view the status and outcomes of your Cypress tests. Test results and logs will be displayed to you, allowing you to diagnose and look for any issues.</p>
<h3 id="heading-headless-option">Headless Option</h3>
<p>Cypress offers a headless option for continuous integration (CI) testing. Without the Cypress test runner, you can still run Cypress headlessly. By using the command <code>npx cypress run</code> this simply means there is no graphical user interface involved so the test runner isn't being deployed, here we mostly write scripts to use Cypress.</p>
<p>Web browsers that function without a graphical user interface, or headless browsers, are appropriate for automated testing in server contexts such as continuous integration/continuous delivery systems.</p>
<p>When you run Cypress tests in headless mode, no visible browser window is displayed during test execution. This is very helpful when testing without a user interface in an automated continuous integration and delivery pipeline.</p>
<p>To use this put the command <code>npx cypress run</code> in your terminal.</p>
<ol>
<li><p>Cypress will launch your test in an electron browser when you execute this command.</p>
</li>
<li><p>Upon completion, Cypress will automatically provide a video clip of the test run and screenshots in the event that a test fails, facilitating easy debugging.</p>
</li>
<li><p>The user can adjust screenshots and videos. The folder labeled "screenshots and videos" contains both screenshots and videos cypress produces.</p>
</li>
</ol>
<p>Another way of running tests is through adding scripts to your <code>package.json</code> file. You can add scripts which consist of the <code>key</code> and <code>value</code> pair that work as objects. with <code>key</code> being the name of the command and <code>value</code> being the command that's being run.</p>
<p>Let's run a test headlessly using a key value pair:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"script"</span>: {
    <span class="hljs-string">"cypress:run"</span>: <span class="hljs-string">"cypress run"</span>,
    <span class="hljs-string">"cypress:open"</span>: <span class="hljs-string">"cypress open"</span>
},
</code></pre>
<p>In this code <code>"cypress:run"</code> and <code>"cypress:open"</code> are the <code>keys</code> and <code>"cypress open"</code> and <code>"cypress open"</code> are the commands. To run the command we need the <code>npm run</code> followed by the key used example: <code>npm cypress:run</code>.</p>
<p>Hopefully you now understand how to run tests headlessly in Cypress.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Cypress is an effective tool you can use to test your web apps. You can use it to write end-to-end tests that make sure your React projects function as intended and identify any issues.</p>
<p>Cypress is a useful addition to your testing toolkit because of its interactive Test Runner, easy-to-use API, and robust debugging features. Now go ahead and use Cypress to test your React applications to make sure the user experience is seamless and error-free.</p>
<p>If you want to learn about Cypress in more detail, you can check out their documentation at <a target="_blank" href="https://www.freecodecamp.org/news/p/ee697448-b73d-43dd-85de-d31f1e1005c6/CYPRESS.IO">cypress.io</a>. Cheers to your testing!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test User Interactions Using the React Testing Library ]]>
                </title>
                <description>
                    <![CDATA[ When you're testing different components in React, you’ll need to simulate user interactions with various parts of each component. In this tutorial, I am going to show you some methods to simulate user interactions with different interactive elements... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-test-user-interactions-in-react/</link>
                <guid isPermaLink="false">66d8515139c4dccc43d4d4bf</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kunal Nalawade ]]>
                </dc:creator>
                <pubDate>Mon, 20 Feb 2023 19:12:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/02/0_cba5BsaMWpgBXzGK.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you're testing different components in React, you’ll need to simulate user interactions with various parts of each component.</p>
<p>In this tutorial, I am going to show you some methods to simulate user interactions with different interactive elements.</p>
<p>Before getting started, make sure you know how to structure a test in React and how to write simple tests. You can brush up on that knowledge by reading my <a target="_blank" href="https://www.freecodecamp.org/news/how-to-write-unit-tests-in-react/">previous post</a> about how to write unit tests in React.</p>
<p>I also have a challenge for you, so make sure you read the complete article.</p>
<h2 id="heading-how-to-set-up-the-project">How to Set Up the Project</h2>
<p>Create your React app using the command <code>create-react-app testing-user-interactions</code>. Next, install the library <code>user-event</code>.</p>
<pre><code class="lang-python">npm i user-event
</code></pre>
<p>I am assuming you know how to use this library. If not, I explained how it works in my previous <a target="_blank" href="https://www.freecodecamp.org/news/how-to-write-unit-tests-in-react/">tutorial</a>.</p>
<p>Basically, this library has a few functions you can use to simulate a user. We’ll go through all these functions with examples.</p>
<p>Before writing your tests, you'll need to make the following imports at the start of your test suite (the file in which you write all your tests).</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>
</code></pre>
<p>You'll also need to import the component you’ll be testing.</p>
<p>For every test, we'll be simulating user interactions and test how our component behaves.</p>
<h2 id="heading-how-to-test-select-element-interactions">How to Test <code>select</code> Element Interactions</h2>
<p>In your application, you may have <code>select</code> elements that allow a user to select something from a list of options. Let’s take an example:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'selectCity'</span>&gt;</span>    
    <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span> Mumbai<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>    
    <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span> Bangalore<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>    
    <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span> Chennai<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
</code></pre>
<p>Normally, when a user sees a <code>select</code> element, they would click it and select an option they want. We are going to simulate the exact same behaviour and test if our component reacts accordingly. For instance, if a user selects <code>Chennai</code>, the value shown must be the same.</p>
<p>First, let’s see how to query the element. Let's use <code>getByRole</code> to query the element. You can use the role <code>combobox</code> for the above element.</p>
<pre><code class="lang-javascript">screen.getByRole(<span class="hljs-string">'combobox'</span>)
</code></pre>
<p>What is a role? How do you know which role to use for an element? In React, for testing purposes, there's an attribute called <code>role</code> defined for every HTML element. This makes it easier for you to query an element while testing.</p>
<p>Now, in order to know the value of the role attribute for each element, you can refer to the list of roles <a target="_blank" href="https://www.w3.org/TR/html-aria/#docconformance">here</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/Screenshot-2023-02-18-at-12.37.44-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>A Snapshot of the List of Roles</em></p>
<p>If you want to query an anchor element containing an <code>href</code> attribute, you can use the role <code>link</code>. Same is true for other elements in the list.</p>
<p>However, do not define an explicit role attribute for your HTML element, as it's already defined for you.</p>
<p>Let's see another method of querying the element. If there are is more than one <code>select</code> element, you can use <code>getByLabelText()</code>. For this, add a label, <code>&lt;label htmlFor=”selectCity”&gt;Select City&lt;/label&gt;</code> for the element and do <code>screen.getByLabelText(/select city/i)</code>. You’ll get the element.</p>
<p>Now, use the <code>[selectOptions()](https://testing-library.com/docs/ecosystem-user-event/#selectoptionselement-values-options)</code> method of the library to simulate a user selecting an option. It takes three arguments: the element, the value, and any options.</p>
<pre><code class="lang-javascript">userEvent.selectOptions(    
    screen.getByRole(<span class="hljs-string">'combobox'</span>),    
    <span class="hljs-string">'Chennai'</span>
)
</code></pre>
<p>Then, add an assertion at the end of your test to check if the element has the same value that the user selected.</p>
<pre><code class="lang-javascript">expect(screen.getByRole(<span class="hljs-string">'combobox'</span>)).toHaveValue(<span class="hljs-string">'Chennai'</span>)
</code></pre>
<p>The user selected <code>Chennai</code> and our <code>select</code> element has the same value, so the test passes.</p>
<h2 id="heading-how-to-test-state-change">How to Test State Change</h2>
<p>Now, I’ll make a state update on changing the <code>select</code> element’s value. First, I’ll have a state called <code>cityName</code> to display the option selected.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [cityName, setCityName] = useState(<span class="hljs-string">'Mumbai'</span>);
</code></pre>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span> City Name:  {cityName} <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
</code></pre>
<p>Then, I’ll add an <code>onChange</code> attribute with a method that updates the state.</p>
<pre><code class="lang-javascript">onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> { setCityName(e.target.value)}}
</code></pre>
<p>To write the test for this, follow the same process:</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"City Name header rendering"</span>, <span class="hljs-function">() =&gt;</span> {    
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">SelectElement</span>/&gt;</span></span>)    
    userEvent.selectOptions(        
        screen.getByRole(<span class="hljs-string">'combobox'</span>),        
        <span class="hljs-string">'Bangalore'</span>    )    
    expect(screen.getByRole(<span class="hljs-string">'heading'</span>,               
                     { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/bangalore/i</span>})).toBeInTheDocument();
})
</code></pre>
<p>To query the <code>h2</code> element, use <code>[getByRole()](https://testing-library.com/docs/queries/byrole/)</code> with the <code>name</code> option. The <code>name</code> option adds another filter to the query – in case there are multiple <code>h2</code> elements.</p>
<h2 id="heading-how-to-test-a-hover-event">How to Test a Hover Event</h2>
<p>Your application may have functionalities where a user can hover over an element to see some information as a tooltip. So, to learn how to test this, let’s implement a basic tooltip functionality.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"tooltip"</span>&gt;</span>    
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span> Hover over me<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>    
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"tooltiptext"</span>&gt;</span>Tooltip text<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>There’s also some CSS styles for the classes defined above, but I won’t show them here. Basically, when a user hovers over the text <em>Hover over me,</em> the tooltip text is visible.</p>
<p>To simulate a user hovering over the text element, use the <code>hover()</code> method. Add an assertion at the end to test if the tooltip is visible.</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Tooltip visible"</span>, <span class="hljs-function">() =&gt;</span> {    
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">HoverEvent</span>/&gt;</span></span>)
    userEvent.hover(screen.getByText(<span class="hljs-regexp">/hover over me/i</span>))          
    expect(screen.getByText(<span class="hljs-regexp">/tooltip text/i</span>)).toBeInTheDocument()
})
</code></pre>
<p>The test will pass if the tooltip is visible on hover. If it doesn't, you'll need to refactor your code.</p>
<h2 id="heading-how-to-test-a-file-upload-event">How to Test a File Upload Event</h2>
<p>Another important event is a user uploading files to your website. This is a pretty common functionality in any UI application.</p>
<p>Let’s have an <code>input</code> of type <code>file</code>.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"singleFile"</span>&gt;</span>Upload File<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">type</span>=<span class="hljs-string">'file'</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'singleFile'</span> /&gt;</span>
</code></pre>
<p>In the test, you’ll need to simulate the user uploading a file. Use the <code>upload()</code> method to do that:</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Test single file upload"</span>, <span class="hljs-function">() =&gt;</span> {    
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FileUpload</span>/&gt;</span></span>)    
    <span class="hljs-keyword">const</span> file = <span class="hljs-keyword">new</span> File([<span class="hljs-string">'hello'</span>], <span class="hljs-string">'hello.png'</span>, {<span class="hljs-attr">type</span>: <span class="hljs-string">'image/png'</span>})   
    userEvent.upload(screen.getByLabelText(<span class="hljs-regexp">/upload file/i</span>), file)  
    expect(screen.getByLabelText(<span class="hljs-regexp">/upload file/i</span>).files[<span class="hljs-number">0</span>]).toEqual(file)
})
</code></pre>
<p>First, create a mock file and upload the same. Then, make an assertion that the file present in the input matches our uploaded file. It does, so the test passes.</p>
<p>Now, let’s take an example of multiple file uploads.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'multipleFiles'</span>&gt;</span>Upload multiple files<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">type</span>=<span class="hljs-string">'file'</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'multipleFiles'</span> <span class="hljs-attr">multiple</span> /&gt;</span>
</code></pre>
<p>In our test, upload multiple files.</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Test multiple file uploads"</span>, <span class="hljs-function">() =&gt;</span> {    
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FileUpload</span>/&gt;</span></span>)    
    <span class="hljs-keyword">const</span> files = [        
           <span class="hljs-keyword">new</span> File([<span class="hljs-string">'file1'</span>], <span class="hljs-string">'file1.png'</span>, {<span class="hljs-attr">type</span>: <span class="hljs-string">'image/png'</span>}),       
           <span class="hljs-keyword">new</span> File([<span class="hljs-string">'file2'</span>], <span class="hljs-string">'file2.png'</span>, {<span class="hljs-attr">type</span>: <span class="hljs-string">'application/pdf'</span>})   
    ]    
    userEvent.upload(screen.getByLabelText(<span class="hljs-regexp">/upload multiple files/i</span>), 
                        files)    
    <span class="hljs-keyword">const</span> fileInput = screen.getByLabelText(<span class="hljs-regexp">/upload multiple files/i</span>)  

    expect(fileInput.files.length).toBe(<span class="hljs-number">2</span>);   
    expect(fileInput.files[<span class="hljs-number">0</span>]).toEqual(files[<span class="hljs-number">0</span>]);   
    expect(fileInput.files[<span class="hljs-number">1</span>]).toEqual(files[<span class="hljs-number">1</span>]);
})
</code></pre>
<p>In the assertions, check the number of files and whether both the files match the uploaded ones. Thus, all our tests pass with 100% coverage.</p>
<p>Now, time for the challenge. Go through my posts on <a target="_blank" href="https://medium.com/gitconnected/how-to-implement-multiple-file-uploads-in-react-4cdcaadd0f6e">Muliple File Uploads</a> and <a target="_blank" href="https://medium.com/gitconnected/4-different-examples-of-the-usestate-hook-in-react-5504ce011a20">Different Examples of the useState Hook</a> and write unit tests for all the functionalities. Upload your code on GitHub and share it with me.</p>
<h2 id="heading-how-to-test-form-behaviour">How to Test Form Behaviour</h2>
<p>An important feature of most web apps is form handling. When writing tests for a React app, it’s highly likely you will need to test your component’s behaviour with forms.</p>
<p>In our example, let’s have a form with two <code>input</code> fields, one <code>select</code> field and a submit button.</p>
<p>First, let’s define the states for the respective fields and an additional state for indicating whether the form is submitted.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [name, setName] = 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> [country, setCountry] = useState(<span class="hljs-string">''</span>)
<span class="hljs-keyword">const</span> [formSubmitted, setFormSubmitted] = useState(<span class="hljs-literal">false</span>)
</code></pre>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>    
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> {setName(e.target.value)}}             
           placeholder='Enter name'/&gt;    
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> {setPassword(e.target.value)}}                          placeholder='Enter password'/&gt;    
    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'selectCountry'</span>&gt;</span> Select Country <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>    
    <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'selectCountry'</span>             
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> {setCountry(e.target.value)}}&gt;       
        <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>India<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>        <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>Japan<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>       
        <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>China<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>        <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>USA<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>       
        <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>England<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>    
    <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>    
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleSubmit}</span>&gt;</span> Submit <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>
</code></pre>
<p>In the <code>onChange</code> attribute, make the state updates. In the end, display a text on successful submission of the form.</p>
<pre><code class="lang-html">{formSubmitted &amp;&amp; 'Form Submitted'}
</code></pre>
<p>Also, let’s add some validation in the <code>handleSubmit</code> method. When a validation fails, a list of errors will be shown. Create another state to hold errors.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [errors, setErrors] = useState([])
</code></pre>
<p>Now, implement the <code>handleSubmit</code> method.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {    
    event.preventDefault();    
    <span class="hljs-keyword">const</span> errorList=[];    
    <span class="hljs-keyword">if</span>(name == <span class="hljs-string">''</span> || password == <span class="hljs-string">''</span> || country == <span class="hljs-string">''</span>) {        
        errorList.push(<span class="hljs-string">'Please fill all the details'</span>)    
    }        
    <span class="hljs-keyword">const</span> regex = <span class="hljs-regexp">/^[a-z]*$/i</span>;    
    <span class="hljs-keyword">if</span>(!name.match(regex)) {        
        errorList.push(<span class="hljs-string">'Please enter a valid name'</span>)    
    }    
    <span class="hljs-keyword">if</span>(errorList.length != <span class="hljs-number">0</span>) {        
        setErrors(errorList);        
        setFormSubmitted(<span class="hljs-literal">false</span>);        
        <span class="hljs-keyword">return</span>;      
    }     
    setErrors([]);    
    setFormSubmitted(<span class="hljs-literal">true</span>)
}
</code></pre>
<p>First, we’ll do <code>event.preventDefault()</code> which cancels out any events that prevent submitting the form. Then, we’ll add few error checks and add any errors to the state <code>errors</code>. In the end, if there are no errors then set <code>errors</code> to an empty array and set <code>formSubmitted</code> as <code>true</code>.</p>
<p>We’ll also display the list of all the errors which will be visible on clicking the submit button.</p>
<pre><code class="lang-javascript">errors.map(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> (    
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>        
        {err}   
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
))
</code></pre>
<p>Let’s write the tests for this example. There are a few different things you should test. First, write a test simulating a user interacting with the form and submitting it with correct values.</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Form getting submitted with correct input values"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FormBehaviour</span>/&gt;</span></span>)

    userEvent.type(screen.getByPlaceholderText(<span class="hljs-regexp">/enter name/i</span>), <span class="hljs-string">'kunal'</span>)
    userEvent.type(screen.getByPlaceholderText(<span class="hljs-regexp">/enter password/i</span>),                                   <span class="hljs-string">'pass'</span>)
    userEvent.selectOptions(screen.getByLabelText(<span class="hljs-regexp">/select country/i</span>),                               <span class="hljs-string">'India'</span>)
    userEvent.click(screen.getByText(<span class="hljs-regexp">/submit/i</span>))

    expect(screen.getByText(<span class="hljs-regexp">/form submitted/i</span>)).toBeInTheDocument();
})
</code></pre>
<p>Use the <code>type</code> and <code>click</code> methods of the <code>userEvent</code> library to simulate the respective actions. In the end, add an assertion to see if the text matching the regex <code>/form submitted/i</code> is visible in the document since our logic displays the text as soon as the form is submitted.</p>
<p><img src="https://miro.medium.com/max/1400/1*Tb0jZPtQRAJwlrlWpEhUiw.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Test Coverage for the Form Behaviour Component</em></p>
<p>Our test passes, but there are still some uncovered lines. We have tested regular form behaviour. However, we also have error handling mechanisms in place. So, you also need to write tests that cover different error scenarios.</p>
<p>First, simulate a user clicking the submit button without entering anything.</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Empty fields error shown"</span>, <span class="hljs-function">() =&gt;</span> {    
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FormBehaviour</span>/&gt;</span></span>)    
    userEvent.click(screen.getByText(<span class="hljs-regexp">/submit/i</span>))    
    expect(screen.getByText(<span class="hljs-regexp">/please fill all the details/i</span>))                         .toBeInTheDocument()
})
</code></pre>
<p>On clicking the submit button, an error message should be visible prompting the user to fill all the details. Make an assertion for the same.</p>
<p>Not all lines are covered yet. We still have one more error scenario. What if the user enters an invalid name?</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Invalid name error shown"</span>, <span class="hljs-function">() =&gt;</span> {    
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FormBehaviour</span>/&gt;</span></span>)    
    userEvent.type(screen.getByPlaceholderText(<span class="hljs-regexp">/enter name/i</span>), <span class="hljs-string">'@###'</span>)       userEvent.type(screen.getByPlaceholderText(<span class="hljs-regexp">/enter password/i</span>),                                    <span class="hljs-string">'pass'</span>)    
    userEvent.selectOptions(screen.getByLabelText(<span class="hljs-regexp">/select country/i</span>),                                  <span class="hljs-string">'India'</span>)    
    userEvent.click(screen.getByText(<span class="hljs-regexp">/submit/i</span>))    
    expect(screen.getByText(<span class="hljs-regexp">/Please enter a valid name/i</span>))
          .toBeInTheDocument();
})
</code></pre>
<p>This test is similar to the first test, except here, the user enters an invalid name. So, assert whether the corresponding error is shown.</p>
<p><img src="https://miro.medium.com/max/1400/1*QEBA48iEtB15EmuQrs-DTQ.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>100% Coverage for the Form Behaviour Component</em></p>
<p>Now, all our tests pass with 100% coverage.</p>
<p>You can find the complete code on <a target="_blank" href="https://github.com/KunalN25/testing-user-interactions">GitHub</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Testing user interactions helps you make sure that users can interact efficiently and effectively with your application. You need to cover all possible scenarios a user may encounter. In this article, I have shown you how you can simulate user interactions with different elements.</p>
<p>I have also demonstrated testing form behaviour covering many scenarios. I hope you now know how to use the <code>userEvent</code> library to test your component’s behaviour with user events.</p>
<p>If you are unable to understand the content or find the explanation unsatisfactory, get in touch with me and let me know. New ideas are always appreciated! Feel free to connect with me on Twitter. Till then, Goodbye!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Write Unit Tests in React ]]>
                </title>
                <description>
                    <![CDATA[ When you're building a website and coding all the cool features you had planned, you'll want to test if everything works as expected. Now, you can’t do that just by going through the website yourself – you need to check if each unit of your code is w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-write-unit-tests-in-react/</link>
                <guid isPermaLink="false">66d8515392b237d9f6a7c528</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ unit testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kunal Nalawade ]]>
                </dc:creator>
                <pubDate>Mon, 23 Jan 2023 15:35:25 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/unit-testing-in-react-image.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you're building a website and coding all the cool features you had planned, you'll want to test if everything works as expected.</p>
<p>Now, you can’t do that just by going through the website yourself – you need to check if each unit of your code is working as you want. For that, you need to write unit tests. And they can be quite a hassle when you actually get down to writing them.</p>
<p>So, in this post, I am going to show you how you can get started with writing unit tests in React. I will explain the process through several examples to help you understand better.</p>
<p>For this post, I assume you have a basic knowledge of React. If not, then head over to the <a target="_blank" href="https://reactjs.org/docs/getting-started.html">docs</a> to get started.</p>
<h2 id="heading-how-is-a-test-structured">How Is a Test Structured?</h2>
<p>Testing involves checking if your code is functioning as it's supposed to by comparing the expected output with the actual output.</p>
<h3 id="heading-what-to-test">What to Test</h3>
<p>In general, your tests should cover the following aspects of your code:</p>
<ol>
<li><p>If a component renders with or without props</p>
</li>
<li><p>How a component renders with state changes</p>
</li>
<li><p>How a component reacts to user interactions</p>
</li>
</ol>
<h3 id="heading-what-not-to-test">What Not to Test</h3>
<p>Testing most of your code is important, but here are some things you do not need to test:</p>
<ol>
<li><p><strong>Actual Implementation:</strong> You do not need to test the actual implementation of a functionality. Just test if the component is behaving correctly.<br> Let’s say you want to sort an array on the click of a button. There’s no need to test the actual sorting logic. You only test if the function was called and if the state changes are rendering correctly.</p>
</li>
<li><p><strong>Third Party libraries:</strong> If you are using any third party libraries like Material UI, no need to test those – they should already be tried and tested.</p>
</li>
</ol>
<p>This might seem a little complicated at the moment, but you should understand better through examples.</p>
<p>Any test in React, no matter how complicated, follows this structure:</p>
<ol>
<li><p>Render the component</p>
</li>
<li><p>Get an element from the component and simulate any user interactions</p>
</li>
<li><p>Write an assertion.</p>
</li>
</ol>
<h2 id="heading-how-to-set-up-our-project">How to Set Up Our Project</h2>
<p>First, create the app with <code>create-react-app</code>. We’ll be using <a target="_blank" href="https://jestjs.io/docs/getting-started">Jest</a> and <a target="_blank" href="https://testing-library.com/docs/react-testing-library/intro/">React Testing Library</a>. Both of them come pre - installed with <code>create-react-app</code>. You can see what it looks like here:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"dependencies"</span>: {
    <span class="hljs-string">"@testing-library/jest-dom"</span>: <span class="hljs-string">"^5.16.5"</span>,
    <span class="hljs-string">"@testing-library/react"</span>: <span class="hljs-string">"^13.4.0"</span>,
    <span class="hljs-string">"react"</span>: <span class="hljs-string">"^18.2.0"</span>,
    <span class="hljs-string">"react-dom"</span>: <span class="hljs-string">"^18.2.0"</span>,
    <span class="hljs-string">"react-scripts"</span>: <span class="hljs-string">"5.0.1"</span>,
    <span class="hljs-string">"web-vitals"</span>: <span class="hljs-string">"^2.1.4"</span>
},
</code></pre>
<p>As you can see, Jest is not visible here. But, if you go to the <code>node_modules</code> folder, you will see Jest there. So, no need to install it separately.</p>
<p>Also, make sure you have the following lines in your <code>setupTests.js</code> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">'@testing-library/jest-dom'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'@testing-library/jest-dom/extend-expect'</span>;
</code></pre>
<p>Also, in your <code>package.json</code> file, modify your scripts like this:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-string">"start"</span>: <span class="hljs-string">"react-scripts start"</span>,
    <span class="hljs-string">"build"</span>: <span class="hljs-string">"react-scripts build"</span>,
    <span class="hljs-string">"test"</span>: <span class="hljs-string">"react-scripts test --watchAll --coverage"</span>,
    <span class="hljs-string">"eject"</span>: <span class="hljs-string">"react-scripts eject"</span>
},
</code></pre>
<p>This will run your tests in watch mode and also show you the coverage (that is, the portion of the code covered by your tests). You can also define the coverage threshold by adding another property <code>jest</code>. For now, keep the threshold at 80%.</p>
<pre><code class="lang-javascript"><span class="hljs-string">"jest"</span>: {
    <span class="hljs-string">"coverageThreshold"</span>: {
        <span class="hljs-string">"global"</span>: {
            <span class="hljs-string">"lines"</span>: <span class="hljs-number">80</span>
        }
    }
}
</code></pre>
<p>That’s it, now you are ready to start testing. Let’s first start with a basic test.</p>
<h2 id="heading-how-to-write-your-first-test-for-a-react-app">How to Write Your First Test for a React App</h2>
<p>Let’s write a test for the following component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> FirstTest = <span class="hljs-function">() =&gt;</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">h2</span>&gt;</span> First test <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<p>Here, we just need to test if the <code>h2</code> element renders. Now, where should we write the tests? We can write them inside a <code>__tests__</code> folder anywhere in the <code>src</code> folder. The test file just needs to have a <code>.test.js/jsx</code> extension and the test runner will pick it up.</p>
<p>This is how the test looks in our <code>FirstTest.test.jsx</code> file:</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> FirstTest <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/FirstTest'</span>;

test(<span class="hljs-string">"Example 1 renders successfully"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FirstTest</span>/&gt;</span></span>);

    <span class="hljs-keyword">const</span> element = screen.getByText(<span class="hljs-regexp">/first test/i</span>);

    expect(element).toBeInTheDocument();
})
</code></pre>
<p>First, import the required methods. The <code>test()</code> method contains an individual test. It takes the name of the test and a callback function as the two arguments.</p>
<p>Now, following the structure mentioned above, render the component you're testing using the <a target="_blank" href="https://testing-library.com/docs/react-testing-library/api/#render">render</a> method. Then, use the <code>screen</code> object to make a query for an element. In this case, it’s the <code>h2</code> element. Our query gets an element containing text that matches the regex <code>/first test/i</code> (<em>i</em> means ignore case).</p>
<p>Lastly, make the assertion using the <code>expect</code> method. We expect the element to be in the document and it is, so the test passes.</p>
<p>There are lots of other assertions you can make in your tests. You can read more about them <a target="_blank" href="https://jestjs.io/docs/using-matchers">here</a>. Also, you can find a list of ways to query an element <a target="_blank" href="https://testing-library.com/docs/queries/about">here</a>. We’ll use some of them in our further examples.</p>
<h2 id="heading-how-to-test-with-mock-data-in-react">How to Test With Mock Data in React</h2>
<p>Here, we have a component with a prop <code>data</code> that displays a list of items. Let’s assume this data comes from the backend and your component is displaying this data.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">const</span> TestWithMockData = <span class="hljs-function">(<span class="hljs-params">{data}</span>) =&gt;</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">ul</span>&gt;</span>
            {data.map(item =&gt; (
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{item.id}</span>&gt;</span>
                    {item.id}
                    {item.first_name},
                    {item.last_name},
                    {item.email}

                <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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> TestWithMockData
</code></pre>
<p>While writing tests for a component with props, you need to pass some mock data while rendering this component that pertains to your functionality. Here, one object in our data contains four fields, so we pass some mock data here.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mockData = [
    {
        <span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>,
        <span class="hljs-string">"first_name"</span>: <span class="hljs-string">"Fletcher"</span>,
        <span class="hljs-string">"last_name"</span>: <span class="hljs-string">"McVanamy"</span>,
        <span class="hljs-string">"email"</span>: <span class="hljs-string">"mmcvanamy0@e-recht24.de"</span>,
        <span class="hljs-string">"age"</span>: <span class="hljs-number">30</span>
      }, {
        <span class="hljs-string">"id"</span>: <span class="hljs-number">2</span>,
        <span class="hljs-string">"first_name"</span>: <span class="hljs-string">"Clarice"</span>,
        <span class="hljs-string">"last_name"</span>: <span class="hljs-string">"Harrild"</span>,
        <span class="hljs-string">"email"</span>: <span class="hljs-string">"charrild1@dion.ne.jp"</span>,
        <span class="hljs-string">"age"</span>: <span class="hljs-number">35</span>
      }, 
]
</code></pre>
<p>Note that the actual data could contain thousands of records, but the mock data only needs to be relevant to what you want to test. Now, let’s write the test and check if the list renders.</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"List renders successfully"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TestWithMockData</span> <span class="hljs-attr">data</span>=<span class="hljs-string">{mockData}</span> /&gt;</span></span>)
    expect(screen.getByText(<span class="hljs-regexp">/fletcher/i</span>)).toBeInTheDocument();
})
</code></pre>
<h2 id="heading-how-to-test-with-mock-data-covering-all-branches-in-react">How to Test With Mock Data Covering All Branches in React</h2>
<p>Let’s introduce some branching in the above component. We’ll have another prop, <code>displayUnorderedList</code>, that determines whether to display an ordered or unordered list. We’ll also render <code>Senior</code> for <code>age &gt; 50</code> and <code>Not Senior</code> otherwise.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">const</span> TestWithMockData = <span class="hljs-function">(<span class="hljs-params">{data, displayUnorderedList, handleClick}</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        {displayUnorderedList ? 
            <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
                {data.map(item =&gt; (
                    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{item.id}</span>&gt;</span>
                        {item.id}
                        {item.first_name},
                        {item.last_name},
                        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {
                            console.log("email link clicked")
                            handleClick()
                        }}&gt;{item.email}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>

                        {item.age &gt; 50 ? 'Senior' : 'Not senior'}

                    <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 class="hljs-tag">&lt;<span class="hljs-name">ol</span>&gt;</span>
                {data.map(item =&gt; (
                    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{item.id}</span>&gt;</span>
                        Last name: {item.last_name}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                ))}
            <span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
        }
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> TestWithMockData
</code></pre>
<p>Here's the coverage report so far:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image-141.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Failed Test: Unable to find the element</em></p>
<p>You can see that Line 9 (that is, the unordered list part) is not covered by our existing tests. And our previous test has also failed since it is unable to find <code>fletcher</code> in the component.</p>
<p>Why is this so? In our previous test, we have not passed the <code>displayUnorderedList</code> prop to the component so it is <code>null</code>. So, the component renders the ordered list and it does not contain the <code>first_name</code>.</p>
<p>So, let’s write another test covering the ordered list part.</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Ordered list renders"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TestWithMockData</span> <span class="hljs-attr">data</span>=<span class="hljs-string">{mockData}</span> <span class="hljs-attr">displayUnorderedList</span>=<span class="hljs-string">{false}</span> /&gt;</span></span>)

    expect(screen.getByText(<span class="hljs-regexp">/McVanamy/i</span>)).toBeInTheDocument()
})
</code></pre>
<p>Here, pass the prop’s value as <code>false</code> to render the ordered list. Also, add the <code>displayUnorderedList</code> prop to the failing test and pass the value <code>true</code>.</p>
<p>Now, all our tests pass with 100% coverage.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image-142.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Uncovered Branch</em></p>
<p>One line is still not covered by the test cases, which is the branching logic for <code>age</code>. So, add another record to the mock data that has an age greater than 50.</p>
<pre><code class="lang-javascript">{
    <span class="hljs-string">"id"</span>: <span class="hljs-number">3</span>,
    <span class="hljs-string">"first_name"</span>: <span class="hljs-string">"Amby"</span>,
    <span class="hljs-string">"last_name"</span>: <span class="hljs-string">"Emmer"</span>,
    <span class="hljs-string">"email"</span>: <span class="hljs-string">"aemmer2@buzzfeed.com"</span>,
    <span class="hljs-string">"age"</span>: <span class="hljs-number">67</span>
}
</code></pre>
<p>Now, all our tests should pass with 100% coverage.</p>
<h2 id="heading-how-to-test-user-interactions-in-react">How to Test User Interactions in React</h2>
<p>The most important part of testing any UI app is testing its behaviour with various user interactions. Almost every functionality in a UI app involves user interactions.</p>
<p>You can use the <code>user-event</code> library to simulate user interactions. It has methods for simulating various user events such as <em>click</em>, <em>type</em>, <em>hover</em>, and so on.</p>
<p>First, we need to install the library:</p>
<pre><code class="lang-python">npm install --save-dev @testing-library/user-event
</code></pre>
<p>We can use this library to simulate user events. In our examples, we’ll interact with different elements, mostly <code>input</code> and <code>button</code> elements.</p>
<h3 id="heading-how-to-test-a-function-call-on-click-of-an-element">How to Test a Function Call on Click of an Element</h3>
<p>In our above component, we need to<code>TestWithMockData</code>, make the <code>email</code> field clickable and call a function <code>handleClick</code> on it. This will be passed as props to the component. There, replace <code>{item.email}</code> with:</p>
<pre><code class="lang-javascript">&lt;a onClick={<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"email link clicked"</span>)
    handleClick()
}}&gt;{item.email}&lt;/a&gt;
</code></pre>
<p>Now, our test coverage takes a hit. To cover this scenario, write the following test:</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Email link click handler called"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> mockHandleClick = jest.fn();
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TestWithMockData</span> 
                <span class="hljs-attr">data</span>=<span class="hljs-string">{mockData}</span> 
                <span class="hljs-attr">displayUnorderedList</span>=<span class="hljs-string">{true}</span>
                <span class="hljs-attr">handleClick</span> = <span class="hljs-string">{mockHandleClick}</span>
          /&gt;</span></span>)
    <span class="hljs-keyword">await</span> userEvent.click(screen.getByText(<span class="hljs-regexp">/mmcvanamy0@e-recht24.de/i</span>));
    expect(mockHandleClick).toHaveBeenCalled();
})
</code></pre>
<p>First, create the mock of the <code>handleClick</code> function using <code>jest.fn()</code>. We do not need the actual implementation of the method, as we only want to test the behaviour of the component. So, we create an empty mock and pass the same as props. Read more about mocking functions <a target="_blank" href="https://jestjs.io/docs/mock-functions">here</a>.</p>
<p>Now, query the <code>&lt;a&gt;</code> element by text (any <code>email</code> from the mock data). Use the <code>click()</code> method to simulate a click event. Use <code>await</code> as simulating a user event is an asynchronous operation.</p>
<p>Write an assertion in the end to check if the method was called. The method was called, so our test passes with 100% coverage.</p>
<h3 id="heading-how-to-query-input-fields-and-buttons">How to Query Input Fields and Buttons</h3>
<p>So far, we have used only one method of querying elements – <code>getByText()</code>. Now, let’s see how you can query input fields and buttons.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Enter name'</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span> Submit <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>To query these elements, you can do the following:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> inputElement = screen.getByRole(<span class="hljs-string">'textbox'</span>)
</code></pre>
<p><code>getByRole</code> finds an element by the given role. In this case, the <code>textbox</code> role means the <code>input</code> element.</p>
<p>How is role determined? Each element has a defined role, so you do not need to specify an explicit role attribute. You can see a list of roles for different elements <a target="_blank" href="https://www.w3.org/TR/html-aria/#docconformance">here</a>. Whatever element you want, just do <code>getByRole</code> and refer to the list.</p>
<p>For <code>button</code>, the default role is ‘button’ as you can see here:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> button = screen.getByRole(<span class="hljs-string">'button'</span>)
</code></pre>
<p>What if we add another <code>input</code> element, <code>&lt;input placeholder=’Enter description’/&gt;</code>? The test runner will now throw an error saying that there are two elements with the same role. What should we do in such a scenario? Use another query <code>getByPlaceholderText()</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> nameInput = screen.getByPlaceholderText(<span class="hljs-regexp">/enter name/i</span>);
<span class="hljs-keyword">const</span> descrInput = screen.getByPlaceholderText(<span class="hljs-regexp">/enter description/i</span>);
</code></pre>
<p>You can also use <code>getByLabelText()</code> if your <code>input</code> has a <code>label</code>.</p>
<pre><code class="lang-javascript">&lt;label htmlFor=<span class="hljs-string">'password'</span>&gt; Enter password&lt;/label&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">'password'</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'password'</span>/&gt;</span></span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> passwordInput = screen.getByLabelText(<span class="hljs-regexp">/enter password/i</span>);
</code></pre>
<p>For querying buttons, we can do <code>screen.getByRole('button')</code>.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span> Submit <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span> Apply<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Since we have two buttons here, just doing <code>getByRole</code> will throw an error. So, we use a <code>name</code> option.</p>
<pre><code class="lang-javascript"><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">/submit/i</span> });
<span class="hljs-keyword">const</span> applyButton = screen.getByRole(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-regexp">/apply/i</span> });
</code></pre>
<p>The <code>name</code> option can contain the label of a form element, text of a button, or the value of the <code>aria-label</code> attribute of any element. We can also do <code>getByText()</code> for a button.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> submitButton = screen.getByText(<span class="hljs-regexp">/submit/i</span>);
</code></pre>
<h2 id="heading-how-to-test-state-updates-in-react">How to Test State Updates in React</h2>
<p>We have seen how to query form elements like <code>input</code> and <code>button</code>. Now, let’s simulate some user interactions and test state updates. What do I mean by testing state updates?</p>
<p>State updates cause a component to re-render. So, when your functionality performs a state update, you should test how the component is behaving due to the state change.</p>
<p>First, let’s take a simple example where we set the state as soon as the component is loaded – that is, in the <code>useEffect</code> block.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> TestingStateChange = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [loaded, setLoaded] = useState(<span class="hljs-literal">false</span>)
    useEffect(<span class="hljs-function">() =&gt;</span> {
        setLoaded(<span class="hljs-literal">true</span>)
    }, [])
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        {loaded &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span> Page Loaded <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<p>Here, your entire component, starting from the <code>useEffect</code> statement to the end, is uncovered. When you write the following test, not only does the HTML part get covered but also the <code>useEffect</code> part where you set the state.</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Testing page load"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TestingStateChange</span>/&gt;</span></span>)
    expect(screen.getByText(<span class="hljs-regexp">/page loaded/i</span>)).toBeInTheDocument();
})
</code></pre>
<p>This is how you test state updates. You test whether the component behaves as you expect with a state change.</p>
<h3 id="heading-how-to-test-a-state-change-on-click-of-a-button">How to Test a State Change on Click of a Button</h3>
<p>Let’s have a button and a text that toggles on the click of the button.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [toggleTextVisible, setToggleTextVisible] = useState(<span class="hljs-literal">false</span>)<span class="hljs-string">`</span>
</code></pre>
<pre><code class="lang-html">{toggleTextVisible &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span> Text visible <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> { setToggleTextVisible(!toggleTextVisible) }}&gt; 
    Toggle text 
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>We’ll write the test for this:</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Toggle text visible"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TestingStateChange</span>/&gt;</span></span>);
    <span class="hljs-keyword">await</span> userEvent.click(screen.getByText(<span class="hljs-regexp">/toggle text/i</span>));
    expect(screen.getByText(<span class="hljs-regexp">/text visible/i</span>)).toBeInTheDocument();
})
</code></pre>
<p>With <code>userEvent</code>, we have simulated the click of a button and asserted if the text is visible.</p>
<p>Now, let’s test another scenario where we check if the above button is disabled on the click of another button.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [btnDisabled, setBtnDisabled] = useState(<span class="hljs-literal">false</span>);
</code></pre>
<p>Add a <code>disabled={btnDisabled}</code> attribute to the above button and create another button that controls its value.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> { setBtnDisabled(!btnDisabled) }}&gt; 
      Toggle button disabled 
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Use the <code>toBeDisabled()</code> method to test if the button is disabled.</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Button disabled on click"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TestingStateChange</span>/&gt;</span></span>)
    <span class="hljs-keyword">await</span> userEvent.click(screen.getByText(<span class="hljs-regexp">/toggle button disabled/i</span>));
    expect(screen.getByText(<span class="hljs-regexp">/toggle text/i</span>)).toBeDisabled();
})
</code></pre>
<h3 id="heading-how-to-test-if-an-element-was-added-to-a-list">How to Test if an Element Was Added to a List</h3>
<p>Let’s perform another assertion. Here, we’ll check if an element was added to a list. We’ll have some data and create a state with the data as the initial value. We'll display it while also having a button to add an element to it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [elements, setElements] = useState(data);
</code></pre>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span> List <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
{elements.map(item =&gt; (
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>= <span class="hljs-string">{item.id}</span>&gt;</span>
       {item.id}: { item.name }
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</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> {
    setElements([...elements, {
        id: 4,
        name: 'abhinav'
    }])
}}&gt; Add to list <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Add an attribute <code>data-testid='record'</code> to each record to help query the element. Let’s write the test for this:</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Element added to the list"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TestingStateChange</span>/&gt;</span></span>)
    expect(screen.getAllByTestId(<span class="hljs-string">'record'</span>).length).toBe(<span class="hljs-number">3</span>);

    <span class="hljs-keyword">await</span> userEvent.click(screen.getByText(<span class="hljs-regexp">/add to list/i</span>));
    expect(screen.getAllByTestId(<span class="hljs-string">'record'</span>).length).toBe(<span class="hljs-number">4</span>);
})
</code></pre>
<p>To get multiple elements with the same query, use the <code>getAllBy…</code> method. In this case, there are multiple elements with the test-id <code>record</code>, so using the <code>getAllByTestId()</code> method gets a list of all such elements.</p>
<p>Here, we have checked the length of the list. We can add another assertion to check if the new element is visible.</p>
<pre><code class="lang-javascript">expect(screen.getByText(<span class="hljs-regexp">/abhinav/i</span>)).toBeInTheDocument();
</code></pre>
<h3 id="heading-how-to-test-if-an-element-was-removed-from-a-list">How to Test if an Element Was Removed From a List</h3>
<p>Now, let’s add another button that removes an element from the list.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {
    setElements(elements.filter(item =&gt; item.id != 2))
}}&gt; Remove from list <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>The test for this:</p>
<pre><code class="lang-javascript">test(<span class="hljs-string">"Element removed from list"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TestingStateChange</span>/&gt;</span></span>)
    <span class="hljs-keyword">await</span> userEvent.click(screen.getByText(<span class="hljs-regexp">/remove from list/i</span>));
    expect(screen.getAllByTestId(<span class="hljs-string">'record'</span>).length).toBe(<span class="hljs-number">2</span>);
})
</code></pre>
<p>Here, we test whether the length of the list was reduced. All our tests pass with 100% coverage.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image-143.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Coverage Report for Testing State Change: Tests Passed</em></p>
<h2 id="heading-how-to-test-api-calls-in-react">How to Test API Calls in React</h2>
<p>API calls are an important part of any UI application. Let’s understand how to test an API call.</p>
<p>In this example, we have a file <code>data.json</code> that has some data. We’ll make an API call to fetch this data.</p>
<p>For demonstration, we’ll save this file on the local server only (inside the <code>public</code> folder), but the procedure remains the same when fetching a file from a remote server.</p>
<p>We’ll use <code>fetch</code> to make the API call.</p>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">"http://localhost:3000/data.json"</span>)
  .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
      <span class="hljs-keyword">return</span> res.json();
  })
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
      <span class="hljs-comment">// Store the data in state</span>
  })
</code></pre>
<p>Now, let’s have a state to store the data and render it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [data, setData] = useState([])
</code></pre>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    {data.map(item =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            {item.name}
        <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>
</code></pre>
<p>How do we write the test for this? One way is to mock the <code>fetch</code> method and provide our own mock implementation. But what if there are multiple Fetch API calls in the component? You cannot have the same mock implementation for everyone.</p>
<p>So, we put the <code>fetch</code> call in a separate function and have a mock implementation of that function. We put our API call in a <code>FetchData</code> method that returns the promise returned by <code>fetch</code>.</p>
<p>We put the method in a separate <code>Services.js</code> file and export it from there.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> FetchData = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> fetch(<span class="hljs-string">"http://localhost:3000/data.json"</span>)
        .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
            <span class="hljs-keyword">return</span> res.json();
        }) 
}
</code></pre>
<p>Now, when we call this method inside our component, it looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [data, setData] = useState([])

useEffect(<span class="hljs-function">() =&gt;</span> {
    FetchData().then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
        setData(data);
    })
})

<span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      {data.map(item =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              {item.name}
          <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>
)
</code></pre>
<p>Now, let’s write the test for this. The main thing is to mock the FetchData method. First, import all methods from <code>Services.js</code> as a module.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> services <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/Services'</span>
</code></pre>
<p>Let’s mock our function using <code>jest.spyOn()</code>. The method takes two arguments, the object and the method name as a string.</p>
<pre><code class="lang-python">const mockFetchData = jest.spyOn(services, <span class="hljs-string">'FetchData'</span>)
        .mockImplementation(<span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">return</span> [{
                name: <span class="hljs-string">'kunal'</span>
            }];
        })
</code></pre>
<p><code>spyOn</code> creates an empty mock of the method. Now, we provide our mock implementation where we return some mock data. This will be called when our component renders.</p>
<pre><code class="lang-python">render(&lt;TestingAPICalls/&gt;)
expect(mockFetchData).toHaveBeenCalled();
</code></pre>
<p>We use <code>toHaveBeenCalled()</code> to test if the method was called. It was – so the test passes. Now, to test the behaviour of our component, let’s test if the name was rendered.</p>
<pre><code class="lang-javascript">expect(screen.getByText(<span class="hljs-regexp">/kunal/i</span>)).toBeInTheDocument();
</code></pre>
<p>In this case, the test fails as it is unable to find the element. Why is this so?</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image-144.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Failed Test: Unable to find the element</em></p>
<p>Since API calls and state updates are asynchronous, the text hasn’t been rendered yet. To test asynchronous behaviour, wrap the query in a <code>waitFor()</code> block.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">await</span> waitFor(<span class="hljs-function">() =&gt;</span> {
    expect(screen.getByText(<span class="hljs-regexp">/kunal/i</span>)).toBeInTheDocument();
})
</code></pre>
<p><code>waitFor</code> does exactly what the name suggests. It waits for the asynchronous behaviour to finish before making the query. Now, our test passes with 100% coverage.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image-145.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>100% Coverage for Testing API Calls</em></p>
<p>You can find the complete code on <a target="_blank" href="https://github.com/KunalN25/react-testing-basics">GitHub</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Phew! That was a long one. But it needed to be. I have explained, through basic examples, how you can start writing tests in React.</p>
<p>This article started from a basic test with a simple assertion and explained how to use mock data to serve the purpose of your test.</p>
<p>I also explained how to simulate user interactions, how to test a component’s behaviour on state updates, and lastly how to test API calls. Hopefully, all these examples help you write tests for your upcoming project.</p>
<p>If you are unable to understand the content or find the explanation unsatisfactory, please let me know. New ideas are always appreciated! Feel free to DM me on <a target="_blank" href="https://www.linkedin.com/in/kunalnk25/">LinkedIn</a> if you want to discuss anything. Until then, Goodbye!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ API Testing Best Practices – How to Test APIs for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ In this digital age, APIs have become the cornerstone of how data is shared and processed.  But many users are often unaware of the fact that they are putting their trust in an API and not a person. This is why it's important to leverage API testing ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/rules-of-api-testing-for-beginners/</link>
                <guid isPermaLink="false">66b8dbfb1ce2b93cb4142f0c</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hillary Nyakundi ]]>
                </dc:creator>
                <pubDate>Fri, 16 Dec 2022 21:25:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/12/OOP--2-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this digital age, APIs have become the cornerstone of how data is shared and processed. </p>
<p>But many users are often unaware of the fact that they are putting their trust in an API and not a person.</p>
<p>This is why it's important to leverage API testing techniques to ensure that every aspect of your website or application works as expected.</p>
<h2 id="heading-why-api-testing-is-important">Why API Testing is Important</h2>
<p>My first interaction with APIs was while working on a personal project, a <a target="_blank" href="https://github.com/larymak/VirtualAssistant">VirtualAssistant</a>. It required me to fetch real data from third party apps. </p>
<p>As a beginner, the knowledge I acquired was mainly from tutorials I had watched which basically covered how to <code>GET</code>, <code>POST</code> and <code>DELETE</code> data. I didn't know that there were rules that I should follow while working with APIs. </p>
<p>But as time progressed I came to understand the importance of APIs and the major role they play in the daily applications of a developer's job.</p>
<p>Mastering the rules is an essential step in learning about APIs. Most applications today are made up of pieces of software, each of which needs to be tested in its own way. </p>
<p>There are always new and exciting ways to test an application – and we're not just talking about checking for bugs here. It's more about the app functionality.</p>
<p>As technology continues to advance, the trends related to how to test these applications will also change. </p>
<p>In this guide, we will be discussing the trends that apply to the testing of APIs, how they differ from other types of testing, tools we can leverage when it comes to testing APIs, and how you can stay on top of your game when it comes to API testing.</p>
<p>First, let's get an understanding of what we mean by APIs.</p>
<h2 id="heading-what-is-an-api">What is an API?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/i50d3cqyx3ijgcla2tc4.png" alt="APIs" width="600" height="400" loading="lazy"></p>
<p>APIs are designed for developers to use. They are, in essence, a coding tool that allows your application to communicate with other applications. APIs allow you to integrate third-party applications into your work or use your own data and processes in the cloud.</p>
<p>The functionality of any application is defined by the connection it has with the outside world. For this reason, connecting your application with APIs becomes very helpful. </p>
<p>In today's development process, APIs have become an essential part in web and mobile applications. They enable communication of different components of these systems and help you access data and services.</p>
<p>A very important thing to keep in mind is that when dealing with APIs, you need to be sure everything works properly and that you get the correct feedback before integrating them into your applications. That's why testing them is essential.</p>
<p>For this reason, a vital part of testing any type of software requires evaluating whether or not there's a way the app can be broken. In order to ensure a certain level of continuity when using an API in multiple ways simultaneously, you need to conduct API testing.</p>
<p>Now that you have an understanding of what an API and why testing it is important, let's clarify what exactly might we mean by this – is there more to the term testing?</p>
<h2 id="heading-what-is-api-testing">What is API Testing?</h2>
<p>API Testing is a process that checks that an API adheres to the given 
requirements. Achieving this goal is not an easy task and there are many ways in which a tester can go about doing it effectively. </p>
<p>You can conduct API testing manually or automatically, and it is often considered a part of integration testing. </p>
<ul>
<li>Manual API testing involves directly interacting with the API using tools like <a target="_blank" href="https://www.postman.com/">Postman</a>, <a target="_blank" href="https://apitester.org/">API Tester</a> or any other available online tools. </li>
<li>Automated API testing uses specialized software to send requests to the API and then compares the results to the expected behavior.</li>
</ul>
<p>API testing is important because it helps ensure that the various components of a system are working together as expected. By verifying the functionality of the API, you can be confident that the system as a whole will function as intended.</p>
<h2 id="heading-api-testing-tools">API Testing Tools</h2>
<p>You can use API testing tools to test the functionality of an API. They can help you test the performance of an API, as well as to check for security vulnerabilities. </p>
<p>There are a number of different API testing tools available, each with its own advantages and disadvantages. Some of the most popular API testing tools include SoapUI, Postman, Runscope, and API Tester. </p>
<h2 id="heading-api-testing-principles">API Testing Principles</h2>
<p>Having a set of standard rules is the best way to ensure the quality of your APIs and their implementations. You can apply these rules during testing, coding, and development, as well as in production. </p>
<p>Here are some principles that you should keep in mind:</p>
<ol>
<li>API testing should be a part of your continuous integration and delivery pipeline.</li>
<li>API tests should be easy to maintain and write.</li>
<li>A well-designed API will make your tests easier to write.</li>
<li>You should test at the boundary of your system.</li>
<li>Keep your tests small and focused.</li>
<li>Make sure your tests are deterministic.</li>
<li>Run your tests in parallel for speed.</li>
<li>Use the available and freely accessible tools to simplify API testing. </li>
</ol>
<h2 id="heading-how-to-start-api-testing">How to Start API Testing</h2>
<p>There are many different types of tests you can perform on your APIs:</p>
<ul>
<li>Functionality tests focus on ensuring that the API is able to perform its intended functions. </li>
<li>Performance tests measure the response times of the API and check for bottlenecks. </li>
<li>Security tests assess the vulnerability of the API to attack vectors such as SQL injection and cross-site scripting (XSS). </li>
</ul>
<p>To get started with API testing, you will need to have access to an application with an exposed API. You will also need to choose a method for sending requests to the API (manual or automated), and select a tool or framework for writing your tests (if using automated testing).</p>
<p>Once you have these things set up, you can begin writing your test cases and running them against the API.</p>
<h2 id="heading-api-testing-tips">API Testing Tips</h2>
<p>API testing can be a challenge, but regardless of the tools you decide to use here are some tips that can help:</p>
<ol>
<li>Make sure you have a clear understanding of the API before you start testing. Read the documentation and any other available materials. This will help you know what to expect and how the API should work. </li>
<li>Use API testing tools: This will give you a better understanding of how the API works and make it easier to find any issues. </li>
<li>Test all aspects of the API, including input validation, error handling, and security. These are all important factors in ensuring that your API is working correctly.</li>
<li>Keep your tests up-to-date as the API changes. This will help ensure that you catch any new issues that may arise. </li>
<li>Use mobile apps for API testing. Things are changing all the time these days, where many people are working remotely, and from their phones too. By using mobile apps you can increase your productivity, become more mobile, and work from anywhere in the world. A good example of such a tool is the <a target="_blank" href="https://apitester.org/">API Tester</a>. </li>
</ol>
<p>By following these tips, you can help make sure that your API testing is effective and efficient. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>API testing is a key part of ensuring the quality of your software. By following these highlighted rules you will be able to ensure your tests are functional, secure and have a reliable performance. </p>
<p>This will in turn help you improve the overall quality of your software applications and provide a better user experience.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React Unit Test Handbook + Redux Testing Toolkit ]]>
                </title>
                <description>
                    <![CDATA[ In this step-by-step tutorial, you'll learn how to easily start with Unit Tests in React. You'll learn how to write tests for Redux states as well as fundamental Redux slice tests using the React Testing Library and Jest. 🔐 Here's What We'll Cover ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-write-unit-tests-in-react-redux/</link>
                <guid isPermaLink="false">66bb8f42fce17a7d998852db</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ unit testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Matthes B. ]]>
                </dc:creator>
                <pubDate>Wed, 09 Nov 2022 15:32:10 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/11/pexels-scott-webb-1527893.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this step-by-step tutorial, you'll learn how to easily start with Unit Tests in React. You'll learn how to write tests for Redux states as well as fundamental Redux slice tests using the React Testing Library and Jest.</p>
<h2 id="heading-heres-what-well-cover"><strong>🔐 Here's What We'll Cover</strong></h2>
<ul>
<li>You'll see how easy it is to set up your first unit tests in React.</li>
<li>You'll improve your general React knowledge.</li>
<li>You'll get the hang of why Test Driven Development (TDD) is helpful for your coding workflow.</li>
</ul>
<h2 id="heading-prerequisites"><strong>📝</strong> Prerequisites</h2>
<ul>
<li>You should be familiar with the basic React workflow structure (including functional components and hooks).</li>
<li>You should have a basic knowledge of Redux (I'm using Redux Toolkit for this guide).</li>
<li>You don't need any prior knowledge about testing.</li>
<li>I'm using the <code>npm</code> installation approach, not the <code>yarn</code> one.</li>
</ul>
<h2 id="heading-the-objective"><strong>🎯</strong> The Objective</h2>
<p>While learning advanced concepts of React, you'll probably stumble across the topic of testing. Being able to work with automatic tests is also quite handy for any upcoming frontend developers. </p>
<p>However, as I myself was learning React, I had a hard time finding information about how to implement tests for libraries like Redux (even though it's a library I work with all the time). </p>
<p>Beyond that, I found that doing any component testing in React is basically unfeasible when you don't know how to work with the Redux library.</p>
<p>So I took some time to read the Redux documentation and went back and forth with it a bit. Then I decided to write a practical starter guide for unit testing in React, including Redux, to share what I learned. </p>
<p>Since I would like to take a modern approach, I'm also going to use the Redux Toolkit. We'll cover the Redux implementation in this guide.</p>
<h3 id="heading-what-well-cover">What we'll cover:</h3>
<p>To start off, I will provide some general information about tests before I directly go into creating the first general unit tests. </p>
<p>Next I'll give a quick overview of how to implement some Redux Toolkit logic. </p>
<p>Then we will work on some unit tests within an application which uses Redux Toolkit. For this step we will adjust our previously created tests to the new Redux environment.</p>
<p>This is a step-by-step guide. If you are new to tests, I recommend following this guide in order from top to bottom.</p>
<p>I have also created a <a target="_blank" href="https://github.com/Matthes-Baer/unit-test-redux-article-app">public GitHub repository</a> for this guide with some commentary. You can use that if you want to look something up without scrolling through this guide in its full length again.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#what-different-kinds-of-tests-are-there">What Different Kinds of Tests Are There?</a></li>
<li><a class="post-section-overview" href="#how-to-set-up-your-react-testing-environment">How to Set Up Your React Testing Environment</a></li>
<li><a class="post-section-overview" href="#heading-check-out-your-created-react-application">Check Out Your Created React Application</a></li>
<li><a class="post-section-overview" href="#how-to-create-your-first-unit-test">How to Create Your First Unit Test</a></li>
<li><a class="post-section-overview" href="#how-to-create-a-failing-test-on-purpose">How to Create a Failing Test on Purpose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-some-additional-tests">How to Create Some Additional Tests</a></li>
<li><a class="post-section-overview" href="#heading-how-to-perform-testing-with-the-react-redux-toolkit">How to Perform Testing with the React Redux Toolkit</a></li>
<li><a class="post-section-overview" href="#heading-outlook-for-advanced-testing">Outlook for Advanced Testing</a></li>
</ol>
<h2 id="heading-what-different-kinds-of-tests-are-there">📋 What Different Kinds of Tests Are There?</h2>
<p>This quick guide won't provide you with detailed theoretical knowledge about all the <a target="_blank" href="https://www.freecodecamp.org/news/types-of-software-testing/">different kinds of testing out there</a>. At this point, you should just understand that there are generally three kinds of tests:</p>
<ul>
<li>Unit Tests</li>
<li>Integration Tests</li>
<li>End-To-End Tests</li>
</ul>
<p>To put it in simple words: You can see these three types of tests as generally increasing in their complexity. </p>
<p>While <a target="_blank" href="https://www.freecodecamp.org/news/unit-tests-explained/">unit tests</a> cover single functions and components, integration tests typically focus on multiple functions and their connections to each other. <a target="_blank" href="https://www.freecodecamp.org/news/end-to-end-testing-tutorial/">End-to-end tests</a> are even more complex and give insights about multiple function and component structures. </p>
<p>There are other test concepts, but these three are the most important ones for web developers, for example.</p>
<p>Again, this is really put in simple words. But in this case it's sufficient to know that unit tests are basically the least complex tests out of these three. </p>
<p>It's also quite easy to work with unit tests as soon as you have a basic understanding of how testing in general works.</p>
<p>I would also like to quickly emphasize that there are mainly two ways of testing your application.</p>
<ul>
<li>Manual Testing</li>
<li>Automatic Testing</li>
</ul>
<p>Manual Testing is pretty much what you probably already do for all of your application which you create. When manually testing your application, you basically start your React application with <code>npm run start</code> and actually click on buttons to check if the corresponding function works.</p>
<p>Automatic Tests, on the other hand, are pretty much functions you create which automatically check your application to see if the respective steps work that you defined within these tests. This automatic kind of testing is extremely important for larger projects. </p>
<p>With this automatic approach, it's also way easier to scale your tests. In the end, you have a lot of tests which automatically test your whole application in a relatively short amount of time. These test can help you spot any potential errors which might have occurred during development. This would take much more time if you were to go back constantly to manually test your application.</p>
<p>Being able to work with automatic tests is also typically a big plus for your résumé as a frontend developer.</p>
<h2 id="heading-how-to-set-up-your-react-testing-environment">🔧 How to Set Up Your React Testing Environment</h2>
<p>In order to get a practical start, we are going to directly dive into our React application. </p>
<p>You will see that the setup of a testing environment is relatively easy in React – or, to be more precise, React does it all for you during the regular install setup.</p>
<p>Therefore, I'm creating a React application with the following line:</p>
<p><code>npx create-react-app &lt;name of your application&gt;</code></p>
<p>After this step, we need everything that should be added for using Redux in our React application:</p>
<ul>
<li><strong>React Redux:</strong> <code>npm install react-redux</code> (provides some mandatory hooks, for example)</li>
<li><strong>React Redux Toolkit:</strong> <code>npm install @reduxjs/toolkit</code> (provides logic for creating a store or slices, for example)</li>
</ul>
<p>It's worth mentioning that there also is the <strong>Redux core</strong> (<code>npm install redux</code>). But this is already part of the React Redux Toolkit installation, so we don't have to install it here too. </p>
<p>If you wanted to use React without the React Redux Toolkit, then you would have to separately reach out to the Redux core installation.</p>
<p>You can also create a new React application from scratch with <code>npx create-react-app my-app --template redux</code> which includes the React Redux Toolkit, the React core, React Redux, as well as a template from the React Redux Toolkit. </p>
<p>Choose this approach if you don't have any existing React applications, since it's probably more convenient.</p>
<p>Under the hood, you now have an application that uses the <strong>React Testing library</strong> combined with <strong>Jest</strong> (a testing framework). Together, they have pretty much everything that you will need for testing your React application. </p>
<p>You don't have to install anything else for this purpose. These tools come out of the box with a standard React installation.</p>
<h2 id="heading-check-out-your-created-react-application">🔎 Check Out Your Created React Application</h2>
<p>As you go into your newly created React application, you will find the folder and file structure you are likely familiar with. Besides others, there is the <code>App.js</code> file, which is created like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> logo <span class="hljs-keyword">from</span> <span class="hljs-string">'./logo.svg'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App-header"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{logo}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App-logo"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"logo"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
          Edit <span class="hljs-tag">&lt;<span class="hljs-name">code</span>&gt;</span>src/App.js<span class="hljs-tag">&lt;/<span class="hljs-name">code</span>&gt;</span> and save to reload.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">"App-link"</span>
          <span class="hljs-attr">href</span>=<span class="hljs-string">"https://reactjs.org"</span>
          <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span>
          <span class="hljs-attr">rel</span>=<span class="hljs-string">"noopener noreferrer"</span>
        &gt;</span>
          Learn React
        <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Within the <code>src</code> folder, you also have the file <code>App.test.js</code>. This file is actually a first test that came out of the box with React installation. This file is structured like this:</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> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;

test(<span class="hljs-string">'renders learn react link'</span>, <span class="hljs-function">() =&gt;</span> {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
  <span class="hljs-keyword">const</span> linkElement = screen.getByText(<span class="hljs-regexp">/learn react/i</span>);
  expect(linkElement).toBeInTheDocument();
});
</code></pre>
<p>Even without fully understanding what <code>render</code> or <code>screen</code> is, for example, we can see that something is going on with our <code>App</code> component in there. In fact, this is a unit test that is focusing on a specific part of the <code>App</code> component.</p>
<p>While this first template for a test is a handy representation of what a test looks like, I would like to create a test file from scratch.</p>
<p>Generally speaking, tests are separated into different test suites. These test suites are typically a group of tests that focus on the same component, for example. Tests within the same test suite basically have the same superordinate topic.</p>
<p>To check this, try to enter <code>npm run test</code> in your terminal when you are within your React application. </p>
<p>It could say something like "There are no new tests or changes since the last commit" – in this case, just enter <code>a</code> in the terminal to run all tests regardless.</p>
<p>In the end, you should be able to see this within the terminal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/image-124.png" alt="Image" width="600" height="400" loading="lazy">
<em>Result of <code>npm run test</code></em></p>
<p>At the top, you can see that the <code>App.test.js</code> file passed. Basically, all tests within this file were successful. </p>
<p>Below that, you can see <code>renders learn react link</code>: This is the description for this particular test, which we can define individually. We will get back to this later.</p>
<p>Further down, we can finally see the test suites and tests. As you can see, we have one test suite and one test. To be more precise, we have one test suite that includes this one test. </p>
<p>Later on, you will recognize that we will use like 1-3 test suites while there will be around 5+ tests, for example. Again, test suites basically provide a structure that groups single tests together.</p>
<p>The stuff with the snapshots is not important for your specific case.</p>
<p><a target="_blank" href="https://jestjs.io/docs/snapshot-testing">Snapshots</a> are an advanced concept for testing. So a reference snapshot (like an image that was taken) is being compared with the version after some actions took place. This can help to check whether the UI stays the same after some actions or if some changes happened all of a sudden.</p>
<p>I won't focus on testing with snapshots in this article. This is a topic that you might want to look up after understanding some unit testing basics.</p>
<h2 id="heading-how-to-create-your-first-unit-test">🔨 How to Create Your First Unit Test</h2>
<p>Now that we've looked at a unit test, lets dive into the first test which we'll build from scratch on our own.</p>
<p>For that, I would like to create a new folder called <code>__tests__</code>. This is common when you are working with tests or checking out other applications. </p>
<p>I'm also dragging the already-available <code>App.test.js</code> file into this folder. This doesn't change anything about the result.</p>
<p>Our folder structure now looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/image-125.png" alt="Image" width="600" height="400" loading="lazy">
<em>general folder structure with <code>__tests__</code></em></p>
<p>Within <code>__tests__</code>, we create the file <code>myFirstTesting.test.js</code>. We need this file structure of <code>&lt;test name&gt;.test.js</code>. You can also create a test file with <code>&lt;test name&gt;.spec.js</code> – both approaches will work the same.</p>
<p>Our first step is to import the <code>App.js</code> component: <code>import App from "../App";</code>.</p>
<p>To create our first test, we have to make use of the <code>test()</code> function. You could also use <code>it()</code>. Both will achieve the same result. </p>
<p>The first parameter of this function has to be a string, which describes what we are going to test (remember the stuff with "renders learn react link" within the test file we viewed?). This is going to help you have a more precise overview after running all the tests. </p>
<p>In this case, I will use the description <code>"renders logo in App component"</code>. The second parameter is another function for which we are using an anonymous arrow function. Our <code>myFirstTesting.test.js</code> file now looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;

test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {

})
</code></pre>
<p>Even though there is not much going on, let's try entering <code>npm run test</code> again. We will find the following result in our terminal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/image-126.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Therefore, we now have two test files, resulting in two test suites and two tests.</p>
<p>Now we would actually like to test something. Since we added the description <code>"renders logo in App component"</code>, we are going to test exactly that. </p>
<p>In order to do that, we need the <code>render()</code> function, which we'll use whenever we actually want to render a component from our application.</p>
<p>In order to add the <code>render()</code> function, we have to import it from the React Testing library, which is already part of our React application without any other installations. </p>
<p>While we're at this step of importing, let's also import <code>screen</code> (also part of the React Testing library). It provides access to different functions that will look through the current screen after something gets rendered and find specific elements, for example.</p>
<p>After adding these two imports, our <code>myFirstTesting.test.js</code> file now looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;

test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {

})
</code></pre>
<p>Now that we have all that, let's actually start working on our test.</p>
<p>First of all, we need to render our component. Remember that tests are self-contained and don't know that we have an <code>App.js</code> with the corresponding content in our React application. We have to individually tell the specific test that a component exists by rendering it with <code>render()</code> at the top of the test. This is going to look like this: <code>render(&lt;App /&gt;);</code>.</p>
<p>Now that we have rendered the <code>App.js</code> component in this test, we should try to check if a specific content part can be found by the test. This way, we can actually test if <code>App.js</code> was rendered like it was supposed to be. </p>
<p>Assuming something went wrong, we would not be able to find the React logo, for example, which is currently part of the <code>App.js</code> component.</p>
<p>So we will try to find this logo, which is an <code>img</code> element. To do this, we can make use of the <code>getByAltText()</code> function that finds an element by its specific <code>alt</code> attribute, which is commonly utilized for images. We have access to this function with <code>screen</code> that we imported earlier. </p>
<p>We now have this expression: <code>screen.getByAltText("logo")</code>. So the test looks at the screen where we rendered the <code>App.js</code> component beforehand, and then gets an element, which has an <code>alt</code> attribute of <code>"logo"</code>. We will connect all this to a variable. </p>
<p>Our test file now looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;

test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
  <span class="hljs-keyword">const</span> image = screen.getByAltText(<span class="hljs-string">"logo"</span>);
})
</code></pre>
<p>There are a bunch of different functions like <code>getByAltText()</code> that you can use to look for elements with a specific text content, a specific role like a button, or even with a test id that you can add to the actual element.</p>
<p>You also have the opportunity to look for multiple elements. Besides that, you don't have to use a string as parameter. A regular expression with <code>/logo/i</code> is also feasible, for example. We will use different ways to find elements throughout this starter guide.</p>
<p>For the last step, we have to utilize <code>expect()</code>, which we use to see what behavior we can expect. In this case, we expect that our <code>image</code> variable is part of the component and therefore exists. </p>
<p>For this approach, our file would look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;

test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
  <span class="hljs-keyword">const</span> image = screen.getByAltText(<span class="hljs-string">"logo"</span>);
  expect(image).toBeInTheDocument();
})
</code></pre>
<h2 id="heading-how-to-create-a-failing-test-on-purpose">❗ How to Create a Failing Test on Purpose</h2>
<p>If we now run our tests with <code>npm run test</code>, everything will pass. Now, let's try to reverse this logic so that we actually create a failing test. This way, we can check if this test actually has some impact or not. </p>
<p>To do this, we can go into our <code>App.js</code> file and change the <code>alt</code> attribute for the logo image. If you change it to <code>alt="loo"</code>, the test will fail and it'll give you some information.</p>
<p>In our case, though, I would like to change something on the test itself to make it fail and show you another expression that is handy to know. Instead of <code>expect(image).toBeInTheDocument();</code> we can also type <code>expect(image).not.toBeInTheDocument();</code>. So here we added a <code>not</code>. This basically reversed the logic, and now the test expects that no such image element exists.</p>
<p>If we now try to run the test, we will find the following error message in our terminal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/image-127.png" alt="Image" width="600" height="400" loading="lazy">
<em>The error message we get</em></p>
<p>You can see that the test expected that there was no such element as <code>image</code>. However, it found something and therefore answered with an error message.</p>
<p>You don't have to make all your tests purposely fail to check if they work or not. I just wanted to show you what an actual failing test would look like.</p>
<h2 id="heading-how-to-create-some-additional-tests">✏️ How to Create Some Additional Tests</h2>
<p>Now we have finished with our first test and have some basic knowledge about what to expect when working with unit tests. Next, we will check out some other test examples.</p>
<p>In order to create a more realistic scenario, I will add one additional component, which we'll insert into the <code>App.js</code> component.</p>
<p>For this step, first we create a folder called <code>components</code> in our <code>src</code> folder. This is not a must, but it is common to structure your files like that.</p>
<p>Within the <code>components</code> folder, we create <code>List.js</code>. Our folder structure now looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/image-155.png" alt="Image" width="600" height="400" loading="lazy">
<em>current folder structure</em></p>
<p>Now, let's try to follow more of a test driven development (TDD) workflow, which is quite modern. I'm not necessarily sayint that this is always recommended. But a TDD approach is considered best practice by more and more people nowadays. </p>
<p>Of course, in this tutorial we'lre "only" talking about unit tests and not integration or end-to-end tests, but the general TDD workflow is similar for all three test categories.</p>
<p>So using this test driven development approach, we basically add tests and work on our application simultaneously. </p>
<p>To be more precise, we even create tests for single components and function parts before you even implement this tested logic in your application. </p>
<p>So there is a lot of going back and forth instead of creating all the tests at once at the end.</p>
<h3 id="heading-how-to-start-the-setup-for-listjs">How to Start the Setup for <code>List.js</code></h3>
<p>In our example, we have added the <code>List.js</code> component. Within this component, I would like to add a list with a button. When a user clicks on the button, it adds something to the list (an object with multiple keys and values).</p>
<p>In order to have some sort of frame, I will first add some <code>div</code> elements and similar stuff to our <code>List.js</code> component before we dive into the actual logic.</p>
<p>The <code>List.js</code> component now looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> List = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
      <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginLeft:</span> "<span class="hljs-attr">auto</span>", <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">500px</span>", <span class="hljs-attr">marginBottom:</span> "<span class="hljs-attr">500px</span>" }}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>This is a list<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">listStyleType:</span> "<span class="hljs-attr">none</span>" }}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>This is the first list entry<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 class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>This button can add a new entry to the list<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>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> List;
</code></pre>
<p>I also added the <code>List.js</code> component as a child to <code>App.js</code> (below all the other stuff in <code>App.js</code>) so it will be visible without changing anything else.</p>
<p>The result looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/image-156.png" alt="Image" width="600" height="400" loading="lazy">
<em>How it looks</em></p>
<p>This won't win you any style competitions but it's sufficient for our case.</p>
<h3 id="heading-setup-for-the-test-for-listjs">Setup for the test for <code>List.js</code></h3>
<p>Since we want to test while we are working on our component, I will now jump directly to the testing part even if nothing really happened in our <code>List.js</code> component in terms of click functions, for example.</p>
<p>We could create a new test file, but I would like to show you a new function we can use for our test suites specifically. This function is called <code>describe()</code> and can be handy for further structuring our tests.</p>
<p>To use <code>describe()</code>, we jump to <code>myFirstTesting.test.js</code> within <code>__tests__</code>. Right now, this file basically serves as one test suite for the test we specifically created for the <code>App.js</code> component. But I would like to have two test suites within this test file: one for the <code>App.js</code> tests and one for the <code>List.js</code> tests.</p>
<p>For this step, I'm using the <code>describe()</code> function, which basically works like the <code>test()</code> function in terms of parameters. </p>
<p>The first parameter will be a string, describing the respective test suite. The second parameter is a function, which then includes our <code>test()</code> functions with their stuff.</p>
<p>It will look like this in our case:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;

describe(<span class="hljs-string">"App.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> image = screen.getByAltText(<span class="hljs-string">"logo"</span>);
    expect(image).toBeInTheDocument();
  });
});

describe(<span class="hljs-string">"List.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"example"</span>, <span class="hljs-function">() =&gt;</span> {});
});
</code></pre>
<p>Before we jump into this new test, I would actually like to add something for the <code>App.js</code> testing. Since we have this <code>describe()</code> block, we could just add a new <code>test()</code> function – and this is what I'm going to do.</p>
<p>See the following newly added test described with <code>"renders List.js component in App.js"</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;

describe(<span class="hljs-string">"App.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> image = screen.getByAltText(<span class="hljs-string">"logo"</span>);
    expect(image).toBeInTheDocument();
  });

   test(<span class="hljs-string">"renders List.js component in App.js"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> textInListJS = screen.getByText(<span class="hljs-regexp">/This is a list/i</span>);
    expect(textInListJS).toBeInTheDocument();
  });
});

describe(<span class="hljs-string">"List.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"example"</span>, <span class="hljs-function">() =&gt;</span> {});
});
</code></pre>
<p>So I'm rendering the <code>App.js</code> component and looking for text via a regular expression, which is part of the <code>List.js</code> component. This test can basically be understood as a render test for <code>List.js</code>. If <code>List.js</code> had not been able to be rendered within <code>App.js</code>, this test would not pass.</p>
<p>If you are confused that this works without separately using <code>render()</code> on the <code>List.js</code> component, remember that <code>List.js</code> is part of <code>App.js</code> and everything inside <code>App.js</code> will be rendered under typical conditions. If you tried to look for a text phrase that doesn't exist in <code>List.js</code>, this new test would fail. Right now, in our case, it passes.</p>
<p>I would also like to emphasize that you can have multiple <code>expect()</code> functions within the same test. Therefore, we also could have structured this new test like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;

describe(<span class="hljs-string">"App.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> image = screen.getByAltText(<span class="hljs-string">"logo"</span>);
    <span class="hljs-keyword">const</span> textInListJS = screen.getByText(<span class="hljs-regexp">/This is a list/i</span>);

    expect(image).toBeInTheDocument();
    expect(textInListJS).toBeInTheDocument();
  });
});

describe(<span class="hljs-string">"List.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"example"</span>, <span class="hljs-function">() =&gt;</span> {});
});
</code></pre>
<p>This would also work perfectly fine in our case. And this can be handy in situations where you test for some small elements which are directly connected to each other and have the same requirements to be rendered. </p>
<p>But keep in mind that in our case, we should have adjusted the description for this test. That's because <code>"renders logo in App component"</code> is not correct anymore if we are testing more than that in this test. So let's head back to the structure with two separate tests for now. But have in mind that you are able to work like this.</p>
<h3 id="heading-back-to-the-test-for-listjs">Back to the test for <code>List.js</code></h3>
<p>Now I would like to work with the second <code>describe()</code> block that we created a few moments ago, where we want to work with tests specifically for the <code>List.js</code> component.</p>
<p>Since we are aiming for a test-driven development approach, we should think about what we are going to build, write a test, and then implement that logic in our component.</p>
<p>We want to create a simple list in our <code>List.js</code> component. So there will be an array, which we will go through with <code>map()</code>. </p>
<p>For this approach, we will utilize the <code>useState()</code> hook so we have a state that can dynamically adjust (the array of list items). Our first test will be to check if the length of this array in its initial state is equal to <code>1</code>.</p>
<p>To find the items within this state, we will make use of the <code>getAllByTestId()</code> method, which allows us to search for specific elements we marked with a <code>data-testid</code> in the frontend. </p>
<p>The test with the description <code>"renders initial state of listData state"</code> that I created is now included:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;

describe(<span class="hljs-string">"App.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> image = screen.getByAltText(<span class="hljs-string">"logo"</span>);
    <span class="hljs-keyword">const</span> textInListJS = screen.getByText(<span class="hljs-regexp">/This is a list/i</span>);

    expect(image).toBeInTheDocument();
    expect(textInListJS).toBeInTheDocument();
  });
});

describe(<span class="hljs-string">"List.js component"</span>, <span class="hljs-function">() =&gt;</span> {
   test(<span class="hljs-string">"renders initial state of listData state"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">List</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> list = screen.getAllByTestId(<span class="hljs-string">"list-item"</span>);
    expect(list.length).toEqual(<span class="hljs-number">1</span>);
  });
});
</code></pre>
<p>Right now this test will fail, of course, because we haven't added any of this logic to the component yet.</p>
<p>So I adjusted the <code>List.js</code> component. It now looks like this:</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">const</span> List = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> initialState = [
    {
      <span class="hljs-attr">id</span>: <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getSeconds()}</span>`</span>,
      <span class="hljs-attr">description</span>: <span class="hljs-string">"This is something"</span>,
      <span class="hljs-attr">significance</span>: <span class="hljs-number">7</span>,
    },
  ];
  <span class="hljs-keyword">const</span> [listData, setListData] = useState(initialState);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
      <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginLeft:</span> "<span class="hljs-attr">auto</span>", <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">500px</span>", <span class="hljs-attr">marginBottom:</span> "<span class="hljs-attr">500px</span>" }}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>This is a list<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">listStyleType:</span> "<span class="hljs-attr">none</span>" }}&gt;</span>
        {listData.map((listItem) =&gt; {
          return (
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{listItem.id}</span> <span class="hljs-attr">data-testid</span>=<span class="hljs-string">"list-item"</span>&gt;</span>
              {listItem.description}
            <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 class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>This button can add a new entry to the list<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>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> List;
</code></pre>
<p>Newly added was the <code>listData</code> state array via a <code>useState()</code> hook as well as an initialState, which I initialized with one object at the very top. I also made use of the <code>map()</code> function to go through this <code>listData</code> to create a list. </p>
<p>For each <code>&lt;li&gt;</code> element, I'm adding a key and a <code>data-testid</code>. This <code>data-testid</code> is the id we need for our test to find the respective elements.</p>
<p>On our actual application, we can see the <code>listItem.description</code> for this initial state:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/image-165.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>So by manually testing (actually looking at our application in the browser), we can see that this should work. If we now run our tests, we will also see that the test we created passed.</p>
<h3 id="heading-how-to-add-an-object-to-the-state">How to add an object to the state</h3>
<p>Now let's test something more exciting: the logic to add a new object to this <code>listData</code> state. Again, we will start by working on our test first before actually implementing the required logic within the React component.</p>
<p>With this newly added test described by <code>"adds a new data entry to listData after button click"</code>, our test file now looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> List <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/List"</span>;
<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>;

describe(<span class="hljs-string">"App.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"renders logo in App component"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> image = screen.getByAltText(<span class="hljs-string">"logo"</span>);
    expect(image).toBeInTheDocument();
  });

  test(<span class="hljs-string">"renders List.js component in App.js"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> textInListJS = screen.getByText(<span class="hljs-regexp">/This is a list/i</span>);
    expect(textInListJS).toBeInTheDocument();
  });
});

describe(<span class="hljs-string">"List.js component"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"renders initial state of listData state"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">List</span> /&gt;</span></span>);
    <span class="hljs-keyword">const</span> list = screen.getAllByTestId(<span class="hljs-string">"list-item"</span>);
    expect(list.length).toEqual(<span class="hljs-number">1</span>);
  });

  test(<span class="hljs-string">"adds a new data entry to listData after button click"</span>, <span class="hljs-function">() =&gt;</span> {
    render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">List</span> /&gt;</span></span>);
    <span class="hljs-keyword">let</span> listItems = screen.getAllByTestId(<span class="hljs-string">"list-item"</span>);
    <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">/This button can add a new entry to the list/i</span>,
    });

    expect(list.length).toEqual(<span class="hljs-number">1</span>);
    userEvent.click(button);
    list = screen.getAllByTestId(<span class="hljs-string">"list-item"</span>);
    expect(list.length).toEqual(<span class="hljs-number">2</span>);
  });
});
</code></pre>
<p>At the bottom, you can see this test. Therefore, we are first rendering the <code>List.js</code> component before looking for all available list items we assigned a test id to. You will see exactly where we put the test id in a few moments.</p>
<p>We also have to look for the button that we want to test to see if clicking on it adds something to the list. We do this with <code>getByRole()</code> which expects a role like <code>"button"</code> or <code>"table"</code> as a first parameter, for example (there are a bunch of different roles you can target). The second parameter is optional and is an object that can receive a value for the <code>name</code> key.</p>
<p><code>name</code> is pretty much the text content we have specifically for the button in this case. This optional second parameter is handy when you have multiple elements of type <code>"button"</code> in your component and want to get a specific button out of these.</p>
<p>After getting the <code>listItems</code> as well as the <code>button</code>, we start off with a first <code>expect()</code> to basically test the initial state. In this initial state, we expect to have only one list item.</p>
<p>Then, with the help of <code>userEvent</code>, we are going to click on the button. You could also use <code>fireEvent</code> for this situation (<code>userEvent</code> is still pretty new compared to the <code>fireEvent</code> approach). Both will work, and both are helpful for any action where you want to interact with specific elements. In this case, I want to simulate clicking on a button.</p>
<p>Tests generally follow a "arrange -&gt; act -&gt; assert" pattern that you can follow to structure them. Within the "arrange" part, you initialize and get all required elements. With the "act" part, you would simulate a mouse click (as in our case), for example. With "assert," you are checking if it all behaves like you expected it to.</p>
<p>In another case, you could simulate changing the value of an input field with <code>fireEvent.change(inputField, { target: { value: someValueVariable } })</code>, for example. Maybe you want to focus an input field or even drag an element - such actions can be simulated via <code>fireEvent</code> and <code>userEvent</code>.</p>
<p>After the button click, it again looked for all <code>listItems</code> since the current value of this variable would still be <code>1</code> from the previous initialization. As soon as this step is completed, it uses another <code>expect()</code> function to check if the length of the <code>listItems</code> array is now equal to <code>2</code> and not <code>1</code>.</p>
<p>Now that we have our test logic, let's jump back to the <code>List.js</code> component and implement the corresponding logic:</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">const</span> List = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> initialState = [
    {
      <span class="hljs-attr">id</span>: <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getSeconds()}</span>`</span>,
      <span class="hljs-attr">description</span>: <span class="hljs-string">"This is something"</span>,
      <span class="hljs-attr">significance</span>: <span class="hljs-number">7</span>,
    },
  ];
  <span class="hljs-keyword">const</span> [listData, setListData] = useState(initialState);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
      <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginLeft:</span> "<span class="hljs-attr">auto</span>", <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">500px</span>", <span class="hljs-attr">marginBottom:</span> "<span class="hljs-attr">500px</span>" }}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>This is a list<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">listStyleType:</span> "<span class="hljs-attr">none</span>" }}&gt;</span>
        {listData.map((listItem) =&gt; {
          return (
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{listItem.id}</span> <span class="hljs-attr">data-testid</span>=<span class="hljs-string">"list-item"</span>&gt;</span>
              {listItem.description}
            <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 class="hljs-tag">&lt;<span class="hljs-name">button</span>
        <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span>
          setListData([
            ...listData,
            { id: 999, description: "999", significance: 100 },
          ])
        }
      &gt;
        This button can add a new entry to the list
      <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>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> List;
</code></pre>
<p>The only part that changed is the button at the bottom of this file. So I added a function that gets invoked when clicking on this button. The function then adjusts the current state of <code>listData</code> which is responsible for rendering our list. I copied the current state with a spread operator and then added another hard-coded object as the new entry for this list. </p>
<p>Of course, there are more creative ways to fill in the values for the <code>id</code>, <code>description</code>, and <code>significance</code> keys.</p>
<p>I would also like to emphasize that you have the opportunity to create a separate function outside of the <code>return()</code> and access this function like this: <code>onClick={separateFunctionToAddObjectToState}</code> on the same button element. This would also work without having to render something additional within the test.</p>
<p>If we now run our test, we will see that it passes. If you try to still expect a length of <code>1</code> after clicking on the button, the test will fail like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/image-192.png" alt="Image" width="600" height="400" loading="lazy">
<em>error alert for length of 1</em></p>
<p>So it actually does what it is supposed to do.</p>
<h2 id="heading-setup-for-redux">🔧 Setup for Redux</h2>
<p>After working with local states via the <code>useState()</code> hook, I would like to work on the same files and adjust them for Redux (or the Redux Toolkit, to be precise). </p>
<p>I'm not going to dive deep into what Redux actually is and what every term like <code>action</code>, <code>store</code>, or <code>reducer</code> means in detail – since this would be worthy of a whole new guide. If you want that, you can <a target="_blank" href="https://www.freecodecamp.org/news/redux-for-beginners/">read this guide to Redux basics</a>.</p>
<p>Instead, I will give just a quick rundown and show which files I'm adding and editing. Then I'll talk about how to handle the <code>render()</code> method, including the Redux store provider, which can cause a lot of frustration when testing if you don't know about it.</p>
<h3 id="heading-overall-folder-structure">Overall folder structure:</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/image-14.png" alt="Image" width="600" height="400" loading="lazy">
<em>current overall folder structure with the React Redux Toolkit</em></p>
<p>You can see that I added an <code>app</code> (for the store) as well as a <code>features</code> (for the slice) folder.</p>
<h3 id="heading-updated-indexjs-file">Updated <code>index.js</code> file:</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App"</span>;
<span class="hljs-keyword">import</span> reportWebVitals <span class="hljs-keyword">from</span> <span class="hljs-string">"./reportWebVitals"</span>;
<span class="hljs-keyword">import</span> { Provider } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-redux"</span>;
<span class="hljs-keyword">import</span> store <span class="hljs-keyword">from</span> <span class="hljs-string">"./app/store"</span>;

<span class="hljs-keyword">const</span> root = ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>));
root.render(
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Provider</span> <span class="hljs-attr">store</span>=<span class="hljs-string">{store}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Provider</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>
);
</code></pre>
<p>You can see that I added a <code>provider</code> and wrapped it around the application so we have access to the store from anywhere.</p>
<h3 id="heading-created-storejs-file">Created <code>store.js</code> file:</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { configureStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reduxjs/toolkit"</span>;
<span class="hljs-keyword">import</span> { ListSlice } <span class="hljs-keyword">from</span> <span class="hljs-string">"../features/ListSlice"</span>;

<span class="hljs-keyword">const</span> store = configureStore({
  <span class="hljs-attr">reducer</span>: {
    <span class="hljs-attr">listReducers</span>: listSlice.reducer,
  },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> store;
</code></pre>
<p>In this file, we have created the required store for the Redux implementation.</p>
<h3 id="heading-created-listslicejs-file-in-features-folder">Created <code>ListSlice.js</code> file in <code>features</code> folder:</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { createSlice } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reduxjs/toolkit"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> initialState = {
  <span class="hljs-attr">value</span>: [
    {
      <span class="hljs-attr">id</span>: <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getSeconds()}</span>`</span>,
      <span class="hljs-attr">description</span>: <span class="hljs-string">"This is something"</span>,
      <span class="hljs-attr">significance</span>: <span class="hljs-number">7</span>,
    },
  ],
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ListSlice = createSlice({
  <span class="hljs-attr">name</span>: <span class="hljs-string">"listReducers"</span>,
  initialState,
  <span class="hljs-attr">reducers</span>: {},
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { } = ListSlice.actions;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ListSlice.reducer;
</code></pre>
<p>Here we have created the slice that we added to the store. Notice that I haven't added any reducer yet. This slice just contains the current corresponding state.</p>
<h3 id="heading-updated-listjs-file-in-components-folder">Updated <code>List.js</code> file in <code>components</code> folder:</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useSelector, useDispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-redux"</span>;

<span class="hljs-keyword">const</span> List = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> listState = useSelector(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.listReducers.value);
  <span class="hljs-keyword">const</span> dispatch = useDispatch(); <span class="hljs-comment">// not used right now</span>

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
      <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginLeft:</span> "<span class="hljs-attr">auto</span>", <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">500px</span>", <span class="hljs-attr">marginBottom:</span> "<span class="hljs-attr">500px</span>" }}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>This is a list<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">listStyleType:</span> "<span class="hljs-attr">none</span>" }}&gt;</span>
        {listState.map((listItem) =&gt; {
          return (
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{listItem.id}</span> <span class="hljs-attr">data-testid</span>=<span class="hljs-string">"list-item"</span>&gt;</span>
              {listItem.description}
            <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 class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>This button can add a new entry to the list<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>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> List;
</code></pre>
<p>On the frontend, we swapped the local state (using the useState hook) with the Redux state (using the useSelctor hook). You'll also see that I adjusted the button. There is no click function anymore (we will get back to that later on).</p>
<h2 id="heading-how-to-perform-testing-with-the-react-redux-toolkit">🔎 How to Perform Testing with the React Redux Toolkit</h2>
<p>Now that we have updated and created all the necessary files for the React Redux Toolkit logic, I would like to run a quick test of all the tests we previously created.</p>
<p>The result is that all tests have failed:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/image-15.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Keep in mind that I adjusted the button in <code>List.js</code>, for example, so the corresponding test was expected to fail. However, not all tests should have failed.</p>
<p>The test environments are working in their own world. They don't know if you wrap a provider somewhere in <code>index.js</code> and enable Redux logic. So the tests are still trying to make the rendering work without Redux. But our application now depends on Redux to manage our main state.</p>
<p>This means that we have to adjust the <code>render()</code> function so that this function is actually aligned with the Redux logic.</p>
<p>A method to make this work is to introduce a helper function, which we will store in a new folder called <code>utils</code>. The file will be called <code>utils-for-tests.jsx</code>. The content will look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { render } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;
<span class="hljs-keyword">import</span> { configureStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reduxjs/toolkit"</span>;
<span class="hljs-keyword">import</span> { Provider } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-redux"</span>;
<span class="hljs-comment">// As a basic setup, import your same slice reducers</span>
<span class="hljs-keyword">import</span> { ListSlice } <span class="hljs-keyword">from</span> <span class="hljs-string">"../features/ListSlice"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">renderWithProviders</span>(<span class="hljs-params">
  ui,
  {
    preloadedState = {},
    <span class="hljs-regexp">//</span> Automatically create a store instance if no store was passed in
    store = configureStore({
      reducer: { listReducers: ListSlice.reducer },
      preloadedState,
    }),
    ...renderOptions
  } = {}
</span>) </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Wrapper</span>(<span class="hljs-params">{ children }</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Provider</span> <span class="hljs-attr">store</span>=<span class="hljs-string">{store}</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">Provider</span>&gt;</span></span>;
  }

  <span class="hljs-comment">// Return an object with the store and all of RTL's query functions</span>
  <span class="hljs-keyword">return</span> { store, ...render(ui, { <span class="hljs-attr">wrapper</span>: Wrapper, ...renderOptions }) };
}
</code></pre>
<p>This code information can be found in the <a target="_blank" href="https://redux.js.org/usage/writing-tests">Redux documentation</a>. You can almost copy and paste it all for your application.</p>
<p>But you have to adjust the slices that are used in there. Since in our application there is only the <code>ListSlice</code> we don't have much to add. Just import that and update the content of the <code>configureStore()</code> function, like we managed it in our <code>store.js</code> file.</p>
<p>This step is necessary to basically mock the entire Redux logic and put it together into one new <code>render()</code> function.</p>
<p>With that, we can import this new function into our test files (<code>App.test.js</code> and <code>myFirstTesting.test.js</code>) and then replace all <code>render()</code> functions with <code>renderWithProviders()</code>. The <code>App.test.js</code> file, for example, now looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { screen } <span class="hljs-keyword">from</span> <span class="hljs-string">"@testing-library/react"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"../App"</span>;
<span class="hljs-keyword">import</span> { renderWithProviders } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/utils-for-tests"</span>;

test(<span class="hljs-string">"renders learn react link"</span>, <span class="hljs-function">() =&gt;</span> {
  renderWithProviders(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
  <span class="hljs-keyword">const</span> linkElement = screen.getByText(<span class="hljs-regexp">/learn react/i</span>);
  expect(linkElement).toBeInTheDocument();
});
</code></pre>
<p>There is not much more to do! If we now run our tests again (and comment out this one test, which is going to fail regardless because the button logic is not active anymore), it will work again.</p>
<h3 id="heading-slice-testing">Slice testing</h3>
<p>Another exciting part about testing with Redux is testing the slices. If you created your application with the React Redux Toolkit template, then you will be provided with some corresponding tests.</p>
<p>For our case, I also want to implement a new test file where we will specifically test <code>ListSlice.js</code> and its corresponding Redux logic.</p>
<p>For this slice, we have to import the slice and the corresponding reducers we want to test. To start, I will import the slice and test if it gets initialized with the initialState.</p>
<p>This is actually not the TDD approach since we already manually tested this part. Netherless, I would like to implement an automatic test as well:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> ListSlice, { initialState } <span class="hljs-keyword">from</span> <span class="hljs-string">"../features/ListSlice"</span>;

describe(<span class="hljs-string">"tests for ListSlice"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"initialize slice with initialValue"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> listSliceInit = ListSlice(initialState, { <span class="hljs-attr">type</span>: <span class="hljs-string">"unknown"</span> });
    expect(listSliceInit).toBe(initialState);
  });
});
</code></pre>
<p>Notice that I'm using <code>.spec</code> instead of <code>.test</code>. This doesn't matter. You can choose either. In this case, I'm going with <code>.spec</code> to remind you that this is also a viable option.</p>
<p>Also remember that we exported the initialState within our slice (see above). So we are able to import it here.</p>
<p>Other than that, we are already familiar with the <code>describe()</code> environment, which includes one <code>test()</code>. Within this test, I'm initializing a variable <code>listSliceInit</code>, which will hold the value we are receiving after the slice operation took place.</p>
<p>For this operation, we use <code>ListSlice</code> as a function and include the initial state as the first argument (in this case <code>initialState</code>). The second argument will be a reducer in most cases. </p>
<p>But in this case, we don't need to enter a reducer. Instead, we are using an object with <code>type: "unknown"</code>. This is basically telling the function that we don't want to perform any additional operations.</p>
<p>Therefore, <code>listSliceInit</code> should now include our state value, which includes an array with one entry. The corresponding test will pass.</p>
<p>To force a failure, I'm entering <code>expect(listSliceInit).toBe({ value: [] });</code> instead of the previous <code>expect()</code> function. So instead of our <code>initialState</code> we are expecting it to have an empty array. Now our test environment will tell us the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/image-24.png" alt="Image" width="600" height="400" loading="lazy">
<em>failing test</em></p>
<p>So it actually tells us what exactly it expected – in this case, it expected the <code>initialState</code>.</p>
<p>Next, I would like to test a reducer. However, we haven't added one yet. So I will adjust <code>ListSlice</code> in the <code>ListSlice.js</code> file like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ListSlice = createSlice({
  <span class="hljs-attr">name</span>: <span class="hljs-string">"listReducers"</span>,
  initialState,
  <span class="hljs-attr">reducers</span>: {
    <span class="hljs-attr">testAddReducer</span>: <span class="hljs-function">(<span class="hljs-params">state, action</span>) =&gt;</span> {
      state.value.push(action.payload);
    },
  },
});
</code></pre>
<p>Thus, I added <code>testAddReducer()</code>, which is responsible for pushing one additional element to the current state value, which it receives via an input from the dispatch (through <code>action.payload</code>).</p>
<p>If we now jump back to the <code>listSlice.spec.js</code> file, I'm adding another unit test:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> ListSlice, { initialState, testAddReducer } <span class="hljs-keyword">from</span> <span class="hljs-string">"../features/ListSlice"</span>;

describe(<span class="hljs-string">"tests for ListSlice"</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">"initialize slice with initialValue"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> listSliceInit = ListSlice(initialState, { <span class="hljs-attr">type</span>: <span class="hljs-string">"unknown"</span> });
    expect(listSliceInit).toBe(initialState);
  });

  test(<span class="hljs-string">"testAddReducer"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> testData = {
      <span class="hljs-attr">id</span>: <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getSeconds()}</span>`</span>,
      <span class="hljs-attr">description</span>: <span class="hljs-string">"This is for the test section"</span>,
      <span class="hljs-attr">significance</span>: <span class="hljs-number">5</span>,
    };

    <span class="hljs-keyword">const</span> afterReducerOperation = ListSlice(
      initialState,
      testAddReducer(testData)
    );

    expect(afterReducerOperation).toStrictEqual({
      <span class="hljs-attr">value</span>: [initialState.value.at(<span class="hljs-number">0</span>), testData],
    });
  });
});
</code></pre>
<p>I added the test for <code>testAddReducer</code>. You can see that I imported the reducer as well.</p>
<p>Firstly, I'm initializing a new variable, <code>testData</code>, which contains the data I would like to push to the current state.</p>
<p>After that, we follow the same structure as before with <code>afterReducerOperation</code>. But instead of this <code>type: "unknown"</code> stuff, we add the reducer as the second argument. This receives the <code>testData</code> as a parameter – basically like you would see it in a dispatch.</p>
<p>Then, we expect this <code>afterReducerOperation</code> variable to be strictly equal to the value of an array, which has two entries: <code>initialState.value.at(0)</code> (the first entry of our <code>initialState</code>) and <code>testData</code>. And this test will pass like we actually expected it.</p>
<p>If we are trying to enter some other entries or change the current ones, you would be able to see this test failing:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/image-25.png" alt="Image" width="600" height="400" loading="lazy">
<em>forced error: I added a third entry to the array</em></p>
<h3 id="heading-how-to-make-the-button-click-function-work-again">How to make the button click function work again</h3>
<p>Remember the button click within the <code>List.js</code> component (for adding something to the <code>listData</code> state) that wasn't working anymore after we changed to the Redux setup? Let's quickly update that to make that logic work within a Redux environment for the sake of completeness. Since we have the required reducer now, this will be an easy step.</p>
<p>To make the test work again, which added a new element to the state, we have to adjust it a little on the frontend to implement the Redux logic. (Previously we used the useState hook for a local state.)</p>
<p>For this step, we are making use of the <code>dispatch()</code> function in order to reach out to the <code>testAddReducer</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useSelector, useDispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-redux"</span>;
<span class="hljs-keyword">import</span> { testAddReducer } <span class="hljs-keyword">from</span> <span class="hljs-string">"../features/ListSlice"</span>;

<span class="hljs-keyword">const</span> List = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> listState = useSelector(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.listReducers.value);
  <span class="hljs-keyword">const</span> dispatch = useDispatch();

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
      <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginLeft:</span> "<span class="hljs-attr">auto</span>", <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">500px</span>", <span class="hljs-attr">marginBottom:</span> "<span class="hljs-attr">500px</span>" }}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>This is a list<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">listStyleType:</span> "<span class="hljs-attr">none</span>" }}&gt;</span>
        {listState.map((listItem) =&gt; {
          return (
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{listItem.id}</span> <span class="hljs-attr">data-testid</span>=<span class="hljs-string">"list-item"</span>&gt;</span>
              {listItem.description}
            <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 class="hljs-tag">&lt;<span class="hljs-name">button</span>
        <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span>
          dispatch(
            testAddReducer({
              id: `${new Date().getSeconds()}1`,
              description: "This is added",
              significance: 5,
            })
          )
        }
      &gt;
        This button can add a new entry to the list
      <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>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> List;
</code></pre>
<p>Besides the button logic, nothing else has changed in this file.</p>
<p>In the corresponding test (within <code>myFirstTesting.test.js</code> nothing has changed), if we now test everything – including this updated test – we will see that everything works fine:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/11/image-34.png" alt="Image" width="600" height="400" loading="lazy">
<em>final test run</em></p>
<p>And that's pretty much it for fundamental slice and general Redux unit testing!</p>
<h2 id="heading-outlook-for-advanced-testing">🔭 Outlook for Advanced Testing</h2>
<p>There are different topics like <a target="_blank" href="https://redux-toolkit.js.org/api/createAsyncThunk">thunks</a> (or <a target="_blank" href="https://redux-toolkit.js.org/rtk-query/overview">RTK Query</a> as an alternative) which could also be tested. But I'm considering this as an advanced topic, and it would take some more time to explain these processes.</p>
<p>If you are not aiming to be an expert in testing at this point, the topics we discussed for unit tests in Redux in this tutorial should be sufficient for you.</p>
<p>Generally speaking, I would recommend diving deeper into so-called mocks, spies, and also snapshots. These will be helpful if you are working on some other more advanced tests. </p>
<p>The stuff with <code>renderWithProvider()</code> is basically based on such a mock – there, we artificially created a store with reducers and a provider to create this new <code>render()</code> function. So mocks are especially helpful for any third-party libraries, for example. </p>
<p>As I said, though, mocks, spies, and snapshots are more of an advanced topic to wrap your head around.</p>
<h2 id="heading-further-learning-opportunities">📣 Further Learning Opportunities</h2>
<p>I recently started to work on my first <a target="_blank" href="https://www.udemy.com/user/matthes-bar/">free Udemy course</a>. While this first free course covers the basics of the React Redux Toolkit with German audio and manually added English subtitles, I'm also planning to publish other Udemy courses completely in English in the future. </p>
<p>I would really appreciate it if you would check out this cost-free course in order to provide me with some feedback.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test Your Express.js and Mongoose Apps with Jest and SuperTest ]]>
                </title>
                <description>
                    <![CDATA[ By Rakesh Potnuru Testing is a vital part of software development. The sooner you start testing, the better.  In this article, I'll show you how to write tests for your NodeJs/ExpressJS and MongoDB/Mongoose applications with Jest and Supertest. Let's... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-test-in-express-and-mongoose-apps/</link>
                <guid isPermaLink="false">66d460c9787a2a3b05af43f6</guid>
                
                    <category>
                        <![CDATA[ Express JS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Jest ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mongoose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 27 Sep 2022 23:26:16 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/09/how-to-write-tests.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Rakesh Potnuru</p>
<p>Testing is a vital part of software development. The sooner you start testing, the better. </p>
<p>In this article, I'll show you how to write tests for your NodeJs/ExpressJS and MongoDB/Mongoose applications with <strong>Jest</strong> and <strong>Supertest</strong>.</p>
<h2 id="heading-lets-get-started">Let's get started</h2>
<p>First let's set up a demo Express.js app.</p>
<p>Let's say we are building a backend REST API for an eCommerce application.</p>
<p>This app should:</p>
<ul>
<li>Get all the products</li>
<li>Get a product by id</li>
<li>Add product(s) to the database</li>
<li>Delete product(s) from the database</li>
<li>Update product information</li>
</ul>
<h2 id="heading-expressjs-app-set-up">Express.js App Set Up</h2>
<h3 id="heading-step-1-project-set-up">Step 1: Project set up</h3>
<p>First, create a folder and start a blank application with <code>npm</code>.</p>
<pre><code class="lang-bash">npm init
</code></pre>
<p>Fill all the details it asks for.</p>
<p>Then, install <code>express</code>, <code>mongoose</code>, <code>axios</code> and <code>dotenv</code> with the following command:</p>
<pre><code class="lang-bash">npm i express mongoose axios dotenv
</code></pre>
<p>Here's a link to the <a target="_blank" href="https://github.com/itsrakeshhq/jest-tests-demo/blob/a1725cb3379f78a03cf8d3d4cfa22127469e8b50/package.json">package.json</a> on my GitHub.</p>
<h3 id="heading-step-2-create-the-boilerplate">Step 2: Create the boilerplate</h3>
<p>Let's create all the folders and files and then fill them with some boilerplate code.</p>
<p>This is how your folder hierarchy should look:</p>
<pre><code class="lang-bash">.
├── controllers
│   └── product.controller.js
├── models
│   └── product.model.js
├── routes
│   └── product.route.js
├── package-lock.json
├── package.json
├── .env
├── app.js
└── server.js
</code></pre>
<p>Use these files' code by copying and pasting. Analyze the code and flow as best you can.</p>
<ul>
<li><code>[product.controller.js](https://github.com/itsrakeshhq/jest-tests-demo/blob/main/controllers/product.controller.js)</code></li>
<li><code>[product.model.js](https://github.com/itsrakeshhq/jest-tests-demo/blob/main/models/product.model.js)</code></li>
<li><code>[product.route.js](https://github.com/itsrakeshhq/jest-tests-demo/blob/main/routes/product.route.js)</code></li>
<li><code>[app.js](https://github.com/itsrakeshhq/jest-tests-demo/blob/main/app.js)</code> </li>
<li><code>[server.js](https://github.com/itsrakeshhq/jest-tests-demo/blob/main/server.js)</code></li>
</ul>
<h3 id="heading-step-3-database-setup">Step 3: Database setup</h3>
<p>I advise using two databases for a project—one for testing, the other for development. But just one database will be sufficient for learning purposes.</p>
<p>First, create a <a target="_blank" href="https://mongodb.com">MongoDB</a> account or log in.</p>
<p>Then create a new project. Give it a name and press the <strong>Next</strong> button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/Screenshot-2022-09-26-205148.png" alt="Naming the project" width="600" height="400" loading="lazy">
<em>Naming the project</em></p>
<p>Then click <strong>Create Project</strong> after that.</p>
<p>We must create a database in the following window by selecting a cloud provider, a location, and specs. So press <strong>Build a Database</strong> to get going.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/Screenshot-2022-09-26-205911.png" alt="Build a database" width="600" height="400" loading="lazy">
<em>Build a database</em></p>
<p>Choose "Shared" because it is sufficient for learning purposes. And then click <strong>Create</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/Screenshot-2022-09-26-211701.png" alt="Choose a deployment option" width="600" height="400" loading="lazy">
<em>Choose a deployment option</em></p>
<p>Next, select "aws" as your cloud provider and the region that is closest to you. Following your selection, click <strong>Create Cluster</strong>.</p>
<p>The cluster's formation will take some time. Create a user to access your database in the meanwhile.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/Screenshot-2022-09-26-212537.png" alt="Create Superuser" width="600" height="400" loading="lazy">
<em>Create Superuser</em></p>
<p>Choose "My Local Environment" because we are developing our application. You can then add an IP addresses. To conclude, click <strong>Close</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/Screenshot-2022-09-26-213347.png" alt="Add IP addresses" width="600" height="400" loading="lazy">
<em>Add IP addresses</em></p>
<p>You will receive a URI string after the database is set up, which we'll use to connect to the database. The string appears as follows:</p>
<pre><code class="lang-bash">mongodb+srv://&lt;YOUR_USERNAME&gt;:&lt;YOUR_PASSWORD&gt;@&lt;YOUR_CLUSTER_URL&gt;/&lt;DATABASE_NAME&gt;?retryWrites=<span class="hljs-literal">true</span>&amp;w=majority
</code></pre>
<p>Put this string in the <code>.env</code> file.</p>
<pre><code class="lang-bash">MONGODB_URI=your database string
</code></pre>
<p>Now we're ready to start testing our app.</p>
<h2 id="heading-how-to-write-tests-with-jest-and-supertest">How to Write Tests with Jest and SuperTest</h2>
<h3 id="heading-step-1-install-packages">Step 1: Install packages</h3>
<p>You need three npm packages to begin writing tests: <code>jest</code>, <code>supertest</code>, and <code>cross-env</code>. You can install them like this:</p>
<pre><code class="lang-bash">npm i jest supertest cross-env
</code></pre>
<ul>
<li><code>jest</code>: Jest is a framework for testing JavaScript code. Unit testing is the main usage of it.</li>
<li><code>supertest</code>: Using Supertest, we can test endpoints and routes on HTTP servers.</li>
<li><code>cross-env</code>: You can set environmental variables inline within a command using cross-env.</li>
</ul>
<h3 id="heading-step-2-add-test-script">Step 2: Add test script</h3>
<p>Open your <code>package.json</code> file and add the test script to the scripts.</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"cross-env NODE_ENV=test jest --testTimeout=5000"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>,
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"nodemon server.js"</span>
},
</code></pre>
<p>In this case, we're using <code>cross-env</code> to set environment variables, <code>jest</code> to execute test suites, and <code>testTimeout</code> is set to <code>5000</code> because certain requests might take a while to finish.</p>
<h3 id="heading-step-3-start-writing-tests">Step 3: Start writing tests</h3>
<p>First, create a folder called <code>tests</code> at the application's root, and then create a file there called <code>product.test.js</code>. Jest searches for the folder <code>tests</code> at the project's root when you do <code>npm run test</code>. As a result, you must place your test files in the <code>tests</code> folder.</p>
<p>Next, import the <code>supertest</code> and <code>mongoose</code> packages into the test file.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
<span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">"supertest"</span>);
</code></pre>
<p>Import <code>dotenv</code> to load environment variables, and import <code>app.js</code> as that is where our application starts.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
<span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">"supertest"</span>);
<span class="hljs-keyword">const</span> app = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../app"</span>);

<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();
</code></pre>
<p>You'll need to connect and disconnect the database before and after each test (because we don't require the database once testing is complete).</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/* Connecting to the database before each test. */</span>
beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> mongoose.connect(process.env.MONGODB_URI);
});

<span class="hljs-comment">/* Closing database connection after each test. */</span>
afterEach(<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> mongoose.connection.close();
});
</code></pre>
<p>Now you can write your first unit test.</p>
<pre><code class="lang-javascript">describe(<span class="hljs-string">"GET /api/products"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should return all products"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app).get(<span class="hljs-string">"/api/products"</span>);
    expect(res.statusCode).toBe(<span class="hljs-number">200</span>);
    expect(res.body.length).toBeGreaterThan(<span class="hljs-number">0</span>);
  });
});
</code></pre>
<p>In the above code, </p>
<ul>
<li>We use <code>describe</code> to describe the unit test. Even though it is not required, it will be useful to identify tests in test results.</li>
<li>In <code>it</code>, we write the actual test code. Tell what the test performs in the first argument, and then in the second argument, write a callback function that contains the test code.</li>
<li>In the callback function, the request is sent to the endpoint first, and the expected and actual responses are then compared. The test passes if both answers match, else, it fails. ✨ As simple as that ✨.</li>
</ul>
<p>You can write tests for all the endpoints in the same manner.</p>
<pre><code class="lang-javascript">describe(<span class="hljs-string">"GET /api/products/:id"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should return a product"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app).get(
      <span class="hljs-string">"/api/products/6331abc9e9ececcc2d449e44"</span>
    );
    expect(res.statusCode).toBe(<span class="hljs-number">200</span>);
    expect(res.body.name).toBe(<span class="hljs-string">"Product 1"</span>);
  });
});

describe(<span class="hljs-string">"POST /api/products"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should create a product"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app).post(<span class="hljs-string">"/api/products"</span>).send({
      <span class="hljs-attr">name</span>: <span class="hljs-string">"Product 2"</span>,
      <span class="hljs-attr">price</span>: <span class="hljs-number">1009</span>,
      <span class="hljs-attr">description</span>: <span class="hljs-string">"Description 2"</span>,
    });
    expect(res.statusCode).toBe(<span class="hljs-number">201</span>);
    expect(res.body.name).toBe(<span class="hljs-string">"Product 2"</span>);
  });
});

describe(<span class="hljs-string">"PUT /api/products/:id"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should update a product"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app)
      .patch(<span class="hljs-string">"/api/products/6331abc9e9ececcc2d449e44"</span>)
      .send({
        <span class="hljs-attr">name</span>: <span class="hljs-string">"Product 4"</span>,
        <span class="hljs-attr">price</span>: <span class="hljs-number">104</span>,
        <span class="hljs-attr">description</span>: <span class="hljs-string">"Description 4"</span>,
      });
    expect(res.statusCode).toBe(<span class="hljs-number">200</span>);
    expect(res.body.price).toBe(<span class="hljs-number">104</span>);
  });
});

describe(<span class="hljs-string">"DELETE /api/products/:id"</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">"should delete a product"</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app).delete(
      <span class="hljs-string">"/api/products/6331abc9e9ececcc2d449e44"</span>
    );
    expect(res.statusCode).toBe(<span class="hljs-number">200</span>);
  });
});
</code></pre>
<p>Then run <code>npm run test</code> to run the test suites (suite - test file).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/image-428.png" alt="Image" width="600" height="400" loading="lazy">
<em>Test results</em></p>
<p>And that's it! You now know how to test your Express/Mongoose apps with Jest and SuperTest.</p>
<p>Now go forth and create new tests for your apps. :)</p>
<p>If you have any questions, feel free to message me on <a target="_blank" href="https://twitter.com/rakesh_at_tweet">Twitter</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Software Testing? A Beginner's Guide ]]>
                </title>
                <description>
                    <![CDATA[ Software testing is essential to development. It saves you time and money in production mode.  But software testing is a complex topic and can be a bit difficult to understand. In this article, I'll explain the major topics in software testing and ho... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/software-testing-beginners-guide/</link>
                <guid isPermaLink="false">66c4c6501b22d2d8d9040ec8</guid>
                
                    <category>
                        <![CDATA[ beginner ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ unit testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sophia Iroegbu ]]>
                </dc:creator>
                <pubDate>Wed, 21 Sep 2022 16:03:49 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/09/Tech-Blog-Cover--4-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Software testing is essential to development. It saves you time and money in production mode. </p>
<p>But software testing is a complex topic and can be a bit difficult to understand.</p>
<p>In this article, I'll explain the major topics in software testing and how this practice can help you. </p>
<h3 id="heading-table-of-contents">Table of Contents:</h3>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-software-testing">What is Software Testing?</a></li>
<li><a class="post-section-overview" href="#heading-types-of-software-testing">Types of Software Testing</a></li>
<li><a class="post-section-overview" href="#heading-different-types-of-functional-software-testing">Different Types of Functional Software Testing</a></li>
<li><a class="post-section-overview" href="#heading-software-testing-principles">Software Testing Principles</a></li>
<li><a class="post-section-overview" href="#heading-why-is-software-testing-needed">Why is Software Testing needed?</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-what-is-software-testing">What is Software Testing?</h2>
<p>Software testing is the process of making sure your software/app works as it should. There are various methods you can use to test your code, and each testing method has different requirements. </p>
<p>For instance, unit testing involves writing test cases to ensure the code works as it should, and Beta testing consists of testing the preview version of the software or app to make sure users can use the product.</p>
<p>Software testing is integral to the process of building good software that works as it should. It also helps improve productivity and performance. Testing is an important part of the <em>Software Development Life Cycle</em> (SDLC). </p>
<p>Other benefits of testing your code include preventing bugs, reducing cost, and reducing time of development.</p>
<h2 id="heading-types-of-software-testing">Types of Software Testing</h2>
<p>There are two general types of software testing:</p>
<h3 id="heading-functional-testing">Functional Testing:</h3>
<p>Functional Testing is a software testing method that validates the system against the customer's requirements or specifications. </p>
<p>This type of testing aims to test each function of the software by providing the correct input and ensuring the output is right. </p>
<p>For examples, let's say you write a test case to test creating a user. The test case provides the correct input (email, first name, last name and password) and ensures the output (success message) is accurate, as well.</p>
<p>Functional testing checks that everything is functioning properly by emulating business scenarios based on applicable requirements.</p>
<h3 id="heading-non-functional-testing">Non-functional Testing:</h3>
<p>Non-functional testing is a software testing method that tests for end-user experiences, such as performance and reliability under load. This could either make or break a user experience. </p>
<p>When your code fails at non-functional testing, it may not cause an issue that user would note but it can flag a problem in the system.</p>
<p>Non-functional testing is just about testing the software to know how it responds to load on the system.</p>
<p>In this guide, we will focus on Functional Software Testing.</p>
<h2 id="heading-different-types-of-functional-software-testing">Different Types of Functional Software Testing</h2>
<p>There are different types of software testing, and each has a specific aim. We'll look at each one quickly now. </p>
<h3 id="heading-unit-testing">Unit Testing:</h3>
<p>Unit testing is a type of software testing that validates how each software unit performs and whether that specific piece of code does what it should. A unit is the smallest testable component of an application.</p>
<p>The aim is to confirm that each unit of software code works as expected. You do unit testing during the coding (development) stage or phase. Developers write these tests as they go. </p>
<p>Unit tests isolate possible bugs in your code and help you correct them. A unit could be a single function, method, procedure, module, or object.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/image-404.png" alt="Image" width="600" height="400" loading="lazy">
<em>Code Snippet of a unit test case in Python</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/image-403.png" alt="Image" width="600" height="400" loading="lazy">
<em>Code Snippet of a unit test case in Java</em></p>
<h3 id="heading-integration-testing">Integration Testing:</h3>
<p>Integration Testing is software testing which helps ensure that software components or functions work together properly. This is the second phase of the software testing process that comes after unit testing.</p>
<p>In this type of testing, units or individual software components are tested in groups. This testing method mainly focuses on exposing defects in interactions between integrated components and units.</p>
<h3 id="heading-system-testing">System Testing:</h3>
<p>System testing involves the process of testing integrated software. The aim is to evaluate the system's compliance with specify requirements. </p>
<p>In system testing, the quality assurance team evaluates how each component of the application or software work together in a full, integrated environment.</p>
<h3 id="heading-acceptance-testing">Acceptance Testing:</h3>
<p>Acceptance testing is a software testing method where a system is tested or checked for acceptability. It evaluates the system's compatibility with the business requirements and assesses whether it is acceptable for delivery.</p>
<p>It is also known as formal testing performed to fit user needs, requirements, and business processes. It determines if a system satisfies the standard business criteria and if users or customers will be able to accept it. </p>
<p>Acceptance testing is the last stage of software testing done after system testing and before making the system available for public use.</p>
<h3 id="heading-regression-testing">Regression Testing:</h3>
<p>Regression testing ensures that a component continues working as it should, after including additional components in the program. You perform regression testing when something changes, such as adding a new module to the program.</p>
<p>This type of testing represents the complete testing of executed test cases that are re-executed to ensure the current functionalities still work just fine.</p>
<h3 id="heading-alpha-testing-and-beta-testing">Alpha Testing and Beta Testing:</h3>
<p>Alpha testing is also known as initial validation testing. It is an aspect of acceptance testing done before the product is given to the consumers or users. QA (Quality Assurance) testers usually do this. Alpha testing is done internally by the QA team.</p>
<p>Beta testing is also known as second phase of validation testing. But this type of testing is done externally, which means the public does it. </p>
<p>The version of the code/software for this phase of testing is released to a limited number of users for testing in a real-time scenario. For instance, freeCodeCamp's math curriculum is available for beta testing <a target="_blank" href="https://www.freecodecamp.org/news/freecodecamp-foundational-math-curriculum/">here</a>.</p>
<h2 id="heading-software-testing-principles">Software Testing Principles</h2>
<p>Everything in tech has principles. These are guidelines to help you build better software and avoid errors.</p>
<p>Here are some software testing principles you should follow when writing tests for your code:</p>
<h3 id="heading-testing-aims-to-show-the-presence-of-defects-not-the-absence">Testing aims to show the presence of defects, not the absence:</h3>
<p>Software testing aims to spot software failures. This reduces the presence of faults and errors. </p>
<p>Software testing ensures defects are visible to the developer but doesn't guarantee defect-free software. Multiple types of testing can't even ensure error-free software. Testing can only decrease the number of errors.</p>
<h3 id="heading-exhaustive-testing-is-not-possible">Exhaustive testing is not possible:</h3>
<p>Exhaustive Testing is the process of testing software for all valid and invalid inputs and pre-conditions. </p>
<p>This method of testing is not realistic because test cases presume that the software is correct and it produces the correct output in every test case. If you truly try to test every aspect and test case in your software, it will take too much time and effort, and it's not practical.</p>
<h3 id="heading-perform-early-testing">Perform early testing:</h3>
<p>Testing your software at an early phase helps avoid minor bugs or errors. When you can spot errors at an early stage of the Software Development Life Cycle(SDLC), it's always less expensive. It is best to start software testing from the beginning of the project.</p>
<h3 id="heading-defect-clustering">Defect clustering:</h3>
<p>Defect clustering refers to when most of the problems you find occur in just a few parts of the application or software. If you can identify the modules or areas where these defects occur, you can focus most of your testing efforts on them. </p>
<p>Keep the Pareto Principle in mind when testing your code: 80% of software defects tend to come from 20% of the modules.</p>
<h3 id="heading-beware-of-the-pesticide-paradox">Beware of the Pesticide paradox:</h3>
<p>This principle is based on a theory – "the more you use pesticide on a crop, the more immune the crop will eventually grow, and the pesticide will not be effective." </p>
<p>When you repeat particular test cases over and over, you will see fewer and fewer new bugs. So to find new bugs, update your test cases and run them once you add new test cases.</p>
<h3 id="heading-testing-is-context-dependent">Testing is context-dependent:</h3>
<p>Testing is context-dependent, which means that you should test your software based on its needs, functionalities, and requirements.</p>
<p>Your test approach should depend on what your software does. Not every software needs the same type/method of testing because every application has its unique functionalities.</p>
<p>For instance, when testing an eCommerce web app, you will focus on its functionality to display products, so you will test how it shows products to end-users. When dealing with an API, you will focus on the response the API returns when an endpoint is called. </p>
<p>You wouldn't necessarily use the same test cases for both – that is what it means that testing is context-dependent.</p>
<h3 id="heading-the-absence-of-errors-is-a-fallacy">The absence of errors is a fallacy:</h3>
<p>If you build software that is 99% bug-free, but it doesn't follow user requirements, it is not usable for end-users. </p>
<p>Know that it is very much necessary that your 99% bug-free software still meets or fulfills your user requirements. It is important to write test cases to find errors in the code, but you also need to test your software for your end-users (with them and how they'll use it in mind). The best way to do this is to carry out beta testing.</p>
<h2 id="heading-why-is-software-testing-needed">Why is Software Testing Needed?</h2>
<p>Besides making sure your software is bug-free and meets user requirements, software testing has other advantages. </p>
<h3 id="heading-software-testing-improves-security">Software testing improves security:</h3>
<p>When building software, security is a crucial part of your planning. This is because vulnerable software could jeopardize you users and their information, as hackers can use stolen info for malicious purposes.</p>
<p>As a product undergoes testing, the end-user can count on the fact that they will be getting a reliable product and their details will be secured and safe. So users are more likely to get a product that is free from vulnerabilities with the help of software testing.</p>
<h3 id="heading-software-testing-improves-product-quality">Software testing improves product quality:</h3>
<p>You want your software or product to be bug-free, low-risk, and effective at what it should do. And you can achieve this by including test cases and other testing methods when building out the code.</p>
<p>In addition, you won't know how good your product is until you test it. This helps you provide the best product version before it gets released (and discover any inconsistencies or pain points along the way – so you can improve them).</p>
<h3 id="heading-software-testing-improves-customer-satisfaction">Software testing improves customer satisfaction:</h3>
<p>For instance, let's say you download a new app and try to use some of its functionality – but it shows an error. This will probably frustrate you, and you might not want to use the app again, right?</p>
<p>This is exactly why software testing is important. It can help you discover such errors and detect them before you release the product to the user, and gives the developers a chance to prevent the error. </p>
<p>By investing in software testing early in the development stage, you are letting the users know that you care about their experience. It could also help you create a solid long-term customer relationship.</p>
<h3 id="heading-software-testing-saves-money">Software testing saves money:</h3>
<p>Software testing can save you a lot of money – but how?</p>
<p>Each stage of development involves many things, such as clear communication and coordination between multiple teams, and each step has a laundry list of things that could go awry.</p>
<p>Catching those errors when the product is live is a horrible experience because you may have to handle PR, retasking fixes, and trying to sort the problem in real time.</p>
<p>In addition, your users won't be able to access the app while you're fixing it, which defeats the app's purpose and provides a bad user experience in the meantime. Software testing helps resolve this stress, and once live, your user can enjoy your app/product to the fullest.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, software testing is a crucial part of development. It can help save your team a lot of trouble, and it feels great to create a usable, bug-free product that users enjoy and recommend.</p>
<p>If software testing interests you, you can check freeCodeCamp's QA certificate course <a target="_blank" href="https://www.freecodecamp.org/learn/quality-assurance/#quality-assurance-and-testing-with-chai">here</a> to learn more about QA testing. QA testers are techies that focus on testing softwares and apps for errors. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Perform Integration Testing using JUnit 5 and TestContainers with SpringBoot ]]>
                </title>
                <description>
                    <![CDATA[ TestContainers is a library that helps you run module-specific Docker containers to simplify Integration Testing. These Docker containers are lightweight, and once the tests are finished the containers get destroyed. In the article we are going to un... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/integration-testing-using-junit-5-testcontainers-with-springboot-example/</link>
                <guid isPermaLink="false">66d460f437bd2215d1e245c9</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spring-boot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sameer Shukla ]]>
                </dc:creator>
                <pubDate>Fri, 26 Aug 2022 15:46:39 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/08/testcontainers-logo.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>TestContainers is a library that helps you run module-specific Docker containers to simplify Integration Testing.</p>
<p>These Docker containers are lightweight, and once the tests are finished the containers get destroyed.</p>
<p>In the article we are going to understand what the TestContainers is and how it helps you write more reliable Tests.</p>
<p>We are also going to understand the important components (Annotations and Methods) of the library which help you write the Tests.</p>
<p>Finally, we will also learn to write a proper Integration Test in SpringBoot using the TestContainers library and its components.</p>
<h2 id="heading-limitations-of-testing-with-an-h2-in-memory-database">Limitations of Testing with an H2 In-Memory Database</h2>
<p>The most common approach to Integration Testing today is to use an H2 in-memory database. But there are certain limitations to this method.</p>
<p>First of all, say we are using version 8.0 of MySQL in production, but our integration tests are using H2. We can never execute our tests for the database version running on Production.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-303.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>SpringBoot app with MySQL DB and H2</em></p>
<p>‌Secondly, the test cases are less reliable because in production we are using an altogether different database and the tests are pointing to H2. The application may run into issues in production, but the integration tests may succeed.</p>
<p>I was trying to access my RESTful service on local and faced this error:</p>
<p>“<strong>Caused by: org.postgresql.util.PSQLException: FATAL: database "example_db" does not exist</strong>”.</p>
<p>It happened because of a permission issue, but the tests on local worked fine.</p>
<p>And finally, as documented <a target="_blank" href="http://h2database.com/html/features.html#compatibility">here</a>, H2 is compatible with other databases only up to a certain point. There are few areas where H2 is incompatible. If you need to use “nativeQueries” in a SpringBoot application, for example, then using H2 may cause problems.</p>
<h2 id="heading-enter-the-testcontainers-library">Enter the TestContainers Library</h2>
<p>By using TestContainers we can overcome the limitations of H2.</p>
<ul>
<li><p>Integration tests will point to the same version of the database as it’s in production. So we can tie our TestContainer Database Image to the same version running on production.</p>
</li>
<li><p>Integration tests are lot more reliable because both application and tests are using the same database type and version and there won't be any compatibility issues in Testcases.</p>
</li>
</ul>
<h2 id="heading-what-is-testcontainers">What is TestContainers?</h2>
<p>The TestContainers library is a wrapper API over Docker. When we write code to create a container behind the scenes it may be translated to some Docker command, for example‌:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-283.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>MySQLContainer Creation</em></p>
<p>This code may be translated to something like the following:</p>
<pre><code class="lang-docker">docker <span class="hljs-keyword">run</span><span class="bash"> -d --env MYSQL_DATABASE=example_db --env MYSQL_USER=<span class="hljs-built_in">test</span> --env MYSQL_PASSWORD=<span class="hljs-built_in">test</span> ‘mysql:latest’</span>
</code></pre>
<p>TestContainers has a method name “withCommand”. You use it to set the command that should be run inside the Docker container which confirms that TestContainers is a wrapper API over Docker.</p>
<p>TestContainers downloads the MySQL, Postgres, Kafka, Redis images and runs in a container. The MySQLContainer will run a MySQL Database in a container and the Testcases can connect to it on the local machine. Once the execution is over the Database will be gone – it just deletes it from the machine. In the Testcases we can start as many container images as we want.</p>
<p>TestContainers supports JUnit 4, JUnit 5 and Spock. If you go to the TestContainers.org website, just visit the QuickStart section that explains how to use it:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-284.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>TestContainers.org how to start with Test Framework</em></p>
<p>TestContainers supports almost every Database from MySQL and Postgres to CockroachDB. You can find more info about this on the TestContainers.org website under the Modules section:‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-285.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>TestContainers support for Database Modules</em></p>
<p>‌TestContainers also supports Cloud Modules like GCloud Module and Azure Module as well. If your application is running on Google Cloud, then TestContainers has support for Cloud Spanner, Firestore, Datastore and so on.‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-286.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>TestContainers support for GCloud Module</em></p>
<p>In the article so far, we have discussed only about Databases, but TestContainers supports various other components like Kafka, SOLR, Redis, and more.</p>
<h2 id="heading-how-to-use-the-testcontainers-library">How to Use the TestContainers Library</h2>
<p>In this article we are going to explore TestContainers with JUnit 5. To implement TestContainers we need to understand a few important TestContainers annotations, methods, and the libraries that we need to implement in our project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-288.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>TestContainers libraries</em></p>
<h3 id="heading-annotations-in-testcontainers">Annotations in TestContainers</h3>
<p>Two important annotations are required in our Tests for TestContainers to work: @TestContainers and @Container.</p>
<p>@TestContainer is JUnit-Jupiter extension which automatically starts and stops the containers that are used in the tests. This annotation finds the fields that are marked with @Container and calls the specific Container life-cycle methods. Here, MySQLContainer life-cycle methods will be invoked.‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-289.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>MySQLContainer</em></p>
<p>The MySQLContainer is declared as static because if we declare Container as static then a single container is started and it will be shared across all the test methods.</p>
<p>If it’s an instance variable, then a new container is created for each test method.</p>
<h2 id="heading-testcontainers-library-methods">TestContainers Library Methods</h2>
<p>There are few important methods in TestContainers library that you'll use in the tests. They're good to know before using the library.</p>
<ul>
<li><p><strong>withInitScript</strong>: Using ‘withInitScript’ we can execute the .SQL to define the schema, tables, and plus add the data into the database. In short, this method is used to run the .SQL to populate the database.</p>
</li>
<li><p><strong>withReuse</strong> (true): Using “withReuse” method we can enable the reuse of containers. This method works well in conjunction with enabling the “testcontainers.reuse.enable:true” property in the “.testcontainers.properties” file.</p>
</li>
<li><p><strong>start</strong>: we use this to start the container.</p>
</li>
<li><p><strong>withClasspathResourceMapping</strong>: This maps a resource (file or directory) on the classpath to a path inside the container. This will only work if you are running your tests outside a Docker container.</p>
</li>
<li><p><strong>withCommand</strong>: Set the command that should be run inside the Docker container.</p>
</li>
<li><p><strong>withExposedPorts</strong>: Used to set the port that the container listens on.</p>
</li>
<li><p><strong>withFileSystemBind</strong>: Used to map a file / directory from the local filesystem into the container.</p>
</li>
</ul>
<h2 id="heading-testcontainers-use-case">TestContainers Use Case</h2>
<p>In the example we'll look at now, the application will communicate only with the database and write the integration tests for it using TestContainers. Then we'll extend the use-case by implementing Redis in between.</p>
<p>If the data exists in the Redis cache it will be returned, otherwise it'll dip into the database for saving and retrieval based on the Key.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-308.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Use-Case</em></p>
<p>The service is simple. It has 2 endpoints – the first one is to create a user and the second one is to find a user by email. If the user is found it is returned, otherwise we get a 404. The service class code looks something like this:‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-291.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Service Component</em></p>
<p>We are going to write tests for this class. You can find the entire codebase <a target="_blank" href="https://github.com/sameershukla/testcontainers_demo">here</a>:‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-292.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Test Class</em></p>
<p>The test class is marked with @TestContainers annotation which starts/stops the container. We use the @Container annotation to call the specific container's life-cycle methods.</p>
<p>Also, the “MySQLContainer” is declared as static because then a single container is started. Then it gets shared across all the test methods (we have already discussed the importance of these annotations).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-293.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>BeforeAll</em></p>
<p>Next we need to write a setup method marked with @BeforeAll, where we have enabled the “withReuse” method. This helps us reuse the existing containers. We are using the “withInitScript” method to execute the “.sql” file and then starting the container.‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-294.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Overwriting Properties</em></p>
<p>‌@DynamicPropertySource helps us override the properties declared in the properties file. We write this method to allow TestContainers to create the URL, username, and password on its own – otherwise we may face errors.</p>
<p>For example on removing username and password we may face an ‘Access denied’ error which may confuse us. So it’s better to allow TestContainer to assign these properties dynamically on its own.</p>
<p>That’s it – we are ready to run the Testcases:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-295.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Test Cases</em></p>
<p>Execute @AfterAll to stop the container, otherwise it may keep running on your local machine if you don't explicitly stop it. ‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-296.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-use-genericcontainer">How to Use GenericContainer</h2>
<p>‌‌GenericContainer is the most flexible container. It makes it easy to run any container images within GenericContainer.</p>
<p>Now we have Redis in place, all we need to do in our Testcase is to spin up a GenericContainer with the Redis image.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-297.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>GenericContainer for Redis</em></p>
<p>Then we start the Generic Redis container in @BeforeAll and stop it with the @AfterAll tear down method.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-298.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Starting Containers</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-299.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Stopping Containers</em></p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>It's extremely easy to use TestContainers in our application to write better tests. The learning curve is not too steep and it has support for various different modules from a variety of databases like Kafka, Redis and others.</p>
<p>Writing tests using TestContainers makes our tests lot more reliable. The only flip side is that the tests are slow compared to H2. This is because H2 is in memory and TestContainers takes time to download the image, run the container, and execute the entire setup we have discussed in this article.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
