---
# System prepended metadata

title: 處理非同步

---

# 處理非同步

如有要調用 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