<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arzaqul Mughny</title>
    <description>The latest articles on DEV Community by Arzaqul Mughny (@arzaqulmughny).</description>
    <link>https://dev.to/arzaqulmughny</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1174124%2F92eed428-731a-4b82-85e5-f3b2a3de026f.jpeg</url>
      <title>DEV Community: Arzaqul Mughny</title>
      <link>https://dev.to/arzaqulmughny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arzaqulmughny"/>
    <language>en</language>
    <item>
      <title>Testing React App Using Vitest &amp; React Testing Library</title>
      <dc:creator>Arzaqul Mughny</dc:creator>
      <pubDate>Sat, 13 Jul 2024 08:35:34 +0000</pubDate>
      <link>https://dev.to/arzaqulmughny/testing-react-app-using-vitest-react-testing-library-457j</link>
      <guid>https://dev.to/arzaqulmughny/testing-react-app-using-vitest-react-testing-library-457j</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When developing an application, we can't ensure our app is bug-free. We will test our app before going into production. Often, while creating new features or modifying existing code, we can unintentionally broke previous code and produce errors. It would be useful to know this during the development process. Imagine if we don't knew the broken feature and that is main feature of our application, If this happens, it can lead to user frustration and damage the application's reputation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxa1sbbjbq087cj07m26.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxa1sbbjbq087cj07m26.jpg" alt="Error illustration" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;To minimize this from happening, we can implement automated testing. With this, we can quickly identify any bugs that occur and take immediate action to fix them. I don't want to dwell on what automated testing is, you can read more about it &lt;a href="https://www.google.com/url?sa=t&amp;amp;source=web&amp;amp;rct=j&amp;amp;opi=89978449&amp;amp;url=https://en.wikipedia.org/wiki/Test_automation&amp;amp;ved=2ahUKEwj7x9j5jKOHAxU3SWwGHeFBDPsQFnoECBAQAQ&amp;amp;usg=AOvVaw0g9q0JbGt4-lynotMHvmXC" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Benefit of implementing Automate Testing&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Early Bug Detection&lt;/strong&gt;&lt;br&gt;
By running tests automatically, bugs can be detected earlier in the development process, making them easier and cheaper to fix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increased Confidence&lt;/strong&gt;&lt;br&gt;
With automated testing in place, developers and stakeholders have more confidence that the application functions as expected, improving the overall quality of the final product.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Several things need to be considered before carrying out automated testing in our application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Messy code isn't testable&lt;/strong&gt;&lt;br&gt;
We need to know how to write clean code in React, such as breaking large components down into smaller parts and abstracting logic. Implementing automated testing encourages developers to write clean code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clarity regarding feature specifications&lt;/strong&gt;&lt;br&gt;
It can be very tiring if we have written test code but the features change often. We would then need to update the test code repeatedly, which takes a lot of time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, I will share my learning journey about testing our React application, including how to set it up and a little about how to implement it. Enough explaining, Let’s get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Used
&lt;/h2&gt;

&lt;p&gt;There is tools used to test our application, I will explain according to my understanding about these tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vitest
&lt;/h3&gt;

&lt;p&gt;We will write unit test &amp;amp; integration test in our application. We can use &lt;a href="https://vitest.dev/" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt; as test runner, it will run our all test code then give the results.&lt;/p&gt;

&lt;h3&gt;
  
  
  React Testing Library (RTL)
&lt;/h3&gt;

&lt;p&gt;Using &lt;a href="https://testing-library.com/docs/react-testing-library/intro/" rel="noopener noreferrer"&gt;React Testing Library&lt;/a&gt; we can test our React component by user perspective.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mock Service Worker (MSW)
&lt;/h3&gt;

&lt;p&gt;While testing our app in integration test test we don't interact to other services like API server. We need to &lt;em&gt;"fake"&lt;/em&gt; that API response using &lt;a href="https://testing-library.com/docs/react-testing-library/intro/" rel="noopener noreferrer"&gt;Mock Service Worker&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Events
&lt;/h3&gt;

&lt;p&gt;This is a companion for React Testing Libary is used for simulate user interactions&lt;/p&gt;

&lt;h3&gt;
  
  
  JSDOM
&lt;/h3&gt;

&lt;p&gt;While testing we no need to start React project instead using &lt;a href="https://www.npmjs.com/package/jsdom" rel="noopener noreferrer"&gt;JSDOM&lt;/a&gt; to emulate browser&lt;/p&gt;

&lt;h3&gt;
  
  
  Jest DOM
&lt;/h3&gt;

&lt;p&gt;This is like an utility to ensure the html elements behavior are as we expect&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Create React App using Vite&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest react-test -- -- template react-ts
cd react-test
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Vitest&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -D Vitest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install React Testing Library (RTL)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev @testing-library/react @testing-library/dom @types/react @types/react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install JSDOM&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -D jsdom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Jest DOM&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev @testing-library/jest-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install User Event&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev @testing-library/user-event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add script run Vitest in &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "scripts": {
        "test": "vitest"
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure vite.config.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// &amp;lt;reference types="vitest" /&amp;gt;
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react()],
    test: {
        globals: true, // Make Vitest variable can be accessed globaly
        environment: 'jsdom', // Change default testing environment to jsdom
        setupFiles: './src/tests/setup.ts', // Setup file
    },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure &lt;code&gt;tsconfig.app.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   "compilerOptions": {
    "types": ["vitest/globals"] // Make code editor has type hinting for Vitest
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Mock Service Worker (MSW)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install msw@latest --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;handler.ts&lt;/code&gt; in &lt;code&gt;src/tests/mocks&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { http, HttpResponse } from 'msw';

export const handlers: any = [];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;node.ts&lt;/code&gt; in &lt;code&gt;src/tests/mocks&lt;/code&gt; for mocking API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;setup.ts&lt;/code&gt; in &lt;code&gt;src/tests&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import '@testing-library/jest-dom';
import { server } from './mocks/node';

beforeEach(() =&amp;gt; {
    // Enable mocking
    server.listen();
});

afterEach(() =&amp;gt; {
    // Disable mocking
    server.resetHandlers();
});

afterAll(() =&amp;gt; {
    // Clean up once the tests are done
    server.close();
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;Now we can start to write test code for our app&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Unit Testing
&lt;/h2&gt;

&lt;p&gt;To start i will give a simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// sum.ts
export const sum = (a: number, b: number): number =&amp;gt; {
    return a + b;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will test this function by creating sum.test.ts alongside sum.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// sum.test.ts
import { sum } from './sum';

describe('sum function', () =&amp;gt; {
  test('should return the sum of two numbers', () =&amp;gt; {
    expect(sum(1, 2)).toBe(3);
    expect(sum(-1, 1)).toBe(0);
    expect(sum(0, 0)).toBe(0);
    expect(sum(5, 7)).toBe(12);
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;describe&lt;/code&gt;: grouping some related tests with sum function&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test&lt;/code&gt;: is a block that contains specific test&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expect&lt;/code&gt;: this is the way to make an assertion. &lt;code&gt;expect&lt;/code&gt; expects the result of sum(a, b) to be equal to the value we specified with toBe.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toBe&lt;/code&gt;: is one of &lt;a href="https://vitest.dev/api/expect.html" rel="noopener noreferrer"&gt;Vitest matcher&lt;/a&gt; which is used to express expectations in unit tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To start Vitest we can type&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;npm run test&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 in terminal and see the result:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52tsxx1x0oxhzm3viq84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52tsxx1x0oxhzm3viq84.png" alt="Testing result" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing React Component
&lt;/h2&gt;

&lt;p&gt;I will make a counter component for example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/components/Counter.tsx
import { useState } from 'react';

const Counter = () =&amp;gt; {
    const [count, setCount] = useState(0);

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;button
                type='button'
                data-testid="decrease"
                onClick={() =&amp;gt; setCount((currentValue) =&amp;gt; currentValue - 1)}
            &amp;gt;
                Decrease
            &amp;lt;/button&amp;gt;
            &amp;lt;h1 data-testid="current"&amp;gt;{count}&amp;lt;/h1&amp;gt;
            &amp;lt;button
                type='button'
                data-testid="increase"
                onClick={() =&amp;gt; setCount((currentValue) =&amp;gt; currentValue + 1)}
            &amp;gt;
                Increase
            &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

export default Counter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We test &lt;code&gt;Counter.tsx&lt;/code&gt; by creating &lt;code&gt;Counter.test.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/components/Counter.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

describe('Counter component', () =&amp;gt; {
    it('Should render correctly', () =&amp;gt; {
        render(&amp;lt;Counter /&amp;gt;);

        // Assert element exists
        expect(screen.getByTestId('decrease')).toBeInTheDocument();
        expect(screen.getByTestId('increase')).toBeInTheDocument();
        expect(screen.getByTestId('current')).toBeInTheDocument();

        // Assert rendering initial value
        expect(screen.getByTestId('current')).toHaveTextContent('0');
    });

    it('Should decrease and incrementing correctly'),
        async () =&amp;gt; {
            render(&amp;lt;Counter /&amp;gt;);
            const user = userEvent.setup();

            // Decreasing count
            const decreaseButton = screen.getByTestId('decrease');
            await user.click(decreaseButton);
            expect(screen.getByTestId('current')).toHaveTextContent('0');

            // Increasing count
            const increaseButton = screen.getByTestId('increase');
            await user.click(increaseButton);
            expect(screen.getByTestId('current')).toHaveTextContent('1');
        };
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Component That Do Api Call
&lt;/h2&gt;

&lt;p&gt;I will use &lt;a href="https://jsonplaceholder.typicode.com/" rel="noopener noreferrer"&gt;Jsonplaceholder&lt;/a&gt; free api&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/Users.tsx

import { useEffect, useState } from 'react';

type User = {
    id: number;
    name: string;
};

/**
 * User list
 */
const Users = () =&amp;gt; {
    const [data, setData] = useState&amp;lt;null | User[]&amp;gt;(null);
    const [error, setError] = useState&amp;lt;null | unknown&amp;gt;(null);
    const [loading, setLoading] = useState(false);

    /**
     * Fetch users
     */
    const fetchUsers = async () =&amp;gt; {
        setLoading(true);

        try {
            const response = await fetch('https://jsonplaceholder.typicode.com/users');
            const result = await response.json();

            setData(result);
            setError(null);
        } catch (error) {
            setData(null);
            setError(error);
        } finally {
            setLoading(false);
        }
    };

    useEffect(() =&amp;gt; {
        fetchUsers();
    }, []);

    if (loading) {
        return &amp;lt;div data-testid='loading'&amp;gt;Loading...&amp;lt;/div&amp;gt;;
    }

    if (error) {
        return &amp;lt;div data-testid='error'&amp;gt;Error when fetching users...&amp;lt;/div&amp;gt;;
    }

    return (
        &amp;lt;ul data-testid='users'&amp;gt;
            {data?.map((user) =&amp;gt; (
                &amp;lt;li
                    data-testid='user'
                    key={user.id}
                &amp;gt;
                    {user.name}
                &amp;lt;/li&amp;gt;
            ))}
        &amp;lt;/ul&amp;gt;
    );
};

export default Users;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding handler&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/tests/mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers: any = [
    http.get('https://jsonplaceholder.typicode.com/users', () =&amp;gt; {
        return HttpResponse.json([
            {
                id: 1,
                name: 'Arza',
            },
            {
                id: 2,
                name: 'Zaarza',
            },
        ]);
    }),
];

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/components/Users.test.tsx

import { render, screen, waitForElementToBeRemoved } from '@testing-library/react';
import Users from './Users';
import { server } from '../tests/mocks/node';
import { HttpResponse, http } from 'msw';

describe('Users component', () =&amp;gt; {
    it('Should render correctly', () =&amp;gt; {
        render(&amp;lt;Users /&amp;gt;);

        // Assert element exists
        expect(screen.getByTestId('loading')).toBeInTheDocument();
    });

    it('Should render users correctly after loading', async () =&amp;gt; {
        render(&amp;lt;Users /&amp;gt;);

        // Wait for loading
        const loading = screen.getByTestId('loading');
        await waitForElementToBeRemoved(loading);

        // Assert rendering users
        const userItem = screen.getAllByTestId('user');
        expect(userItem).toHaveLength(2);
    });

    it('Should render error correctly', async () =&amp;gt; {
        // Mock error
        server.use(
            http.get('https://jsonplaceholder.typicode.com/users', async () =&amp;gt; {
                return new HttpResponse(null, {
                    status: 500,
                });
            })
        );

        render(&amp;lt;Users /&amp;gt;);

        // Assert rendering error
        expect(await screen.findByTestId('error')).toBeInTheDocument();
    });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>react</category>
      <category>vitest</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
