• בלוג
  • זהירות - קוד עובד לפניך!

זהירות - קוד עובד לפניך!

24/08/2021

המדד הכי גרוע לאיכות קוד הוא "עובד/לא עובד" ואני תמיד אעדיף קוד נכון שלא עובד על פני קוד עובד ולא נכון. ועם זה הלכתי לקשקש קצת עם JavaScript היום ובפרט עם testing-library. שימו לב לבדיקה הבאה שעוברת:

it('Increases after I click the button', async () => {
  const screen = render(Counter);
  const button = screen.getByRole('button', { name: 'Click Here' });
  await userEvent.click(button);

  expect(screen.getByText(/clicked 1 times/)).toBeTruthy();
});

זה קוד בדיקה למונה לחיצות שמוודא שאחרי שלחצתי על כפתור יופיע הטקסט clicked 1 times.

חדי העין שבין הקוראים יכולים לראות את הבעיה בשורה הרביעית של הקוד:

await userEvent.click(button);

זה רעיון יפה רק ש userEvent.click לא מחזיר Promise. קוד הבדיקה הבא (שגם עובד) הוא שקול לתוכנית הראשונה:

it('Increases after I click the button', async () => {
  const screen = render(Counter);
  const button = screen.getByRole('button', { name: 'Click Here' });
  userEvent.click(button);
  await undefined;

  expect(screen.getByText(/clicked 1 times/)).toBeTruthy();
});

אה וצריך לציין - אם מוחקים את שורת ה await ונשארים רק עם זה:

it('Increases after I click the button', async () => {
  const screen = render(Counter);
  const button = screen.getByRole('button', { name: 'Click Here' });
  userEvent.click(button);

  expect(screen.getByText(/clicked 1 times/)).toBeTruthy();
});

הבדיקה נכשלת.

למעשה הקריאה ל await undefined, בדיוק כמו גירסה מבוססת הבטחות של setTimeout(0) היא דרך לפצל את הפונקציה לשני חלקים ולתת למנוע "לנשום" בין שניהם כדי שהוא יוכל לסיים פעולות אסינכרוניות אחרות. וזה רק עניין של מזל ושל מימוש מנוע JavaScript ספציפי בגללו הקוד הזה עבד.

מה צריך במקום? להבין שהבעיה היא לא ב click אלא בזמן ש Vue צריך אחרי ה click כדי לעדכן את הממשק, ולהוסיף את ההמתנה בשורה הבאה שמחפשת את האלמנט. קוד ברור ונכון יותר לאותה בדיקה יראה כך:

it('Increases after I click the button', async () => {
  const screen = render(Counter);
  const button = screen.getByRole('button', { name: 'Click Here' });
  userEvent.click(button);

  expect(screen.findByText(/clicked 1 times/)).toBeTruthy();
});