今天來學習如何測試非同步行為!
今天來學習一些測試非同步的方法,在此之前先來想想有什麼樣的情況,會需要使用到這些方法呢?
首先,如有要調用 API 在等待回傳值的過程,並等待畫面重新渲染,或是等待頁面上的某元素消失時(例如 Loading 過場效果),這些時機點可能就需要使用到非同步的測試方法!
那有哪些方法呢?
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
是 GetBy
及 WaitFor
的結合體,可以協助進行非同步的等待外也能協助我們 query DOM,使用方式如下:
// 當 DOM 元素內容不會立刻出現時,可透過 findBy.. 等待內容出現
await screen.findByText('期望出現的內容');
與 waitFor 要等待元素從 DOM 上移除可以透過 waitForElementToBeRemoved
,waitForElementToBeRemoved
是 waitFor
底下的小包裝器,使用方法也相似:
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