آموزش عملی تست نویسی با React Testing Library و Jest

درود بر همراهان عزیز آکادمی راهکارینو. در این مقاله آموزشی می خواهیم شما را بصورت عملی با تست نویسی در ریکت توسط React-Testing-Library و Jest آشنا کنیم.
همانطور که در مقاله “آموزش مفاهیم تست نویسی در ری اکت” توضیح دادیم، ما در این آموزش می خواهیم بصورت TDD یا Test-Driven Development تست نویسی را انجام دهید. یعنی ابتدا تست موردنظر را می نویسیم، سپس تست Fail می شود و در نهایت کدی را می نویسیم که تست مذکور PASS شود.
*می توانید سورس کد پروژه را در گیتهاب بنده مشاهده و کپی کنید.
تست پیش فرض App.test.js در پکیج Create-React-App:
پس از اینکه پروژه ری اکتی خود را توسط دستور npx create-react-app راه اندازی کردید، بطور پیش فرض در فولدر src یک فایل بنام App.test.js قرار دارد که کد زیر را شامل می شود:
import { render, screen } from '@testing-library/react'; import App from './App'; test('renders learn react link', () => { render(<App />); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); });
در ادامه می خواهیم تست خود را در این فایل تعریف کنیم.
تست نویسی برای یک مینی پروژه (کار با دکمه و چک باکس):
سناریویی که می خواهیم برای آن تست بنویسیم به این صورت است: مطابق تصویر زیر، می خواهیم وقتی روی دکمه قرمز رنگ کلیک کردیم، رنگ دکمه آبی شود و متن آن نیز آپدیت شود. و برعکس. اولین تستی که می خواهیم برای این مثال بنویسیم اینست: چک کردن رنگ و تکست اولیه دکمه
اگر از مقاله قبلی به خاطر داشته باشید، متد test دو آرگومان ورودی می گیرد. اولین آرگومان برابر با یک رشته متنی است که بیانگر کاری است که تست انجام می دهد. و دومین آرگومان یک تابع کال بک می باشد که بدنه اصلی تست در این تابع نوشته خواهد شد.
تست کردن رنگ و متن اولیه دکمه:
اولین متد تستی که می نویسیم برای بررسی رنگ اولیه دکمه و متن آن است. در بدنه تابع تست ابتدا باید توسط متد render کامپوننت موردنظر را رندر کنیم. سپس المان موردنظر را در DOM پیدا کنیم. آبجکت screen می تواند به VirtualDOM دسترسی داشته باشد. پس از این آبجکت استفاده خواهیم کرد. روش پیشنهاد شده برای دستیابی به المان مورد نظر استفاده از نقش یا role المان است. که در این مثال button می باشد:
test("button has correct initial color and text", () => { render(<App />); // find an element with role of button and text of 'Change to blue' const myBtn = screen.getByRole("button", { name: "Change to blue" }); // expect the bg-color to be red expect(myBtn).toHaveStyle({ backgroundColor: "red" }); });
در این تست انتظار داریم رنگ دکمه در حالت پیش فرض red و متن آن نیز Change to blue باشد. اکنون باید فایل App.js را باز کرده و دکمه زیر را در آن تعریف کنید:
<div className="App"> <button style={{ backgroundColor: "red" }}>Change to blue</button> </div>
پس از انجام اینکار، در ترمینال دستور npm test را اجرا کنید. پیغام سبزرنگ PASS را مطابق تصویر زیر مشاهده خواهید کرد:
نکته 1: در این مثال از matcher ای بنام toHaveStyle استفاده کردیم. برای مشاهده لیست کامل matcher ها در JEST به jest-dom github مراجعه نمایید.
نکته 2: برای مشاهده لیست کامل روش های دسترسی به المان در تست نویسی ری اکت، به مقاله “آموزش مفاهیم تست نویسی در React” مراجعه شود.
تست کردن رویداد کلیک دکمه در ری اکت:
اکنون می خواهیم تستی بنویسیم که وقتی روی دکمه مذکور کلیک شد تغییر رنگ دکمه و متن آن را چک کند. برای اینکار می توانیم یک متد تست جداگانه در فایل App.test.js تعریف کنیم. البته در این مثال بهتر است از همان متد تست اول استفاده کنیم و یک expect دیگر به آن اضافه کنیم.
برای اینکه رویدادهای تعاملی کاربر، مانند کلیک را بتوانیم تست کنیم باید ابتدا fireEvent را از کتابخانه test-library به شکل زیر ایمپورت کنیم:
import { render, screen, fireEvent } from "@testing-library/react";
حال به متد test رفته و کدهای زیر را به آن اضافه کنید:
// click the button fireEvent.click(myBtn); // expect the bg-color to be blue expect(myBtn).toHaveStyle({ backgroundColor: "blue" }); // expect the text of button to be 'Change to red' expect(myBtn.textContent).toBe("Change to red");
نکته: وقتی یک expect با خطا روبرو می شود، FAIL می شود، به سراغ اجرای بقیه تست ها نمی رود و همانجا عملیات تست متوقف می شود.
در کد بالا، ابتدا رویداد کلیک دکمه را شبیه سازی کردیم. سپس انتظار داریم که رنگ پس زمینه دکمه به آبی تغییر کند. درنهایت هم انتظار داریم متن دکمه به “Change to red” آپدیت شود.
اما این تست مسلما FAIL می شود. زیرا همانطور که قبلا گفتیم روش ما TDD است یعنی ابتدا تست را تعریف می کنیم سپس کدی می نویسیم که تست را PASS کند.
در این مثال باید به فایل App.js خود دستوراتی را برای تغییر رنگ و متن دکمه پس از کلیک روی آن اضافه کنیم. باید از state در این کامپوننت به شکل زیر استفاده کرد. سورس کد کامل فایل App.js :
import { useState } from "react"; import "./App.css"; function App() { const [btnBgColor, setBtnBgColor] = useState("red"); const newBtnBgColor = btnBgColor === "red" ? "blue" : "red"; return ( <div className="App"> <button style={{ backgroundColor: btnBgColor }} onClick={() => setBtnBgColor(newBtnBgColor)} > Change to {newBtnBgColor} </button> </div> ); } export default App;
پس از تعریف کدهای فوق در App.js شاهد PASS شدن تست مان خواهیم بود.
تست نویسی برای چک باکس (Checkbox):
به عنوان مثال بعدی می خواهیم یک چک باکس به فایل App.js اضافه کنیم که در حالت پیش فرض (initial)، تیک نداشته باشد. در حالتی که این چک باکس تیک بخورد، دکمه فوق غیرفعال (disable) شود.
برای تست شرایط اولیه چک باکس، تست زیر را در فایل App.test.js بنویسید:
test("initial conditions", () => { render(<App />); const myBtn = screen.getByRole("button", { name: "Change to blue" }); // expect the button to be enable at first initial expect(myBtn).toBeEnabled(); const myCheckbox = screen.getByRole("checkbox"); // expect the checkbox to be disabled at first initial expect(myCheckbox).not.toBeChecked(); });
در متد تست فوق انتظار داریم که دکمه در ابتدا فعال باشد و چک باکس تیک نداشته باشد. نکته ای که در expect چک باکس در این مثال وجود دارد اینست که وقتی انتظار داریم چک باکس تیک نخورده باشد (یعنی checked نباشد)، باید از عبارت not برای false کردن checked استفاده کنیم. زیرا در jest-dom چیزی برای بررسی unChecked بودن نداریم.
اکنون برای اینکه تست فوق PASS شود، کافیست یک چک باکس به شکل زیر به فایل App.js اضافه کنیم:
<input type="checkbox" />
در ادامه می خواهیم تستی را تعریف کنیم که وقتی چک باکس تیک می خورد دکمه غیر فعال شود و بالعکس، اگر چک باکس تیک نداشته باشد، دکمه فعال باشد.
تابع تست زیر را به این منظور تعریف کنید:
test("when checkbox is checked, button is disabled", () => { render(<App />); const myCheckbox = screen.getByRole("checkbox"); const myBtn = screen.getByRole("button"); // check the checkbox fireEvent.click(myCheckbox); // expect the button to be disabled expect(myBtn).toBeDisabled(); // check the checkbox again fireEvent.click(myCheckbox); // expect the button to be disabled expect(myBtn).toBeEnabled(); });
در کد بالا، ابتدا دو المان چک باکس و دکمه را از DOM پیدا کرده ایم، سپس رویداد کلیک را روی چک باکس شبیه سازی کرده ایم. در اینحالت انتظار داریم دکمه غیرفعال باشد (expect اول) و وقتی دوباره روی چک باکس کلیک می کنیم، انتظار داریم (expect دوم) دکمه فعال شود.
اکنون برای اینکه به روش TDD عمل کنیم و تست بالا PASS شود باید کد زیر را در فایل App.js اضافه کنیم:
const [disabled, setDisabled] = useState(false); <div className="App"> <button style={{ backgroundColor: btnBgColor }} onClick={() => setBtnBgColor(newBtnBgColor)} disabled={disabled} > Change to {newBtnBgColor} </button> <input type="checkbox" defaultChecked={disabled} aria-checked={disabled} onChange={(e) => setDisabled(e.target.checked)} /> </div>
حالا تست ما با چراغ سبز مواجه می شود و به همین سادگی آن را PASS کرده ایم:
افزودن برچسب (Label) به چک باکس:
در این مرحله می خواهیم یک تگ label را برای چک باکس تعریف کنیم تا خوانایی آن را افزایش دهیم و اگر در آینده چندین چک باکس در برنامه داشتیم بتوان آنها را از هم تشخیص داد. در فایل App.test.js در آخرین متد test دستور یافتن چک باکس در DOM را بصورت زیر تغییر دهید (یک name با مقدار Disable button به آن اضافه کنید):
const myCheckbox = screen.getByRole("checkbox", { name: "Disable button" });
حال اگر دستور npm test را در ترمینال اجرا کنیم، تست ما FAIL خواهد شد و خطای زیر را می بینیم:
TestingLibraryElementError: Unable to find an accessible element with the role "checkbox" and name "Disable button"
برای رفع این خطا در فایل App.js یک تگ label بصورت زیر بعد از تگ input چک باکس تعریف کنید:
<label htmlFor="disable-button-checkbox">Disable button</label>
نکته: دقت کنید که باید یک اتریبیوت آیدی با مقدار زیر را برای چک باکس تعریف کنید تا برچسب بالا بتواند به این input متصل شود:
id="disable-button-checkbox"
حالا جواب تست ما مثبت خواهد شد (!!!) و تست را PASS کرده ایم.
تست کردن رنگ بک گراند دکمه غیرفعال:
در مرحله آخر از تست نویسی در این مقاله آموزشی می خواهیم وقتی چک باکس تیک خورد و دکمه غیرفعال شد، رنگ بک گراند دکمه خاکستری شود و همچنین آن را توسط یک expect تست کنیم. مطابق معمول، می خواهیم طبق سناریوی TDD پیش برویم. پس ابتدا تست ها را می نویسیم سپس کدهای مربوط به فایل App.js
برای پیاده سازی تست این سناریو، می توانیم از دو متد test به صورت زیر استفاده کنیم. یکی برای وقتی رنگ دکمه قرمز است و پس از غیرفعال شدن به خاکستری تبدیل می شود. و دیگری برای وقتی که رنگ دکمه آبی است و پس از غیرفعال شدن به خاکستری تبدیل می شود.
متد تست برای حالت اول (تغییر رنگ دکمه از قرمز به خاکستری و برعکس):
test("Disabled button has gray bg & turn into red", () => { render(<App />); const myCheckbox = screen.getByRole("checkbox", { name: "Disable button" }); const myBtn = screen.getByRole("button", { name: "Change to blue" }); fireEvent.click(myCheckbox); expect(myBtn).toHaveStyle("background-color: gray"); fireEvent.click(myCheckbox); expect(myBtn).toHaveStyle("background-color: red"); });
متد تست برای حالت دوم (تغییر رنگ دکمه از آبی به خاکستری و برعکس):
test("Disabled button has gray bg & turn into blue", () => { render(<App />); const myCheckbox = screen.getByRole("checkbox", { name: "Disable button" }); const myBtn = screen.getByRole("button", { name: "Change to blue" }); fireEvent.click(myBtn); fireEvent.click(myCheckbox); expect(myBtn).toHaveStyle("background-color: gray"); fireEvent.click(myCheckbox); expect(myBtn).toHaveStyle("background-color: blue"); });
برای اینکه تست های فوق PASS شوند باید کد دکمه را طبق زیر آپدیت کنیم:
<button style={{ backgroundColor: disabled ? "gray" : btnBgColor }} onClick={() => setBtnBgColor(newBtnBgColor)} disabled={disabled} > Change to {newBtnBgColor} </button>
حالا تست های ما PASS می شوند.
یونیت تست (Unit Test) و کاربرد آن:
در ین بخش می خواهیم کاربرد یونیت تست و نحوه پیاده سازی آن را در یک مثال ساده آموزش دهیم.
دو مورد از کاربردهای Unit Testing به صورت زیر است:
- وقتی یک تابع یا کامپوننتی داریم که توسط چندین کامپوننت دیگر مورداستفاده قرار می گیرد.
- وقتی یک تابع یا کامپوننتی داریم که لاجیک پیچیده و کد سنگین دارد.
به عنوان مثال فرض کنید یک تابع داریم که یک کلمه چند بخشی را بعنوان ورودی می گیرد (بصورت Camel Case) و حروف بزرگ ابتدای هر کلمه را با space جایگرین کرده و return می کند.
نکته: البته این تابع پیچیدگی خاصی ندارد. ما صرفا برای جنبه آموزشی می خواهیم این تابع را به شکل unit testing تست کنیم.
به این منظور در تابع replaceCamelCaseLetterWithSpace از regex یا regular expressions استفاده می کنیم. بدنه تابع بصورت زیر است:
export function replaceCamelCaseLetterWithSpace(word) { return word.replace(/\B([A-Z])\B/g, " $1"); }
متد describe و کاربرد آن:
سپس به سراغ پیاده سازی متد test می رویم. در اینجا می خواهیم با یک مفهوم جدید در تست نویسی آشنا شوید بنام describe. توسط این متد می توان دو یا چند متد test را ادغام کرد. در این مثال می خواهیم 3 تا متد تست را در یک describe تعریف کنیم.
ابتدا باید در فایل App.test.js تابع فوق را ایمپورت کنیم:
import { replaceCamelCaseLetterWithSpace } from "./App";
سپس سه تابع تست را مطابق کد زیر تعریف می کنیم:
describe("insert spaces before capital letters", () => { test("works for no inner capital letter", () => { expect(replaceCamelCaseLetterWithSpace("One")).toBe("One"); }); test("works for one inner capital letter", () => { expect(replaceCamelCaseLetterWithSpace("OneTwo")).toBe("One Two"); }); test("works for multiple inner capital letters", () => { expect(replaceCamelCaseLetterWithSpace("OneTwoThree")).toBe( "One Two Three" ); }); });
بنابراین تابع فوق را بصورت unit testing تست کردیم و مطمئن شدیم که برای تمام رشته های CamelCase بدرستی کار می کند و خروجی دلخواه ما را می دهد.
خلاصه مقاله:
- استفاده از متد fireEvent برای پیاده سازی تعامل المان DOM با کاربر (مانند کلیک دکمه)
- متدهای Assertion در Jest مانند toBeEnabled, toBeDisabled, toBeChecked
- یافتن المان های DOM توسط متدهای Accessibility مانند getByRole و مقدار name
- اجرای Unit Test بر روی توابع خاص و پیچیده
- متد describe و کاربرد آن در تست نویسی ری اکت
*سورس کد این مینی پروژه در گیتهاب بنده قابل مشاهده می باشد. لطفا به این repo در گیتهاب ستاره دهید.
خب دوستان در اینجا به پایان مقاله آموزش عملی تست نویسی در ری اکت با React-Testing-Library و JEST رسیدیم. امیدوارم استفاده کرده باشید. لطفا اگه از مقاله خوشتون اومد آن را با دوستان خود به اشتراک بگذارید و با ما به روش های زیر در ارتباط باشید:
- اینستاگرام rahkarino
- کانال تلگرام rahkarino_com
- فرم تماس با ما در سایت rahkarino.com
- فرم ارسال دیدگاه در انتهای مقالات
دیدگاهتان را بنویسید