# 處理非同步 如有要調用 API 在等待回傳值的過程,並等待畫面重新渲染,或是等待頁面上的某元素消失時(例如 Loading 過場效果),這些時機點可能就需要使用到非同步的測試方法! - [常用方法](https://hackmd.io/6EGH2CPGSN-8QrUu7oha2w#%E8%99%95%E7%90%86%E9%9D%9E%E5%90%8C%E6%AD%A51) - [實際練習](https://hackmd.io/6EGH2CPGSN-8QrUu7oha2w#%E5%AF%A6%E9%9A%9B%E7%B7%B4%E7%BF%92) --- #### **處理非同步** - **WaitFor** - React Testing Library 提供的方法 - 會回傳 Promise ,而在等待的時間 `WaitFor`可以被調用多次(調用頻率取決於 `interval`的設定,默認為 50ms),會等待 callback 函式的 Promise 被拋出,或 `timeout`超時(默認為一秒) ```jsx 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` : ```jsx waitFor(() => { // 非同步事件執行完成 // ex API 回傳值回傳並渲染成功 }); ``` - ****FindBy**** - `GetBy`及 `WaitFor`的結合體,可以協助進行非同步的等待外也能協助我們 query DOM,使用方式如下: ```jsx // 當 DOM 元素內容不會立刻出現時,可透過 findBy.. 等待內容出現 await screen.findByText('期望出現的內容'); ``` - ****waitForElementToBeRemoved**** - 與 `waitFor` 要等待元素從 DOM 上移除可以透過 `waitForElementToBeRemoved` ,`waitForElementToBeRemoved` 是 `waitFor` 底下的小包裝器,使用方法也相似: ```jsx waitForElementToBeRemoved(() => { // 某元素消失 }); ``` --- #### 實際練習 底下程式碼在尚未獲取 api 資料時會顯示 Loading : ```jsx 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 顯示在頁面上: ```jsx // 記得引入方法 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 效果消失,頁面成功渲染: ```jsx 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