Тестування компонентів в React: що і як тестувати з Jest та Enzyme

28 хв. читання
07 листопада 2018

У статті розглянемо:

  • Правильний порядок тестування компонентів на основі структури проекту.
  • Що можна не тестувати.
  • Важливість Snapshot-тестування.
  • Що тестувати у компоненті та в якому порядку.
  • Детальні приклади коду.

Для розуміння статті потрібні навички налаштування Jest та Enzyme. Детальну інформацію можна знайти на їх офіційних сайтах.

Припустимо, що вам необхідно охопити кодову базу проекту тестами. З чого почати і що слід отримати наприкінці? 100% покриття тестами? Це еталон, якого треба прагнути, але, зазвичай, він недосяжний.

Чому? Тому що вам не треба тестувати увесь код. Ми з'ясуємо, що можна залишити без тестів. Тим більше, 100% покриття тестами не гарантує якість.Не гоніться за відсотками, уникайте несправжніх тестів. Просто намагайтеся не втрачати основні деталі компонента.

Визначення правильного порядку тестування компонентів на основі структури проекту

Розглянемо частину структури проекту:

Тестування компонентів в React: що і як тестувати з Jest та Enzyme

Директорія shared тут найбільш важлива. В ній зберігаються компоненти, що використовуються на різних сторінках проекту. Вони повторно використовувані і, зазвичай, маленькі та нескладні. Якщо той чи інший компонент виходить з ладу, це призведе до збоїв в інших місцях. Тому треба упевнитись, що вони написані правильно. У директорії є декілька тек з компонентами.

Тестування компонентів в React: що і як тестувати з Jest та Enzyme

Як визначити правильний порядок тестування компонентів у директорії shared:

  • Завжди переходьте від простого до складного. Перегляньте кожну директорію та з'ясуйте які компоненти independent (тобто їх рендеринг не залежить від інших компонентів). Вони завершені і використовуються самостійно. У структурі бачимо директорію inputs, що знаходиться у folder. Там є input-компоненти для форм Redux (такі як TextInput, SelectInput, CheckboxInput, DateInput тощо).
  • Визначаємо допоміжні компоненти. Вони часто використовуються в inputs компонентах, але тестувати їх слід окремо. Компоненти у директорії utils не складні, але дуже важливі. Вони багаторазові та корисні для повторюваних дій.
  • Наступний крок: визначаємо які компоненти можна використовувати незалежно один від одного. Якщо такі є, обираємо їх для тестування. У нашій структурі це widgets — маленькі компоненти з простою функціональністю. Вони будуть третіми у черзі на тестування.
  • Проаналізуйте решту директорій та визначте більш складні компоненти, які можна використовувати незалежно або з іншими компонентами. У нас це тека modals. Пояснимо їх пізніше.
  • Найбільш складні компоненти залишились наостанок. Це директорія hoc та fields з теки forms. Як визначити що тестувати спочатку? Я беру ту директорію, де компоненти вже були залучені у протестованих компонентах. Компонент з директорії hoc був у компоненті widgets.Тому я вже знаю де і з якою метою ця директорія і компонент використовувались.
  • Залишилась тека fields, де знаходяться компоненти, пов'язані з redux-формами.

Кінцевий порядок компонентів (заснований на нашому прикладі) виглядатиме так:

Тестування компонентів в React: що і як тестувати з Jest та Enzyme

Якщо ви будете дотримуватися цього порядку, ви будете збільшувати складність компонентів крок за кроком. Коли справа доходить до більш складних компонентів, ви вже знаєте як поводяться простіші.

Не переходьте до тестування, скажімо, поля array, якщо ви не впевнені як протестувати поле text. Не беріться за компоненти, пов'язані з redux-формами, якщо ви ще не тестували поле form.

Будьте послідовними у своєму виборі: не беріть перший компонент, що приходить на думку. Звичайно, структура вашого проекту буде відрізнятися. Ви можете мати інші назви директорій чи додаткові компоненти, дії, редьюсери, але логіка вибору порядку компонентів для тестування залишиться незмінною.

Що слід оминути при тестуванні:

  • Сторонні бібліотеки. Не треба тестувати функціональність з інших бібліотек. Ви не відповідальні за цей код. Пропустіть їх або імітуйте реалізацію.
  • Константи: Назва говорить сама за себе. Вони є частиною статичного коду, що не призначений для зміни.
  • Вбудовані стилі (якщо ви використовуєте їх у своєму компоненті). Для цього необхідно продублювати об'єкт зі стилями у вашому тесті. Якщо стилі об'єкта змінюються, необхідно внести зміни до тесту. Не повторюйте код компонента у тесті. Ви забудете його змінити. Тим більше, ваші колеги ніколи не здогадаються, що відбулося дублювання. У більшості випадків, вбудовані стилі не змінюють поведінку компонента, томі їх не слід тестувати. Виняток — ваші стилі змінюються динамічно.
  • Речі, що не стосуються компонентів Будьте уважні: компонент може бути огорнутий в інший компонент. Не тестуйте обгортку — тестуйте їх окремо.

Як ви пишете тести? Я поєдную два підходи:

  • Snapshot-тестування
  • Тестування логіки компонента

Поговоримо про обидва.

Як тестувати зі знімками

Snapshot-тестування є незамінним, якщо вам треба впевнитись, що користувацький інтерфейс не змінився. Якщо ви вперше використовуєте цей вид тестування, у вас можуть виникнути питання щодо організації та управління знімками. Принцип дуже простий, але ніде повністю не описаний.

Крок 1. Напишіть тест для компонента, а у блоку except застосуйте метод .toMatchSnapshot(), який створює сам знімок:

it('render correctly text component', () => {  
    const TextInputComponent = renderer.create(<TextInput />).toJSON();
    expect(TextInputComponent).toMatchSnapshot();
});

Крок 2. При першому запуску тесту на одному рівні з ним буде створено директорію під назвою __snapshots__ з автозгенерованими файлами всередині (мають розширення .snap).

Знімки виглядатимуть так:

// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render TextInput correctly component 1`] = `  
<input  
  className="input-custom"
  disabled={undefined}
  id={undefined}
  name={undefined}
  onBlur={undefined}
  onChange={[Function]}
  pattern={undefined}
  placeholder={undefined}
  readOnly={false}
  required={undefined}
  type="text"
  value={undefined}
/>
`;

Крок 3. Помістіть знімок у репозиторій та збережіть його разом з тестом.

Якщо компонент був змінений, вам просто треба оновити знімок з прапором updateSnapshot (або коротка версія u).

Знімок створено — як він працює?

Розглянемо два випадки:

1. Компонент змінився

  • Запустіть тести.
  • Створюється новий знімок, який порівнюється з автоматично згенерованим знімком у директорії __snapshots__.
  • Тест провалюється, тому що знімки не збігаються.
Тестування компонентів в React: що і як тестувати з Jest та Enzyme

2. Компонент не змінився

  • Запустіть тести.
  • Створюється новий знімок, який порівнюється з автоматично згенерованим знімком у директорії __snapshots__.
  • Тест проходить, тому що знімки ідентичні.
Тестування компонентів в React: що і як тестувати з Jest та Enzyme

Все чудово, поки ми тестуємо маленькі компоненти без логіки (лише UI-рендеринг). Як показує практика, у реальних проектах таких компонентів дуже мало.

Чи достатньо знімків для повного тестування компонентів?

Основні інструкції для тестування компонентів

1. Один компонент повинен мати лише один знімок

Якщо один знімок не проходить, то інші, скоріш за все, також. Не створюйте і не зберігайте купу непотрібних знімків. Вони займають багато простору та збивають з пантелику розробників, які читатимуть тести після вас.

Існують винятки, коли вам необхідно протестувати поведінку компонента у двох станах. Наприклад, перед відкриттям спливного вікна та після.

Навіть такий варіант завжди можна спростити: перший тест зберігає у знімку стан компонента за замовчуванням (без спливного вікна), а другий тест імітує подію та перевіряє наявність певного класу. Так можна легко обійти створення декількох знімків.

2. Тестування props

Тестування props проходить у два етапи:

  • По-перше, перевірте рендеринг значень props за замовчуванням. Я очікую, що значення буде рівним defaultProps (якщо цей prop має defaultProps).
  • По-друге, перевірте користувацьке значення prop. Я встановлюю власне значення і очікую його повернення після рендерингу компонента.

3. Тестування типів даних

Щоб протестувати тип даних, що передається у props або тип отриманих даних, ми можемо використати спеціальну jest-extended бібліотеку. Вона містить розширений набір matches, яких немає у Jest. Бібліотека спрощує тестування типів даних.

З іншого боку, тестування прототипів є суперечливим питанням. Хтось виступатиме проти, але я наполягаю на їх тестуванні, адже функціональність самого пакета не тестується. Я лише перевіряю коректність прототипу. Типи даних надзвичайно важливі у програмуванні, тому їх не слід оминати.

4. Тестування подій

Після створення знімку та покриття props тестами, можна бути впевненим, що компоненти відображаються коректно. Але для повного покриття цього недостатньо, якщо у вас є події у компонентах.

Події можна перевірити різними способами. Найбільш поширені з них:

  • інсценування події => її імітація => очікування виклику події
  • інсценування події => її імітація з параметрами => очікування виклику події з переданими параметрами
  • передача необхідних props => відображення компонентів => симуляція події => очікування певної поведінки викликаної події

5. Тестування умов

Часто у вас є умови для виводу певного класу, рендерингу частини коду, перенесення необхідних props тощо. Не варто забувати про них, адже зі значеннями за замовчуванням тест проходитиме лише одна гілка.

У комплексних компонентах з розрахунками та численними умовами можна пропустити деякі гілки. Щоб переконатися, що усі частини коду охоплено тестами, можна використати інструмент для тестового покриття та візуально перевірити, які гілки охоплено.

6. Тестування стану

Щоб перевірити стан, зазвичай, необхідно написати два тести:

  • Один перевіряє поточний стан.
  • Інший перевіряє стан після виклику події. Рендеринг компоненту => виклик функції прямо у тесті => перевірка зміни стану. Для виклику функції компонента, треба отримати екземпляр компонента. У екземпляра викликаємо методи (приклад у наступних тестах).

Якщо слідувати інструкціям, ваш компонент буде охоплений тестами на 90-100%. 10% залишаються для спеціальних випадків, не описаних у статті.

Приклади тестів

Перейдемо до прикладів. Розглянемо тести компонентів крок за кроком.

1. Тестування компонентів з forms/inputs

Візьміть один компонент з директорії forms/inputs. Нехай це буде DateInput.js – компонент для поля дати.

Лістинг коду компонента: DateInput.js

Тестування компонентів в React: що і як тестувати з Jest та Enzyme

Компонент DateInput використовує бібліотеку react-datepicker з двома утилітами:

  • valueToDate (переводить значення у дату)
  • dateToValue (переводить дату у значення)

Пакет призначений для роботи з датою, а PropTypes для перевірки React props.

У коді компонента бачимо список props за замовчуванням, які допомагають компоненту відображатися.

const defaultProps = {  
    inputClassName: 'input-custom',
    monthsShown: 1,
    dateFormat: 'DD.MM.YYYY',
    showMonthYearsDropdowns: false,
    minDate: moment()
};

Для створення знімку підходять усі props, окрім minDate: moment(). Від moment() ми отримуємо поточну дату, тому знімок не пройде (він зберігає застарілу дату). Рішенням буде імітувати значення дати:

const defaultProps = {  
    minDate: moment(0)
}

Нам необхідний prop minDate у кожному відображеному компоненті. Щоб уникнути повторення props, я створив HOC, який отримує defaultProps і повертає гарний компонент:

import TestDateInput from '../DateInput';  
const DateInput = (props) =>  
    <TestDateInput
        {...defaultProps}
        {...props}
    />;

Не забудьте про moment-timezone, особливо якщо ваші тести будуть використовувати розробники в іншому часовому поясі. Вони отримають зімітоване значення, але з урахуванням часового поясу. Рішенням буде встановити часовий пояс за замовчуванням:

const moment = require.requireActual('moment-timezone').tz.setDefault('America/Los_Angeles')

Тепер компонент date input готовий до тестування.

1. Спочатку створіть знімок

it('render correctly date component', () => {  
    const DateInputComponent = renderer.create(<DateInput />).toJSON();
    expect(DateInputComponent).toMatchSnapshot();
});

2.Тестування props

Перегляньте props та знайдіть важливі. Перший prop, який будемо тестувати — showMonthYearsDropdowns. Якщо його значення true, з'являється випадне меню для місяця та року:

it('check month and years dropdowns displayed', () => {  
    const props = {
            showMonthYearsDropdowns: true
        },
        DateInputComponent = mount(<DateInput {...props} />).find('.datepicker');
    expect(DateInputComponent.hasClass('react-datepicker-hide-month')).toEqual(true);
});

Тестуємо null значення prop. Така перевірка обов'язкова, щоб переконатися, що компонент відображається без визначеного значення:

it('render date input correctly with null value', () => {  
    const props = {
            value: null
        },
        DateInputComponent = mount(<DateInput {...props} />);
    expect((DateInputComponent).prop('value')).toEqual(null);
});

3.Тестування прототипів, що очікують дату у форматі string

it('check the type of value', () => {  
    const props = {
            value: '10.03.2018'
        },
        DateInputComponent = mount(<DateInput {...props} />);
    expect(DateInputComponent.prop('value')).toBeString();
});

4.Тестування подій

Спершу, перевіряємо onChange подію:

  • імітуємо onChange колбек
  • рендеринг date input компонента
  • симуляція зміни події з новим цільовим значенням
  • перевіряємо з яким значенням був викликаний onChange
it('check the onChange callback', () => {  
    const onChange = jest.fn(),
        props = {
            value: '20.01.2018',
            onChange
        },
        DateInputComponent = mount(<DateInput {...props} />).find('input');
    DateInputComponent.simulate('change', { target: {value: moment('2018-01-22')} });
    expect(onChange).toHaveBeenCalledWith('22.01.2018');
});

Упевнимось, що спливне вікно з датою відкривається після кліку на date input. Для цього знайдіть поле вводу дати => зімітуйте подію кліка => очікуйте спливне вікно, якщо клас .react-datepicker наявний.

it('check DatePicker popup open', () => {  
    const DateComponent = mount(<DateInput />),
        dateInput = DateComponent.find("input[type='text']");
    dateInput.simulate('click');
    expect(DateComponent.find('.react-datepicker')).toHaveLength(1);
});

Лістинг тесту.

2. Тестування утиліти

Лістинг коду утиліти: valueToDate.js

Утиліта призначена для перетворення значення в дату користувацького формату.

Спочатку визначимо основні класи для тесту:

  • Оскільки програма перетворює значення, нам треба його перевірити:

    -Якщо значення не визначене, перевіряємо, щоб програма не повертала виключення (помилку)

    -Якщо значення визначене, необхідно перевірити, щоб програма повертала поточну дату

  • Повернене значення належить класу moment. Саме тому нам треба створити екземпляр moment.

  • Другий аргумент — dateFormat. Встановіть його як константу перед тестуванням. Вона буде передаватися у кожному тесті. Чи треба тестувати dateFormat окремо? Гадаю, що ні. Цей аргумент необов'язковий — якщо ми його не встановимо, програма не зламається, а лише поверне дату у форматі за замовчуванням. Це робота moment, а тестування сторонніх бібліотек — не наша справа. Вже було сказано, що не варто забувати про часовий пояс. Це дуже важливо для розробників з різних часових поясів.

Код:

1. Тест для першого випадку. Якщо немає значення – поле пусте

const format = 'DD.MM.YYYY';
it('render valueToDate utility with empty value', () => {  
    const value = valueToDate('', format);
    expect(value).toEqual(null);
});

2. Перевіряємо чи встановлене значення

const date = '21.11.2015',  
      format = 'DD.MM.YYYY';
it('render valueToDate utility with defined value', () => {  
    const value = valueToDate(date, format);
    expect(value).toEqual(moment(date, format));
});

3. Перевіряємо чи належить значення класу moment

const date = '21.11.2015',  
    format = 'DD.MM.YYYY';
it('check value is instanceof moment', () => {  
    const value = valueToDate(date, format);
    expect(value instanceof moment).toBeTruthy();
});

Лістинг тестів

3. Тестування віджетів

Для тесту віджетів я беру компонент spinner.

Лістинг коду віджета: Spinner,js

Тестування компонентів в React: що і як тестувати з Jest та Enzyme

Майже всі веб-ресурси мають цей компонент, тому не будемо його пояснювати.

Йдемо писати тести:

1. Спочатку – створюємо знімок:

it('render correctly Spinner component', () => {  
   const SpinnerComponent = mount(<Spinner />);
   expect(SpinnerComponent).toMatchSnapshot();
});

2. Тестуємо props:

Спочатку беремо заголовок prop за замовчуванням і перевіряємо його коректність.

it('check prop title by default', () => {  
 const SpinnerComponent = mount(<Spinner />);
    expect(SpinnerComponent.find('p').text()).toEqual('Please wait');
});

Далі перевіряємо користувацький заголовок, щоб він повертав правильно визначений prop. Погляньте на код: заголовок огортаємо у rawMarkup, а виводимо за допомогою властивості dangerouslySetInnerHTML.

Код для rawMarkup:

export default function rawMarkup(template) {  
    return {__html: template};
}

Чи потрібні тести для rawMarkup у компоненті spinner? Ні, це утиліта, тому її не треба тестувати окремо. Не переймайтеся як це працює — нам треба лише знати, що prop повертає коректний результат.

Уточнення: Ми використовуємо властивість dangerouslySetInnerHTML для наступного. Наш сайт багатомовний, і за це відповідає певна команда. Вони можуть перекладати його просто декількома словами або навіть обрамити HTML-тегами (такими як <strong>, <i>, <s> або навіть розділити текст списками <ol>, <ul>). Ми не знаємо точно як вони перекладають та оформлюють текст. Нам треба лише коректно рендерити усе.

Я об'єдную два основних випадки в одному тесті:

  • повертається коректний користувацький заголовок prop
  • коректний рендеринг заголовку prop з HTML тегами
it('check prop title with html tags', () => {  
    const props = {
            title: '<b>Please wait</b>'
        },
        SpinnerComponent = mount(<Spinner {...props} />);
    expect(SpinnerComponent.find('p').text()).toEqual('Please wait');
});

Наступний prop – subTitle. Він необов'язковий, тому він не має prop за замовчуванням. Пропускаємо етап тесту значення за замовчуванням і одразу переходимо до тестування користувацьких props:

  • Перевіряємо, щоб рендеринг subTitle prop був коректним
const props = {  
        subTitle: 'left 1 minute'
    },
    SpinnerComponent = mount(<Spinner {...props} />);
it('render correct text', () => {  
    expect(SpinnerComponent.find('p').at(1).text()).toEqual(props.subTitle);
});

Ми вже знаємо, що subTitle є необов'язковим. Саме тому нам треба перевірити, щоб він не відображався з prop за замовчуванням. Для цього перевіряємо кількість тегів <p>:

it('check subTitle is not rendered', () => {  
  const SpinnerComponent = mount(<Spinner />);
    expect(SpinnerComponent.find('p').length).toEqual(1);
});

3. Тестуємо типи prop:

  • Заголовок prop очікується у вигляді string:
it('check prop type for title is string', () => {  
    const props = {
            title: 'Wait'
        },
        SpinnerComponent = mount(<Spinner {...props} />);
    expect(SpinnerComponent.find('p').text()).toBeString();
});
  • subTitle prop також очікує string:
const props = {  
        subTitle: 'left 1 minute'
    },
    SpinnerComponent = mount(<Spinner {...props} />);
it('type for subTitle is string', () => {  
    expect(SpinnerComponent.find('p').at(1).text()).toBeString();
});

Лістинг тестів.

4. Тестування modal (ModalWrapper.js та ModalTrigger.js)

Тестування компонентів в React: що і як тестувати з Jest та Enzyme

Як тестувати modal.

Спершу я хочу пояснити як вони організовані у нашому проекті. У нас є два компоненти: ModalWrapper.js та ModalTrigger.js.

ModalWrapper відповідальний за розмітку спливного вікна. Він складається з modal-контейнера, кнопки close, modal-заголовка та тіла.

ModalTrigger відповідальний за обробку modal. Там є розмітка ModalWrapper та події для контролю розмітки (відкриття та закриття).

Розглянемо компоненти окремо:

1. Лістинг коду компонента:МodalWrapper.js.

Код:

Спочатку ModalWrapper отримує компонент та рендерить його всередині. Перевірте, щоб ModalWrapper не вийшов з ладу без компонента. Створіть знімок з prop за замовчуванням:

it('without component', () => {  
    const ModalWrapperComponent = shallow(<ModalWrapper />);
    expect(ModalWrapperComponent).toMatchSnapshot();
});

Далі здійснимо імітацію його справжнього стану за допомогою рендерингу компонента, що пройшов крізь props:

it('with component', () => {  
   const props = {
           component: () => {}
        },
        ModalWrapperComponent = shallow(<ModalWrapper {...props} />);
    expect(ModalWrapperComponent).toMatchSnapshot();
});

Тестування props

Отримуємо prop custom class name:

it('render correct class name', () => {  
    const props = {
            modalClassName: 'custom-class-name'
        },
        ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find('Modal');
        expect(ModalWrapperComponent.hasClass('custom-class-name')).toEqual(true);
});

Отримуємо prop custom title:

it('render correct title', () => {  
    const props = {
           title: 'Modal Title'
       },
       ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find('ModalTitle');
    expect(ModalWrapperComponent.props().children).toEqual('Modal Title');
});

Отримуємо коректний вигляд prop:

it('check prop value', () => {
        const props = {
               show: true
           },
           ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find('Modal');
        expect(ModalWrapperComponent.props().show).toEqual(true);
    });

Тестування прототипів

  • Для демонстрації prop
it('check prop type', () => {  
    const props = {
           show: true
        },
        ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find('Modal');
    expect(ModalWrapperComponent.props().show).toBeBoolean();
});
  • Для onHide prop
it('render correct onHide prop type', () => {  
    const props = {
            onHide: () => {}
        },
        ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find('Modal');
    expect(ModalWrapperComponent.props().onHide).toBeFunction();
});
  • Для prop компоненту
it('render correct component prop type', () => {  
   const props = {
           component: () => {}
       },
       ModalWrapperComponent = mount(<ModalWrapper {...props} />);
   expect(ModalWrapperComponent.props().component).toBeFunction();
});

Лістинг тестів.

2. Лістинг коду для компонента: ModalTrigger.js

Обгортка modal вже була протестована. Тепер треба протестувати компонент modal trigger.

Огляд компонента: він заснований на стані toggled, що сповіщає про видимість ModalWrapper. Якщо toggled: false, спливне вікно буде приховане (навіть якщо воно видиме). Функція open() відкриває спливне вікно у дочірньому елементі. Подія кліку та функція close() приховують спливне вікно для кнопки, що відображається у ModalWrapper.

Створення знімку:

it('render ModalTrigger component correctly', () => {  
    const ModalTriggerComponent = shallow(<ModalTrigger><div /></ModalTrigger>);
    expect(ModalTriggerComponent).toMatchSnapshot();
});

Чи треба тестувати Modaltrigger з рендерингом компонентів prop? Ні, тому що component буде відображений всередині ModalWrapper. Він не залежить від компонента, що тестується. Його вже було охоплено тестами при тестуванні ModalWrapper.

Тестування props

У нас є один prop children, і ми хочемо впевнитись, що є лише один child.

it('ensure to have only one child (control element)', () => {  
    expect(ModalTriggerComponent.findWhere(node => node.key() === 'modal-control').length).toEqual(1);
});

Тестування прототипів

child prop повинен бути об'єктом, тому перевіримо наступний тест:

const ModalTriggerComponent = mount(<ModalTrigger><div /></ModalTrigger>);
it('check children prop type', () => {  
      expect(ModalTriggerComponent.props().children).toBeObject();
});

Важливо перевіряти стани у компоненті ModalTrigger.

У нас є два стани:

  • Спливне вікно відкрите. Щоб дізнатися, що modal відкритий, треба перевірити його стан. Для цього викликаємо open() у екземпляра компонента та очікуємо, що toggled встановлено true.
it('check the modal is opened', () => {  
    const event = {
        preventDefault: () => {},
        stopPropagation: () => {}
    };
    ModalTriggerComponent.instance().open(event);
    expect(ModalTriggerComponent.state().toggled).toBeTruthy();
});
  • Спливне вікно закрите. Тут навпаки: toggled повинен бути false.
it('check the modal is closed', () => {  
   ModalTriggerComponent.instance().close();
   expect(ModalTriggerComponent.state().toggled).toBeFalsy();
});

Лістинг тестів.

Порада для тестування компонентів, що залежать один від одного: спершу перегляньте компоненти та напишіть план тесту, визначте що треба протестувати в кожному компоненті, перевірте можливі випадки та переконайтеся, що ви не повторили той самий тест в обох компонентах. Уважно проаналізуйте можливі та оптимальні варіанти тестів.

5. Тестування HOC (Компонентів вищого порядку)

Останні два пункти (HOC та тестування полів форм) взаємопов'язані. Я хочу поділитися як тестувати макет полів з його HOC.

Пояснимо що таке BaseFiedLayout, чому нам потрібен цей компонент і де його використовувати:

  • BaseFiedLayout.js — обгортка для input компонентів у формі, таких як TextInput, CheckboxInput, DateInput, SelectInput тощо. Їх назви містять Input, тому що ми використовуємо пакет redux-form.
  • BaseFiedLayout потрібен для створення шаблону для компонентів поля форми, тобто рендерингу міток, підказок, префіксів (скорочення валюти, квадратного метра тощо), іконок тощо.
  • BaseFiedHOC.js потрібен для огортання inputComponent у макет поля та для з'єднання з redux-form за допомогою <Field/> компоненту.

Лістинг коду компонента: BaseFieldHOC.js

Це HOC, який отримує input компонент форми та повертає компонент, з'єднаний з redux-form.

Проаналізуємо HOC:

  • Компонент отримує лише один prop — component. Спочатку треба створити цей компонент та огорнути його у BaseFieldHOC.
  • Щоб отримати поле, з'єднане з redux-form, декоруємо огорнений HOC.
  • Проводимо рендеринг цього поля всередині компонента React Redux <Provider>, щоб сховище було доступним для протестованого компонента. Для імітації сховища:
const store = createStore(() => ({}));

Перед кожним тестом робимо:

let BaseFieldHOCComponent;
beforeEach(() => {  
    const TextInput = () => { return 'text input'; },
        BaseFieldHOCWrapper = BaseFieldHOC(TextInput),
        TextField = reduxForm({ form: 'testForm' })(BaseFieldHOCWrapper);
    BaseFieldHOCComponent = renderer.create(
        <Provider store={store}>
            <TextField name="text-input" />
        </Provider>
    ).toJSON();
});

Тепер компонент готовий до тестування:

  • Створіть знімок:
it('render correctly component', () => {  
    expect(BaseFieldHOCComponent).toMatchSnapshot();
});
  • Переконайтеся, що input компонент огорнутий у BaseFieldLayout після рендерингу:
it('check input component is wrapped in BaseFieldLayout', () => {  
    expect(BaseFieldHOCComponent.props.className).toEqual('form-group');
});

Ось і все — ми охопили HOC. Якщо ви тестуєте компоненти, з'єднані з redux-form, то найскладнішим буде підготувати поле (задекорувати його у redux form та налаштувати сховище). Усе інше простіше — лише слідуйте інструкції.

Лістинг тестів.

6. Тестування форм/полів

Можемо перейти до компонента BaseFieldLayout.

Лістинг коду компонента: BaseFieldLayout.js

Напишемо тести згідно з інструкцією вище:

1. Створюємо знімок.

Цей компонент не буде відображений без defaultProps:

  • inputComponent
  • Props, надані redux-form: об'єкти input і meta. input з властивістю name, і meta з властивостями error і touched:
const defaultProps = {  
   meta: {
        touched: null,
        error: null
    },
    input: {
        name: 'field-name'
    },
    inputComponent: () => { return 'test case'; }
}

Щоб використовувати defaultProps у тесті обгорток:

import TestBaseFieldLayout from '../BaseFieldLayout';
const BaseFieldLayout = (props) => <TestBaseFieldLayout {...defaultProps} {...props} />;

Тепер ми готові зробити знімок:

it('render correctly BaseFieldLayout component', () => {  
    const BaseFieldLayoutComponent = renderer.create(<BaseFieldLayout />).toJSON();
    expect(BaseFieldLayoutComponent).toMatchSnapshot();
});

2. Тестування props

У цього компонента багато props. Я покажу декілька прикладів, а усі інші тестуються за аналогією.

  • Переконайтеся, що prop icon відображається правильно:
it('render correctly icon prop', () => {  
    const props = {
            icon: <span className="icon-exclamation" />
        },
        BaseFieldLayoutComponent = mount(<BaseFieldLayout {...props} />);
        expect(BaseFieldLayoutComponent.find('span').hasClass('icon-exclamation')).toBeTruthy();
});
  • Переконайтеся, що вміст спливної підказки відображається поруч з назвою.
const props = {  
        labelTooltipContent: 'tooltip for label'
    },
    BaseFieldLayoutComponent = mount(<BaseFieldLayout {...props} />);
it('check prop is rendered', () => {  
   expect(BaseFieldLayoutComponent.find('span').hasClass('tooltip-icon')).toBeTruthy();
});
  • Тестуємо prop fieldLink
  • Переконаємось, що fieldLink за замовчуванням null
it('check prop is null by default', () => {  
    const BaseFieldLayoutComponent = shallow(<BaseFieldLayout />);
    expect(BaseFieldLayoutComponent.props().fieldLink).toBe(null);
});
  • Переконаємось, що fieldLink відображається коректно з користувацьким значенням

3. Тестування помилок:

it('check if field has error', () => {  
    const props = {
            meta: {
                touched: true,
                error: 'This field is required'
            }
        },
        BaseFieldLayoutComponent = mount(<BaseFieldLayout {...props} />);
    expect(BaseFieldLayoutComponent.find('.error')).toHaveLength(1);
});

Лістинг тестів.

Висновок

Тепер ви знаєте як провести повне тестування компонентів, засноване на структурі проекту. Я намагався пояснити з власного досвіду що і в якому порядку необхідно тестувати, а що можна оминути. Прикладом слугували декілька компонентів. З їх допомогою я визначив послідовність тестування кодової бази.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація