Try   HackMD

處理非同步

如有要調用 API 在等待回傳值的過程,並等待畫面重新渲染,或是等待頁面上的某元素消失時(例如 Loading 過場效果),這些時機點可能就需要使用到非同步的測試方法!


處理非同步

  • WaitFor

    • React Testing Library 提供的方法
    • 會回傳 Promise ,而在等待的時間 WaitFor可以被調用多次(調用頻率取決於 interval的設定,默認為 50ms),會等待 callback 函式的 Promise 被拋出,或 timeout超時(默認為一秒)
    ​​​​    function waitFor<T>(
    ​​​​      callback: () => T | Promise<T>,
    ​​​​      options?: {
    ​​​​        container?: HTMLElement
    ​​​​        timeout?: number
    ​​​​        interval?: number
    ​​​​        onTimeout?: (error: Error) => Error
    ​​​​        mutationObserverOptions?: MutationObserverInit
    ​​​​      },
    ​​​​    ): Promise<T>
    

    waitFor 前方可以加 await 或不加 await

    ​​​​waitFor(() => {
    ​​​​    // 非同步事件執行完成
    ​​​​    // ex API 回傳值回傳並渲染成功
    ​​​​});
    
    • FindBy

      • GetBy及 WaitFor的結合體,可以協助進行非同步的等待外也能協助我們 query DOM,使用方式如下:

        ​​​​​​​​​​​​// 當 DOM 元素內容不會立刻出現時,可透過 findBy.. 等待內容出現
        ​​​​​​​​​​​​await screen.findByText('期望出現的內容');
        
    • waitForElementToBeRemoved

      • waitFor 要等待元素從 DOM 上移除可以透過 waitForElementToBeRemovedwaitForElementToBeRemovedwaitFor 底下的小包裝器,使用方法也相似:

        ​​​​​​​​​​​​waitForElementToBeRemoved(() => {
        ​​​​​​​​​​​​	// 某元素消失
        ​​​​​​​​​​​​});
        

實際練習

底下程式碼在尚未獲取 api 資料時會顯示 Loading :

import React, { useState, useEffect } from "react";
import axios from "axios";

const TodoList = () => {
  const [isLoding, setLoading] = useState(true);
  const [todoData, setTodoData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await axios.get(
        `https://jsonplaceholder.typicode.com/todos`
      );
      setTodoData(response.data);
      setLoading(false);
    };
    fetchData();
  }, []);

  return (
    <>
      {isLoding && <p> loading ...</p>}
      <ul>
        {todoData &&
          todoData.map((item) => <li key={item.id}>{item.title}</li>)}
      </ul>
    </>
  );
};
export default TodoList;

透過該程式碼來撰寫測試,判斷是否有成功撈取 api 顯示在頁面上:

// 記得引入方法
import { render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import TodoList from "./Loading";

// delectus aut autem 為 call api 的其中一項回傳值
// 判斷他有渲染成功

test("WaitFor", async () => {
  render(<TodoList />);
  await waitFor(() => {
    expect(screen.getByText("delectus aut autem")).toBeInTheDocument();
  });
});

test("FindBy", async () => {
  render(<TodoList />);
  const element = await screen.findByText("delectus aut autem");
  expect(element).toBeInTheDocument();
});

也可透過 waitForElementToBeRemoved 來判斷 Loading 效果消失,頁面成功渲染:

import { render, screen, waitForElementToBeRemoved } from "@testing-library/react";
test("waitForElementToBeRemoved", async () => {
  render(<TodoList />);
  await waitForElementToBeRemoved(() => screen.getByText("loading ..."));
  expect(screen.getByText("delectus aut autem")).toBeInTheDocument();
});

參考文章
https://reflect.run/articles/async-waits-in-react-testing-library/
https://dev.to/tipsy_dev/testing-library-writing-better-async-tests-c67
https://medium.com/@bmb21/reacts-sync-and-async-act-caa297b658b0
https://github.com/mrdulin/react-act-examples/blob/master/sync.md
https://reactjs.org/docs/testing-recipes.html