تست کامپوننت‌های React با استفاده از Enzyme و Jest
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 6 دقیقه

تست کامپوننت‌های React با استفاده از Enzyme و Jest

Enzyme یک ابزار آزمایش JavaScript اوپن سورس، ساخته شده توسط Airbnb است که نوشتن آزمایشات برای React را جالب‌تر و لذت‌بخش‌تر می‌کند. در این مقاله، نحوه نوشتن آزمایش برای React با استفاده از Enzyme و Jest را خواهیم دید.

برای شروع، باید خود را با این دو مورد آشنا کنید:

  1. React - یک کتابخانه JavaScript برای ساخت رابط کاربری لذت‌بخش.
  2. Jest - یک فریم‌وورک آزمایش JavaScript.

جدول محتویات:

  1. چرا Jest؟
  2. راه‌اندازی برنامه React خود
  3. رندر کردن سطحی
  4. رندر کردن DOM کامل
  5. رندر کردن استاتیک
  6. نتیجه گیری:

چرا Jest؟

Jest یک ابزار آزمایش JavaScript سریع، ساخته شده توسط Facebook است که شما را قادر می‌سازد تا بدون هیچ‌گونه پیکربندی، شروع به آزمایش کد JavaScript خود کنید.

این به این معنی است که شما می‌توانید به راحتی عملیات‌هایی مانند code-coverage را به سادگی و با استفاده از گزینه --coverage اجرا کنید.

راه‌اندازی برنامه React خود

در ابتدا، با استفاده از cerate-react-app یک برنامه React جدید بسازید:

create-react-app enzyme-tests
cd enzyme-tests
yarn start

راه‌اندازی Enzyme

برای شروع کار با Enzyme، با استفاده از yarn یا npm، این کتابخانه را به عنوان یک Dependency نصب کنید:

yarn add --dev enzyme enzyme-adapter-react-16

دقت کنید که به یک آداپتور را نیز به همراه Enzyme نصب می‌کنیم، که با نسخه React نصب شده توسط create-react-app تطابق دارد.

اگر از آخرین نسخه React استفاده نمی‌کنید، Enzyme آداپتور‌هایی برای تمام نسخه‌های React، از 0.13.0 تا 16.0.0 نیز دارد.

src/enzyme.js:

import Enzyme, { configure, shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });
export { shallow, mount, render };
export default Enzyme;

نگران خروجی‌های نام‌گذاری شده Enzyme در اینجا نباشید. بعدا با آن‌ها سر و کار خواهیم داشت.

در آخر، دو پوشه به نام‌های components و components/__tests__ داخل شاخه src‌ در جایی که کامپوننت‌ها و آزمایش‌های شما قرار خواهند داشت، بسازید.

رندر کردن سطحی

رندر کردن سطحی، پایه‌ترین نوع آزمایش با Enzyme است. همانطور که از نامش پیداست، رندر کردن سطحی محدودیتی در حد کامپوننت مورد نظر، و نه زیر مجموعه‌های آن دارد.

این نوع آزمایش، در سناریوهای مختلفی به کار می‌آید:

  1. برای کامپوننت‌های نمایشی که فقط ویژگی‌ها را رندر می‌کنند، نیازی به رندر کردن زیر مجموعه‌ها نیست.
  2. برای کامپوننت‌هایی که زیر مجموعه‌هایشان در عمق زیادی قرار دارند، یک تغییر در رفتار آن زیر مجموعه‌ها نباید نحوه رفتار کامپوننت‌ اصلی را تحت تاثیر قرار دهند.

برای این بخش، آزمایش یک کامپوننت نمایشی با رندر کردن سطحی را نشان خواهیم داد.

نگاهی به کامپوننت List در زیر، که یک ویژگی items را می‌گیرد و آن‌ها را در یک لیست نامرتب نشان می‌دهد، داشته باشید.

src/components/List.js

import React from 'react';
import PropTypes from 'prop-types';

/**
 * Render a list of items
 *
 * @param {Object} props - List of items
 */
function List(props) {
  const { items } = props;
  if (!items.length) {
    // No Items on the list, render an empty message
    return <span className="empty-message">No items in list</span>;
  }

  return (
    <ul className="list-items">
      {items.map(item => <li key={item} className="item">{item}</li>)}
    </ul>
  );
}

List.propTypes = {
  items: PropTypes.array,
};

List.defaultProps = {
  items: [],
};

export default List;

بیایید تعدای آزمایش برای این کامپوننت اضافه کنیم.

/src/components/tests/List.test.js

import React from 'react';
import { shallow } from '../enzyme';

import List from './List';

describe('List tests', () => {

  it('renders list-items', () => {
    const items = ['one', 'two', 'three'];
    const wrapper = shallow(<List items={items} />);

    // Expect the wrapper object to be defined
    expect(wrapper.find('.list-items')).toBeDefined();
    expect(wrapper.find('.item')).toHaveLength(items.length);
  });

  it('renders a list item', () => {
    const items = ['Thor', 'Loki'];
    const wrapper = shallow(<List items={items} />);

    // Check if an element in the Component exists
    expect(wrapper.contains(<li key='Thor' className="item">Thor</li >)).toBeTruthy();
  });

  it('renders correct text in item', () => {
    const items = ['John', 'James', 'Luke'];
    const wrapper = shallow(<List items={items} />);

    //Expect the child of the first item to be an array
    expect(wrapper.find('.item').get(0).props.children).toEqual('John');
  });
});

این مجموعه آزمایش، یک متد shallow را از پیکربندی‌ای که در بخش قبلی ساختیم وارد می‌کند، کامپوننت List را جمع‌بندی می‌کند و یک نمونه از کامپوننت رندر شده را بر می‌گرداند.

رندر کردن DOM کامل

در بخش آخر، توانستیم کامپوننت List را رندر سطحی کنیم و آزمایشاتی بنویسیم که متون اصلی را در تگ‌های li نمایش دهند. حال در این بخش، نگاهی به رندر کردن DOM کامل با اعمال برخی تغییرات به کامپوننت مورد نظر، خواهیم داشت.

src/components/ListItem.js

import React from 'react';
import PropTypes from 'prop-types';

/**
 * Render a single item
 *
 * @param {Object} props
 */
function ListItem(props) {
  const { item } = props;
  return <li className="item">{item}</li>;
}

ListItem.propTypes = {
  item: PropTypes.string,
};

export default ListItem;

با استفاده از این کامپوننت جدید، تگ li را با آن کامپوننت جایگزین کنید.

src/components/List.js

...
import ListItem from './ListItem';
...

return (
    <ul className="list-items">
      {items.map(item => <ListItem key={item} item={item} />)}
    </ul>
  );

حال بیایید آزمایشاتی که در بخش قبلی نوشتیم را اجرا کنیم و ببینیم که چه اتفاقی می‌افتد. اگر این کارها را درست انجام داده باشید، آزمایشات شما نیز باید به بدی آزمایشات من شکست بخورند.

چرا باید این اتفاق بیفتد؟ رابط کاربری که اصلا تغییری نکرد. تنها کاری که انجام دادیم، جابه‌جا کردن برخی موارد بود. جمع‌کننده Enzyme یک متد debug را به کار می‌گیرد، که ما را قارد می‌سازد تا به نمونه جمع‌بندی شده کامپوننت خود نگاهی داشته باشیم و ببینیم که مشکل در کجاست.

بیایید یک log به آزمایش خود اضافه کنیم.

/src/components/tests/List.test.js

...

it('renders list-items', () => {

    const items = ['one', 'two', 'three'];

    const wrapper = shallow(<List items={items} />);

    // بیایید ببینیم که در نمونه ما چه مشکلی پیش آمد

    console.log(wrapper.debug());

    // منتظر باش تا آبجکت جمع‌کننده تعریف شود

    expect(wrapper.find('.list-items')).toBeDefined();

    expect(wrapper.find('.item')).toHaveLength(items.length);

  });

...

آزمایشات را دوباره اجرا کرده و نگاهی به خروجی ترمینال داشته باشید. در اینجا می‌توانید نمونه log کامپوننت خود را ببینید.

همانطور که می‌توانید ببینید، متد جمع‌کننده زیرمجموعه ListItem را رندر نمی‌کند. از این رو، آزمایشاتی که به دنبال یک کلاس یا عنصر li می‌گشتند، با شکست مواجه شدند.

ممکن است که رندر کردن سطحی چنین کامپوننت ساده‌ای که در آن زیر مجموعه‌ها، کامپوننت‌های نمایشی هستند، ضروی به نظر نیاید، اما وقتی که در حال نوشتن آزمایشات برای کامپوننت‌هایی که توسط کتابخانه‌هایی مانند connect یا reduxForm در react-redux جمع‌بندی می‌شوند هستید، بسیار کاربردی هستند.

هدف در اینجا، این است که مجبور نشویم کارهای داخلی این کامپوننت‌های سطح بالا را آزمایش کنیم، از این رو نیازی نیست خودمان را با رندر کردن آن‌ها نگران کنیم.

حال بیایید این مشکل را بر طرف کنیم. ما می‌توانیم گشتن به دنبال عناصر li و تگ ListItem در آزمایش خود را متوقف کنیم.

/src/components/tests/List.test.js

...

it('renders list-items', () => {

    const items = ['one', 'two', 'three'];

    const wrapper = shallow(<List items={items} />);

    // منتظر باش تا آبجکت جمع‌کننده تعریف شود

    expect(wrapper.find('ListItem')).toBeDefined();

    expect(wrapper.find('ListItem')).toHaveLength(items.length);

  });

...

در این مورد، در واقع می‌خواهیم که کل شاخه‌های زیر مجوعه‌ها در کامپوننت List را آزمایش کنیم. پس در عوض، کامپوننت shallow را با mount جایگزین می‌کنیم. Mount ما را قادر می‌سازد تا یک رندر کامل را اجرا کنیم. در اینجا، نمونه کد بروزرسانی شده و log نمونه دیباگ را می‌بینید.

/src/componets/tests/List.test.js

import React from 'react';
import { mount } from '../enzyme';
import List from './List';

describe('List tests', () => {

  it('renders list-items', () => {

    const items = ['one', 'two', 'three'];
    const wrapper = mount(<List items={items} />);

    // بیایید ببینیم که در نمونه ما چه مشکلی پیش آمد
    console.log(wrapper.debug());

    // منتظر باش تا آبجکت جمع‌کننده تعریف شود
    expect(wrapper.find('.list-items')).toBeDefined();
    expect(wrapper.find('.item')).toHaveLength(items.length);

  });

  ...

});

همانطور که می‌توانید ببینید، API رندر mount، در حال رندر کردن کل DOM، شامل زیرمجموعه‌هایش است. و به این صورت، مشکل ما بر طرف شده است.

رندر کردن استاتیک

رندر کردن استاتیک به مانند shallow و mount عمل می‌کند، اما به جای برگرداندن نمونه خروجی رندر شده، HTML رندر شده را بر می‌گرداند. این مورد بر پایه Cheerio ساخته شده است.

برای رندر کردن استاتیک، نیازی به دسترسی به متدهای ای‌پی‌آی Enzyme مانند contains و debug ندارید. البته، در عوض نیاز به دسترسی کامل به متد های Cheerio مانند addClass و find خواهید داشت.

برای رندر کردن استاتیک یک کامپوننت React، متد رندر را به مانند قطعه کد زیر، وارد کنید.

/src/components/tests/List.test.js

import React from 'react';

import { render } from '../enzyme';

import List from './List';

import { wrap } from 'module';

describe('List tests', () => {

  it('renders list-items', () => {

    const items = ['one', 'two', 'three'];

    const wrapper = render(<List items={items} />);

    wrapper.addClass('foo');

    // منتظر باش تا آبجکت جمع‌کننده تعریف شود

    expect(wrapper.find('.list-items')).toBeDefined();

    expect(wrapper.find('.item')).toHaveLength(items.length);

  });

  ...

});

نتیجه گیری:

در این مقاله، توانستیم روش‌های مختلف استفاده از Jest و Enzyme برای آزمایش کامپوننت‌های React را ببینیم. امیدوارم بتوانید از این روش‌ها در پروژه‌های خود استفاده کنید.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
در انتظار ثبت رای

/@er79ka

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید