Unit and Integration Testing with Vitest and React Testing Library

Unit and Integration Testing with Vitest and React Testing Library

ধরুন আপনি একটা স্কুলে ভর্তি হতে চান। স্কুলে কি আপনি গিয়ে মাত্র ভর্তি হতে পারবেন? কখনই না। স্কুল কর্তৃপক্ষ আপনাকে বিভিন্নভাবে যাচাই করে নিবেন। আগের ক্লাসের মার্কশীট দেখতে চাইবেন, ছোটখাট টেস্ট নিবেন, সবকিছুতে যদি আপনি পাশ করেন তাহলেই আপনি সেই স্কুলে ভর্তি হতে পারবেন। এখন এই প্রক্রিয়ার মধ্যে যাওয়ার কারণ কী? আপনি যখন ক্লাসে যাবেন তখন সেই ক্লাসে বিভিন্ন সাবজেক্টের অনেক শিক্ষক আসবেন। তারা এসে কি বারবার আপনি কি পারেন তা চেক করবেন? যদি তাই করতে হয় তাহলে তো তিনি আর পড়ানোর সময় পাবেন না। তাই আপনাকে বিভিন্ন টেস্টের মধ্য দিয়ে যাচাই করে স্কুলে নেয়া হয় যাতে সবাই জানে আপনি যোগ্য। আপনার যোগ্যতা যাচাই করতে গিয়ে যেন বারবার সময় নষ্ট না হয়। সেরকম একটা অ্যাপ্লিকেশন যখন আমরা তৈরি করবো আমরা সমস্ত টেস্ট করে সেটাকে উন্মুক্ত করবো, যেন সেটাতে এমন কোনো বাগ না থাকে যেটা ইউজারের মনে একটা বিরূপ প্রভাব ফেলে। সেই সাথে পরবর্তীতে যদি এই অ্যাপ্লিকেশনের কোনো আপডেট করতে হয়, চেইঞ্জের পর সেটা আগের মতোই কাজ করছে কিনা সেটাও চেক করতে পারা যায় খুব সহজেই।

Testing কি?

আমরা যখন কোনো কোড লিখি সেই কোড আমাদের চাহিদামতো আউটপুট জেনারেট করতে পারছে কিনা তা বারবার ব্রাউজার বা টার্মিনালে গিয়ে রান না করে অন্য যে উপায়ে তা ভেরিফাই এবং ভ্যালিডেট করা যায় তাকে টেস্টিং বলে। সফটওয়্যার ডেভেলপমেন্ট জগতে টেস্টিং অনেক গুরুত্বপূর্ণ একটি বিষয়। টেস্টিং এর মাধ্যমে আমরা আমাদের অ্যাপ্লিকেশনের বাগ, ডিফেক্ট, এরর, আনএক্সপেক্টেড আউটকাম ডিটেক্ট করতে পারি।

টেস্টিং এর গুরুত্ব

টেস্ট কেইস লিখতে অনেকের অনেক অলসতা লাগে। কিন্তু একবার যদি আপনি টেস্ট কেইস লিখে ফেলেন তাহলে পরবর্তীতে আপনার অনেক সময় বেঁচে যাবে। সেটা কিভাবে? আমরা জানি কোনো অ্যাপ্লিকেশন একবার বানিয়ে ফেললেই শেষ না। সেটা বারবার রিফ্যাক্টর হতে পারে, বিভিন্ন আপডেট, মডিফিকেশন ইত্যাদি আসতে পারে। আপনি যদি টেস্ট কেইস লিখে রাখেন তাহলে রিফ্যাক্টর করার পর সেটা ঠিকভাবে কাজ করছে কিনা তা টেস্ট রান করলেই বুঝে যাবেন। যদি ঠিকভাবে কাজ না করে তাহলে ঠিক কোন জায়গায় প্রব্লেম সেটাও আপনি ধরে ফেলতে পারবেন।

টেস্টিং এর প্রকারভেদ

টেস্টিং অনেক রকম আছে।

  • Unit Testing

  • Integration Testing

  • System Testing

  • Acceptance Testing

  • Regression Testing

  • Performance Testing

  • Security Testing

আমি এখানে শুধু ইউনিট এবং ইন্টিগ্রেশন টেস্টিং নিয়ে আলোচনা করবো।

ইউনিট টেস্টিং

ইউনিট টেস্টিং হল টেস্টিং প্রক্রিয়ার প্রথম অংশ, যা একটি কোড মডিউল বা কম্পোনেন্ট কে আলাদা আলাদা টেস্ট করার জন্য ব্যবহৃত হয়। ইউনিট টেস্টকে একটি আইসোলেটেড টেস্ট হিসেবে বিবেচনা করা যায়, যেখানে কোন বাহ্যিক ডিপেন্ডেন্সি বা এক্সটারনাল সিস্টেমের সাথে এর কোনো কানেকশন থাকে না।

একটি ইউনিট টেস্ট বিশেষ একটি ফাংশন, মেথড, ক্লাস বা কম্পোনেন্ট এর সাথে সংশ্লিষ্ট হতে পারে। এটি আমাদের কোডের একটি ছোট অংশ পরীক্ষা করে এবং আমাদের কোড ঠিকমতো কাজ করছে কিনা তা যাচাই করে। ইউনিট টেস্ট একটি অটোমেটেড প্রক্রিয়া হয় যার মাধ্যমে আমরা যাচাই করি কোডের প্রতিটি অংশ ঠিকমত কাজ করছে কিনা।

ধরুন আপনি একটা যোগ করার ফাংশন লিখলেন। আপনি অনেকগুলো ইনপুট লিখে চেক করতে চাইছেন। সেক্ষেত্রে আপনাকে টার্মিনালে গিয়ে বারবার রান করে সেটাকে চেক করতে হবে এবং প্রয়োজনীয় বাগগুলো ফিক্স করতে হবে। ইউনিট টেস্ট লিখলে আপনি আপনার সম্ভাব্য সকল ইনপুট একসাথে দিয়ে টেস্ট রান করলেই বুঝতে পারবেন আপনার কোড কাজ করছে কিনা। যদি কাজ না করে তাহলে কোন জায়গায় সমস্যা হচ্ছে সেটাও আপনি বুঝতে পারবেন টেস্ট থেকে।

ইন্টিগ্রেশন টেস্টিং

ধরুন অনেকগুলো কম্পোনেন্ট ব্যবহার করে আমরা একটি কম্পোনেন্ট বানালাম। এখন সেই কম্পোনেন্ট টেস্টিং এর জন্য আমরা যে প্রসেস ব্যবহার করবো সেটা হলো ইন্টিগ্রেশন টেস্টিং। অর্থাৎ এটি হলো এমন একটি টেস্টিং প্রসেস যার মাধ্যমে আমরা কয়েকটা কম্পোনেন্ট কম্বাইন্ডলি ঠিকভাবে কাজ করছে কিনা সেটা যাচাই করবো।

টেস্টিং টুলস

টেস্টিং এর জন্য বিভিন্নরকম লাইব্রেরি / প্যাকেজ আছে। তার মধ্যে React Testing Library, Jest, Vitest ইত্যাদি জনপ্রিয়। আমরা যদি vite দিয়ে প্রজেক্ট তৈরি করি তাহলে রিয়্যাক্ট টেস্টিং লাইব্রেরি ও vitest ব্যবহার করতে পারি। যেহেতু আমার প্রজেক্ট vite দিয়ে করা তাই আমি vitest ব্যবহার করছি। এটা বুঝতে পারলে আপনারা jest দিয়ে কাজ করতে পারবেন। তেমন কোনো পার্থক্য নেই।

ইনস্টলেশন

আমরা প্রথমে vitest ইনস্টল করে নিবো

yarn add -D vitest

এরপর আমরা RTL ইনস্টল করে নিবো।

yarn add -D @testing-library/jest-dom @testing-library/react @testing-library/user-event jsdom

কনফিগারেশন

আমরা আমাদের রুট ফোল্ডারে tests নামক একটি ফোল্ডার নিয়ে তাতে setup.js নামে একটি ফাইল নিবো। এবং তাতে নিচের কোডগুলো লিখবো।

import matchers from '@testing-library/jest-dom/matchers';
import { cleanup } from '@testing-library/react';
import { afterEach, expect } from 'vitest';

// extends Vitest's expect method with methods from react-testing-library
expect.extend(matchers);

// runs a cleanup after each test case (e.g. clearing jsdom)
afterEach(() => {
    cleanup();
});

এখানে আমরা vitest এর মেথডগুলো যেন RTL এর সাথে ম্যাচ করে সেটা লিখলাম। এবং প্রতিটি টেস্ট কেইস শেষে যেন তা ক্লিন করে দেয় সেটা লিখলাম। এবার vite.config.js এর মধ্যে আমরা কিছু মডিফিকেশন করবো।

import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react()],
    test: {
        globals: true,
        environment: 'jsdom',
        setupFiles: './tests/setup.js',
    },
});

test অবজেক্টটি আমরা এখানে অ্যাড করবো। এখানে globals: true মানে হলো আমরা vitest এর সমস্ত ইমপোর্ট গ্লোবাল করে দিলাম। তাই vitest থেকে আমাদের ম্যানুয়ালি কোনোকিছু আলাদা করে ইমপোর্ট করতে হবে না। যেহেতু RTL রিয়্যাক্ট কম্পোনেন্ট টেস্ট করে সেহেতু আমাদের এনভায়রনমেন্ট হবে jsdom। আর আমাদের setupFiles হলো যে setup.js ক্রিয়েট করেছিলাম সেটা। মোটামুটি আমাদের সেটাপ রেডি। আরেকটা ছোট কাজ বাকি। package.json এ গিয়ে আমাদের একটা জিনিস লিখতে হবে।

{
    // ....
    "scripts": {
        // ...
        "test": "vitest"
    }
    // ....
}

আমরা টেস্টের জন্য একটা স্ক্রিপ্ট লিখে নিলাম।

ইউনিট টেস্টের উদাহরণ

আমরা LoginForm নামক একটি কম্পোনেন্ট বানালাম।

import styled from 'styled-components';

const Form = styled.form`
    display: flex;
    flex-direction: column;
    gap: 2rem;
    width: 350px;
    margin: 2rem auto;
`;

const FormGroup = styled.div`
    display: flex;
    flex-direction: column;
    gap: 1rem;
`;

const Label = styled.label`
    font-weight: 700;
    font-size: 1rem;
`;

const Input = styled.input`
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ffc600;
    border-radius: 5px;
`;

const ErrorMessage = styled.p`
    font-weight: 700;
    font-size: 1rem;
    color: red;
    margin-top: 0;
`;

const Button = styled.button`
    padding: 0.8rem;
    border: 1px solid #ffc600;
    display: block;
    width: 30%;
    border-radius: 5px;
    background-color: #ffc600;
    cursor: pointer;
    &:hover {
        background-color: transparent;
    }
`;

const LoginForm = ({ loginInfo, onChange, onSubmit, error }) => {
    return (
        <Form onSubmit={onSubmit}>
            <FormGroup>
                <Label htmlFor="username">Email or username</Label>
                <Input
                    type={'text'}
                    value={loginInfo && loginInfo.username}
                    onChange={onChange}
                    id={'username'}
                    name={'username'}
                    placeholder={'Enter your email or username'}
                />
                {error && <ErrorMessage>Invalid username or email</ErrorMessage>}
            </FormGroup>
            <FormGroup>
                <Label htmlFor="password">Password</Label>
                <Input
                    type={'password'}
                    value={loginInfo && loginInfo.password}
                    onChange={onChange}
                    id={'password'}
                    name={'password'}
                    placeholder={'Enter your password'}
                />
                {error && <ErrorMessage>Invalid Password</ErrorMessage>}
            </FormGroup>
            <Button type="submit">Login</Button>
        </Form>
    );
};

export default LoginForm;

আমরা এখানে স্টাইলিং এর জন্য styled-components ব্যবহার করেছি। এটা ইনস্টল করার জন্য নিচের কমান্ডটি কমান্ড লাইনে দিন।

yarn add styled-components

এখন এই কম্পোনেন্ট ঠিকভাবে কাজ করছে কিনা তা দেখার জন্য আমরা টেস্ট কেস লিখবো। তার জন্য আমাদের tests ফোল্ডারের মধ্যে আমরা LoginForm.test.jsx নামে একটি ফাইল ক্রিয়েট করবো। প্রথমে আমরা টেস্ট করতে চাইছি। আমাদের অ্যাপ্লিকেশনে username or email এর জন্য একটা লেবেলসহ ইনপুট ফিল্ড আছে কিনা।।

import { render, screen } from '@testing-library/react';
import LoginForm from './src/components/loginForm';

describe('LoginForm', () => {
    it('should have an input for username or email with label', () => {
        render(<LoginForm />);

        const labelElement = screen.getByLabelText('Email or username');
        const inputElement = screen.getByPlaceholderText(
            'Enter your email or username'
        );
        expect(labelElement).toBeInTheDocument();
        expect(inputElement).toBeInTheDocument();
    });
});

describe মানে হলো আমরা কোনো একটা কম্পোনেন্টের আন্ডারে টেস্ট করতে চাইছি তার নাম লেখা, আর it এর মধ্যে কোন টেস্ট করতে চাইছি তা লিখা। আমরা প্রথমে আমাদের কম্পোনেন্ট রেন্ডার করে নিলাম। এরপর screen.getByLabelText('Email or username') দিয়ে আমাদের লেবেল ইলেমেন্টকে সিলেক্ট করলাম, সেইসাথে screen.getByPlaceholderText('Enter your email or username') দিয়ে আমরা আমাদের ইনপুট ফিল্ডকে সিলেক্ট করে নিলাম। এখানে কিভাবে কোনটা ধরবো তার বিবরণ আপনারা RTL এর ডকুমেন্টেশনে পেয়ে যাবেন। আমি শুধু টেস্টিং নিয়ে আপনাদের একটা ধারণা দিচ্ছি। ডকুমেন্টেশনে সব আপনারা বিস্তারিত পেয়ে যাবেন।

এরপর আমরা এই দুইটা ইলেমেন্ট এই অ্যাপ্লিকেশনে আছে কিনা তা চেক করে দেখলাম। expect(labelElement).toBeInTheDocument() অর্থ আমরা এই ইলেমেন্ট এই ডকুমেন্টে আছে সেটা এক্সপেক্ট করছি। এরপর টার্মিনালে গিয়ে কমান্ড দিবেন yarn test। যদি নিচের আউটপুট আসে তাহলে আপনার ্টেস্ট পাশ হয়ে গেলো।

✓ src/tests/LoginForm.test.jsx (1)

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  12:36:47
   Duration  16.72s (transform 566ms, setup 2.62s, collect 642ms, tests 122ms)

এরপর আমরা আরো দুইটা টেস্ট কেইস লিখবো।

import { render, screen } from '@testing-library/react';
import LoginForm from './src/components/loginForm';

describe('LoginForm', () => {
    it('should have an input for username or email with label', () => {
        render(<LoginForm />);

        const labelElement = screen.getByLabelText('Email or username');
        const inputElement = screen.getByPlaceholderText(
            'Enter your email or username'
        );
        expect(labelElement).toBeInTheDocument();
        expect(inputElement).toBeInTheDocument();
    });

    it('should have an input for password with label', () => {
        render(<LoginForm />);

        const labelElement = screen.getByLabelText('Password');
        const inputElement = screen.getByPlaceholderText('Enter your password');
        expect(labelElement).toBeInTheDocument();
        expect(inputElement).toBeInTheDocument();
        expect(inputElement).toHaveAttribute('type', 'password');
    });

    it('should have a submit type button with text "Login"', () => {
        render(<LoginForm />);

        const element = screen.getByRole('button');

        expect(element).toBeInTheDocument();
        expect(element).toHaveAttribute('type', 'submit');
        expect(element).toHaveTextContent('Login');
    });
});

একটা পাসওয়ার্ডের জন্য ইনপুট আছে কিনা চেক করবে ও আরেকটা বাটন আছে কিনা চেক করবে। যথারীতি লিখে কমান্ড দিবেন yarn test। নিচের আউটপুট যদি আসে তাহলে আপনার এই কম্পোনেন্টের কাজ শেষ।

 ✓ src/tests/LoginForm.test.jsx (3)

 Test Files  1 passed (1)
      Tests  3 passed (3)
   Start at  12:41:28
   Duration  2.75s

আমরা বারবার কম্পোনেন্ট রেন্ডার না করে beforeEach মেথড ব্যবহার করে একবারেই তা করতে পারি। যেমনঃ

import { render, screen } from '@testing-library/react';
import LoginForm from './src/components/loginForm';

describe('LoginForm', () => {
    beforeEach(() => {
        render(<LoginForm />);
    });
    it('should have an input for username or email with label', () => {
        const labelElement = screen.getByLabelText('Email or username');
        const inputElement = screen.getByPlaceholderText(
            'Enter your email or username'
        );
        expect(labelElement).toBeInTheDocument();
        expect(inputElement).toBeInTheDocument();
    });

    it('should have an input for password with label', () => {
        const labelElement = screen.getByLabelText('Password');
        const inputElement = screen.getByPlaceholderText('Enter your password');
        expect(labelElement).toBeInTheDocument();
        expect(inputElement).toBeInTheDocument();
        expect(inputElement).toHaveAttribute('type', 'password');
    });

    it('should have a submit type button with text "Login"', () => {
        const element = screen.getByRole('button');

        expect(element).toBeInTheDocument();
        expect(element).toHaveAttribute('type', 'submit');
        expect(element).toHaveTextContent('Login');
    });
});

এর ফলে প্রতিটা টেস্টের আগেই তা রেন্ডার হবে। আমাদের আর বারবার render(<LoginForm />); লিখার কোনো প্রয়োজন পড়বে না।

ইন্টিগ্রেশন টেস্টের উদাহরণ

আমরা এবার আমাদের আগের বানানো কম্পোনেন্টকে নিয়ে App.jsx এ ব্যবহার করবো।

import axios from 'axios';
import { useEffect, useState } from 'react';
import LoginForm from './components/loginForm';
import Button from './components/shared/ui/Button';
import Heading from './components/shared/ui/Heading';

const initialLoginInfo = {
    username: '',
    password: '',
    isLoggedIn: false,
    hasError: false,
};

const App = () => {
    const [loginInfo, setLoginInfo] = useState(initialLoginInfo);
    const [users, setUsers] = useState(null);

    useEffect(() => {
        axios
            .get('https://fakestoreapi.com/users')
            .then((res) => res.data)
            .then((data) => setUsers(data));
    }, []);

    const handleSubmit = (e) => {
        e.preventDefault();
        const verified = users.filter(
            (user) =>
                loginInfo.username === user.username &&
                loginInfo.password === user.password
        );

        if (verified.length > 0) {
            setLoginInfo((prev) => ({
                ...prev,
                isLoggedIn: true,
            }));
        } else {
            setLoginInfo((prev) => ({
                ...prev,
                hasError: true,
            }));
        }
    };

    const handleChange = (e) => {
        setLoginInfo((prev) => ({
            ...prev,
            hasError: false,
            [e.target.name]: e.target.value,
        }));
    };

    const handleLogoutBtn = () => {
        setLoginInfo(initialLoginInfo);
    };

    return (
        <div>
            {loginInfo.isLoggedIn ? (
                <div>
                    <Heading>Logged in as {loginInfo.username}</Heading>
                    <Button
                        style={{ margin: '0 auto', width: '8rem' }}
                        type={'button'}
                        onClick={handleLogoutBtn}
                    >
                        Logout
                    </Button>
                </div>
            ) : (
                <LoginForm
                    onSubmit={handleSubmit}
                    onChange={handleChange}
                    loginInfo={loginInfo}
                    error={loginInfo.hasError}
                />
            )}
        </div>
    );
};

export default App;

একটা সিম্পল লগইন পেইজ। যেটাতে লগইন করলে তা নতুন একটা পেইজে নিয়ে যাবে। আশা করি এই সিম্পল জিনিসটা কাউকে ব্যাখ্যা করে দেয়া লাগবে না। আপনারা যদি এই আর্টিকেল পড়ে থাকেন তাহলে ধরেই নিচ্ছি অলরেডি এতটুকু কোড বুঝার মতো আপনারা ক্যাপেবল। এবার আমরা এই কম্পোনেন্টের জন্য টেস্ট কেইস লিখবো। এটাকে ইন্টিগ্রেশন টেস্টিং বলার কারণ হচ্ছে আমরা এখানে অন্য আরেকটি কম্পোনেন্টের সমন্বয়ে এই কম্পোনেন্ট বানিয়েছি। ঐ কম্পোনেন্টের উপর ডিপেন্ড করছে আমার এই কম্পোনেন্ট ঠিকভাবে কাজ করছে কিনা। যেহেতু সমন্বয় অর্থাৎ ইন্টিগ্রেট করেছি তাই এটার টেস্টিংকে আমরা বলবো ইন্টিগ্রেশন টেস্টিং।

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import axios from 'axios';
import App from './src/App';

vi.mock('axios');

const users = [
    { username: 'aditya', password: 'Pass1234' },
    { username: 'john', password: 'Test1234' },
];

describe('App', () => {});

আমরা প্রথমে স্ট্রাকচারটা দাঁড় করালাম। বুঝিয়ে দিচ্ছি বিষয়টা। যেহেতু এখানে আমরা axios ব্যবহার করেছি, তার মানে সার্ভারের সাথে আমার একটা কানেকশন আছে। আমরা সবাই জানি সার্ভার অনেক কস্টলি। আমি টেস্টিং এর জন্য শুধু শুধু এত খরচ কেন বহন করবো? তাই আমরা axios কে mock করে নিলাম, যাতে কোনো রিকোয়েস্ট সরাসরি সার্ভারে না যায়। আমরা কিছু ডামী ডাটা ব্যবহার করেছি। এবার প্রথমে আমরা দেখবো আমাদের App কম্পোনেন্ট ঠিকমতো রেন্ডার হচ্ছে কিনা।

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import axios from 'axios';
import App from '../App';

vi.mock('axios');

const users = [
    { username: 'aditya', password: 'Pass1234' },
    { username: 'john', password: 'Test1234' },
];

describe('App', () => {
    it('renders App component', () => {
        axios.get.mockResolvedValue({ data: users });
        render(<App />);

        expect(
            screen.getByPlaceholderText('Enter your email or username')
        ).toBeInTheDocument();
        expect(
            screen.getByPlaceholderText('Enter your password')
        ).toBeInTheDocument();
        expect(screen.getByRole('button')).toBeInTheDocument();
        expect(screen.getByText('Login')).toBeInTheDocument();
    });
});

আমরা আমাদের মক করা axios থেকে আমাদের ডামী ডাটা ইউজ করেছি, যাতে সার্ভারে কোনো রিকোয়েস্ট না যায়। আর বাকিটাতো আপনারা বুঝতেই পারছেন। এখানে getByRole এর কাজ হলো রোল অনুসারে ইলেমেন্ট সিলেক্ট করা। যেমনঃ বাটন, প্যারাগ্রাফ, ইনপুট ইত্যাদি। এটা টেস্ট করে দেখবেন আগের মতো, কাজ করে কিনা। এরপর আমরা চেক করবো, যদি ইউজারনেইম বা পাসওয়ার্ড ইনভ্যালিড হয় তাহলে কি হবে তা।

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import axios from 'axios';
import App from '../App';

vi.mock('axios');

const users = [
    { username: 'aditya', password: 'Pass1234' },
    { username: 'john', password: 'Test1234' },
];

describe('App', () => {
    it('renders App component', () => {
        axios.get.mockResolvedValue({ data: users });
        render(<App />);

        expect(
            screen.getByPlaceholderText('Enter your email or username')
        ).toBeInTheDocument();
        expect(
            screen.getByPlaceholderText('Enter your password')
        ).toBeInTheDocument();
        expect(screen.getByRole('button')).toBeInTheDocument();
        expect(screen.getByText('Login')).toBeInTheDocument();
    });

    it('should should show error message if username or password is invalid', async () => {
        axios.mockResolvedValue({ data: users });
        render(<App />);

        const loginButton = screen.getByRole('button');

        await userEvent.click(loginButton);
        expect(screen.getByText('Invalid username or email')).toBeInTheDocument();
        expect(screen.getByText('Invalid Password')).toBeInTheDocument();
    });
});

userEvent এর মাধ্যমে আমরা কোনো কিছু টাইপ করতে পারি, ক্লিক করতে পারি। এখানে কোনো ইনপুট ছাড়া যদি আমি লগইন বাটনে ক্লিক করি সেটা আমাদের একটা এরর দিবে। সেটাই এখানে লেখা হয়েছে। এবার আমরা লিখবো যদি লগইন সাক্সেসফুল হয় তাহলে কি হবে সেটার টেস্ট কেইস।

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import axios from 'axios';
import App from '../App';

vi.mock('axios');

const users = [
    { username: 'aditya', password: 'Pass1234' },
    { username: 'john', password: 'Test1234' },
];

describe('App', () => {
    it('renders App component', () => {
        axios.get.mockResolvedValue({ data: users });
        render(<App />);

        expect(
            screen.getByPlaceholderText('Enter your email or username')
        ).toBeInTheDocument();
        expect(
            screen.getByPlaceholderText('Enter your password')
        ).toBeInTheDocument();
        expect(screen.getByRole('button')).toBeInTheDocument();
        expect(screen.getByText('Login')).toBeInTheDocument();
    });

    it('should should show error message if username or password is invalid', async () => {
        axios.mockResolvedValue({ data: users });
        render(<App />);

        const loginButton = screen.getByRole('button');

        await userEvent.click(loginButton);
        expect(screen.getByText('Invalid username or email')).toBeInTheDocument();
        expect(screen.getByText('Invalid Password')).toBeInTheDocument();
    });

    it('should logged in successfully and logged out when click the logout button', async () => {
        axios.mockResolvedValue({ data: users });
        render(<App />);

        await userEvent.type(
            screen.getByPlaceholderText('Enter your email or username'),
            users[1].username
        );
        await userEvent.type(
            screen.getByPlaceholderText('Enter your password'),
            users[1].password
        );
        await userEvent.click(screen.getByRole('button'));

        expect(screen.getByRole('heading')).toBeInTheDocument();
        expect(screen.getByRole('button')).toBeInTheDocument();
        expect(
            screen.getByText(`Logged in as ${users[1].username}`)
        ).toBeInTheDocument();
        expect(screen.getByText('Logout')).toBeInTheDocument();

        await userEvent.click(screen.getByText('Logout'));
        expect(screen.getByText('Login')).toBeInTheDocument();
    });
});

আমরা ইউজার নেইম, পাসওয়ার্ড টাইপ করলাম userEvent এর মাধ্যমে। এরপর লগইন বাটনে ক্লিক করলাম। ঠিকভাবে হওয়াতে তা আমাদের প্রোফাইল পেইজে নিয়ে যাবে। এরপর যদি লগআউট বাটনে ক্লিক করি তাহলে আবার লগইন পেইজে চলে আসবে। userEvent ব্যবহার করার সময় আমাদের খেয়াল রাখতে হবে এখানে async await ব্যবহার করতে হবে।

উপসংহার

এই ব্লগের মাধ্যমে আমি চেষ্টা করেছি টেস্টিং কি এবং টেস্টিং এর গুরুত্বটা বুঝানোর জন্য। আপনারা ডকুমেন্টেশন পড়ে আরো ভালভাবে টেস্টিং সম্পর্কে জেনে নিবেন। এই আর্টিকেলের মাধ্যমে টেস্টিং শেখানোর কোনো উদ্দেশ্য ছিল না। শুরুতে আপনাদের টেস্ট কোড লিখতে অনেক প্যারাময় মনে হবে। কিন্তু বিশ্বাস করেন, যদি একবার টেস্ট কোড লিখে ফেলেন ঐ অ্যাপ্লিকেশনে যতো যাই পরিবর্তন হোক আপনাকে আর ভাবতে হবে না সেটা কাজ করছে কিনা। টেস্ট রান করলেই আপনি বুঝে যাবেন, এবং যদি এরর আসে তাহলে সেই অনুসারে আপনারা তা সল্ভ করে নিতে পারবেন।