איך לבדוק Custom Hook בריאקט
ספריית react-testing-library מספקת כלים מצוינים לבדיקת קומפוננטות - אבל מה עושים עם Custom Hooks? האם עלינו להשאיר אותם לגורלם להישבר ללא בדיקות?
מסתבר שיש פיתרון ואפילו פשוט - במקום לכתוב קוד שבודק את ה Hook, אנחנו כותבים קומפוננטה שמשתמשת ב Hook ובודקים את הקומפוננטה. בואו נראה דוגמה.
1. ה Hook שאני רוצה לבדוק: useLocalStorage
אחד ההוקים האהובים עליי בריאקט נראה כמו useState אבל מוסיף עליו אפקט כך שהמידע גם יישמר ב Local Storage. בפעם הבאה שיפעילו את הקומפוננטה היא תוכל לטעון את המידע מה local storage והמשתמש יוכל להמשיך את העבודה מאותה נקודה. הנה הקוד:
import { useEffect, useState, useCallback } from 'react';
export function useLocalStorage(key, initialValue) {
const oldValue = localStorage.getItem(key);
const [value, setValue] = useState(oldValue ? JSON.parse(oldValue) : initialValue);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [value]);
const clearValue = useCallback(function clearValue() {
localStorage.removeItem(key);
}, [key]);
return [value, setValue, clearValue];
}
ה Hook מחזיר שלוש פונקציות: שתיים ראשונות זהות למה ש useState מחזיר והשלישית היא פונקציה שמוחקת את הערך מה local storage. הערך עובר JSON.stringify לפני השמירה כדי שאפשר יהיה לשמור מידע מכל טיפוס נתונים.
2. קוד הבדיקה: קומפוננטת עזר
בשביל לבדוק את ה Hook אני פותח קובץ חדש בשם useLocalStorage.test.js ובתוכו מגדיר קומפוננטה פשוטה פנימית שתשתמש ב Hook:
import { render } from '@testing-library/react';
import { useLocalStorage } from './useLocalStorage';
import userEvent from '@testing-library/user-event'
function Dummy() {
const [text, setText, clearText] = useLocalStorage('__test_text', 'hello world');
return (
<div>
<p>dummy app: {text}</p>
<button onClick={() => setText('Ouch!')}>set</button>
<button onClick={() => clearText()}>clear</button>
</div>
);
}
מה שחשוב שהקומפוננטה תשתמש בכל היכולות של ה Hook. בעולם האמיתי הייתי יוצר עוד כמה קומפוננטות כאלה, אחת שתשמור מספרים, אחת שתשמור מערך וכך הלאה.
את קוד הבדיקה אני כותב בהמשך הקובץ והוא בסך הכל משתמש בכפתורים של הקומפוננטה ובודק שהכל מתנהג כמו שצריך. בדוגמה שלנו אפשר לבדוק ששינויים בערך גם נכתבים ל Local Storage:
test('initial value is saved in local storage', () => {
const screen = render(<Dummy />);
const textElement = screen.getByText(/hello world/i);
expect(textElement).toBeInTheDocument();
expect(JSON.parse(localStorage.getItem('__test_text'))).toEqual('hello world');
});
test('changed value is saved in local storage', () => {
const screen = render(<Dummy />);
const setButton = screen.getByText(/set/i);
userEvent.click(setButton);
expect(JSON.parse(localStorage.getItem('__test_text'))).toEqual('Ouch!');
});
test('clear value removes the value from local storage', () => {
const screen = render(<Dummy />);
const clearButton = screen.getByText(/clear/i);
userEvent.click(clearButton);
expect(localStorage.getItem('__test_text')).toBe(null);
});
או שאם יש כבר ערך ב Local Storage אז נטען אותו ונשתמש בו לתצוגה:
test('existing value is read', () => {
localStorage.setItem('__test_text', JSON.stringify('lazy dog'));
const screen = render(<Dummy />);
const textElement = screen.getByText(/lazy dog/i);
expect(textElement).toBeInTheDocument();
});
ככל שתגדירו יותר קומפוננטות בדיקה פנימיות (ותמיד באותו קובץ הבדיקה) כך תוכלו לבדוק יותר התנהגויות שונות של ה Hook שלכם ולהיות בטוחים שהוא יעבוד בכל סיטואציה.