iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0

今天來學習如何測試非同步行為!

非同步測試方法

今天來學習一些測試非同步的方法,在此之前先來想想有什麼樣的情況,會需要使用到這些方法呢?

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

那有哪些方法呢?

WaitFor

WaitFor 是 React Testing Library 提供的方法,WaitFor 會回傳 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 回傳值回傳並渲染成功
});

await waitFor(() => {

});

FindBy

FindByGetByWaitFor 的結合體,可以協助進行非同步的等待外也能協助我們 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


上一篇
jest.fn()、 spyOn 差異
下一篇
透過 Mock 來模擬 Axios
系列文
<< 測試魔法 >> 這能動嗎?不然就測測看好了!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言