איך להוסיף בדיקות יחידה ליישום React ו Redux

30/01/2020

אתם כבר יודעים כמה אני אוהב בדיקות יחידה ובמיוחד כאלה שרצות אוטומטית כל פעם שאני מנסה לעשות שטויות ולהעלות קוד שבור ל Production. ספריית הבדיקה Jest מבית פייסבוק נועדה לכתיבת בדיקות יחידה ליישומי React ו Redux (למרות שאפשר להשתמש בה כדי לבדוק כל קוד צד-לקוח). כשהם רק פירסמו אותה הספריה היתה מאוד לא בשלה והרבה זמן התרחקתי ממנה והעדפתי את מוקה. היום המצב שונה ואני חושב שכבר אפשר להמליץ עליה ולצרף דף הוראות שיעזור לכם להוסיף אותה ליישומי React ו Redux שלכם.

אבל לפני הכל - מה Jest עוזרת לנו? אז Jest היא Test Runner שזה אומר שהיא לוקחת את הקוד שלכם, שולחת אותו ל Babel ואז מריצה אותו בתוך Node.JS כדי לראות אם הכל עובד כמו שצריך. זה אומר שאתם לא צריכים אפילו לפתוח דפדפן כדי לוודא שלא שברתם שום לוגיקה, ואפשר משורת הפקודה להפעיל את הבדיקה ולקבל:

➜  my-jsoneditor git:(master) npm run test

> my-jsoneditor@1.0.0 test /Users/ynonperek/work/projects/intel/my-jsoneditor
> jest

 PASS  src/redux/reducer.test.js
 PASS  src/label.test.js

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.723s
Ran all test suites.

ג'סט מזהה לבד את קבצי הבדיקה לפי Naming Convention, כולל את כל מנגנוני ה Assertions שהתרגלנו למצוא במקומות אחרים ויודע לעבוד גם עם קומפוננטות ריאקט וגם עם קוד חיצוני.

ג'סט משתמש ב jsdom כדי לאפשר לכם לכתוב גם קוד שצריך את הרכיבים של הדפדפן ולכן לא היתה לו בעיה להריץ לי למשל בדיקה כמו זו:

test('browser dont work', () => {
  expect(document.body).toBeTruthy();
});

נשמע טוב? בואו נלך להתקין אותו ולכתוב כמה בדיקות.

1. שילוב Jest לפרויקט

בשביל לשלב את Jest בפרויקט צריך קודם להתקין את הספריות עם:

npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer enzyme-adapter-react-16

לאחר מכן יש לעדכן את הקובץ package.json ולהוסיף את התוכן הבא לאזור הסקריפטים:

  "scripts": {
    "test": "jest"
  },

והשלב השלישי הוא לעדכן את Jest כך שישתמש ב Babel לפני הרצת הטסטים. בשביל זה יצרתי קובץ בשם babel.config.js עם התוכן הבא:

// babel.config.js
module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-react'],
};

אם כל זה במקום אתם מוכנים לכתוב את קובץ הבדיקה הראשון שלכם. אני הלכתי על יישום redux ולכן הוספתי קובץ בשם reducer.test.js. המילה test באמצע הקובץ מסמנת ל Jest שזהו קובץ בדיקה. תוכן הקובץ נראה כך:

import reducer, { initialState } from './reducer';

test('initial data is sent when state is null', () => {
  expect(reducer(undefined, {})).toBe(initialState);
});

הוא מושך את ה Reducer ואת ה Initial State ומוודא שלא שכחתי להחזיר את הסטייט הראשוני אם ה Reducer מופעל בלי סטייט.

הפעלה של הטסט עם npm run test ותוכלו לראות טסט אחד שעבר בהצלחה.

2. חיבור אנזים ובדיקת קומפוננטת ריאקט

ג'סט כולל תמיכה ב React באמצעות ספריה נוספת שנקראת enzyme. בשביל לחבר את החוטים יש להיכנס לקובץ package.json ולהוסיף שם בתור מפתח ראשי חדש את הערך jest באופן הבא:

  "jest": {
    "setupFilesAfterEnv": [
      "<rootDir>src/setupTests.js"
    ]
  },

לאחר מכן יש ליצור בתיקיית src קובץ בשם setupTests.js שהוא יהיה קובץ האתחול של אנזים. בקובץ נרשום את התוכן הבא:

// setup file
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

זה מספיק בשביל שנוכל לרנדר קומפוננטת ריאקט לתוך הזיכרון ולבדוק שהיא כוללת את כל המידע שהתכוונו שיהיה בה. בשביל הבדיקה בניתי קומפוננטה פשוטה בשם label:

import React from 'react';

export default function Label(props) {
  return <label>{props.text}</label>
}

אוסף ליישום שלי קובץ חדש בשם label.test.js שיבדוק את הקומפוננטה. תוכן הקובץ:

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

test('Label shows text', () => {
  // Render a checkbox with label in the document
  const label = shallow(<Label text="hi" />);

  expect(label.text()).toEqual('hi');
});

הפונקציה shallow של אנזים מרנדרת קומפוננטה לזיכרון, ואחרי זה אפשר לקרוא לפונקציות נוספות כדי לבדוק את התוצאה. לדוגמא הפונקציה text מחזירה את הטקסט שייכתב בתוך האלמנט, והפונקציה find מחזירה תת-עץ מתוך האלמנט.הנה דוגמא קצת יותר מתוחכמת מתוך התיעוד של אנזים לדברים שתוכלו לעשות עם הספריה:

  it('simulates click events', () => {
    const onButtonClick = sinon.spy();
    const wrapper = shallow(<Foo onButtonClick={onButtonClick} />);
    wrapper.find('button').simulate('click');
    expect(onButtonClick).to.have.property('callCount', 1);
  });

הבדיקה מוודאת שהקומפוננטה Foo באמת מפעילה את ה Callback שהעברנו לה כל פעם שמישהו לוחץ על הכפתור שבתוך הקומפוננטה. אנזים מפותחת ומתוחזקת ב Airbnb ויש בה כל מה שאפשר לדמיין וקצת יותר. התיעוד המלא זמין בעמוד הפרויקט בקישור https://airbnb.io/enzyme/.

וכמובן כל קוד הדוגמא עליו ביססתי את הפוסט זמין בגיטהאב כך שתוכלו להעתיק אליכם אם משהו מההסברים לא היה מספיק ברור. הקישור: https://github.com/ynonp/react-jsoneditor-demo.