[Day27] 建立 Mock Module / Function 的方式(ft. TypeScript)
keywords: testing
, react-testing-library
, jest
今天來談談 Mock Module / Function 的部分。Mock Function 是在 Jest 中非常強大的功能,它可以模擬某個函式會回傳的值, 並且監控該函式被執行的次數,而 Mock Module 可以說是墊加在 Mock Function 上的功能,也就是可以直接模擬某套件中特定函式回傳的結果。
雖然說在寫測試時 Mock Module 很常會被使用到,但因為 Jest 提供了幾種不同的方式讓開發者能夠 Mock Module,因此過去在找資料時常會看到多種不同寫法而對 Mock 的方式感到有點混亂,這篇內容則是整理了筆者比較常用的方式。
建立一個簡單的測試範例
在開始說明 Mock Module 的概念前,讓我們先來看個例子。現在我們有個 React 元件長這樣:
import { useEffect, useState } from 'react';
import { fetchUser } from './utils/api';
import { User } from './types';
function App() {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
fetchUser<User[]>().then((users) => {
return setUsers(users);
});
}, []);
return (
<div className="App">
<header className="App-header">
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</header>
</div>
);
}
export default App;
在 <App />
中,會載入 ./utils/api
這支檔案,在 ./utils/api
中則撰寫一個用來 fetch User API 的方法,它會實際向 jsonplaceholder 發送請求:
// ./utils/api
export const fetchUser = async <T>() => {
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
const data = (await resp.json()) as T;
return data;
};
預設的情況下,jsonplaceholder 會回傳 10 個使用者的資料回來,這時候畫面會長這樣:
接著一如往常的來撰寫測試,可以寫成像這樣:
// App.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
import { create } from 'react-test-renderer';
test('render user data successfully', async () => {
render(<App />);
const listItemElements = await screen.findAllByRole('listitem');
expect(listItemElements.length).toBe(10);
});
在這個測試中我們透過 react-testing-library 提供的 render
來轉譯出 <App />
這個元件,接著在 DOM 上找出 <li />
的元素,並 assert 它應該有 10 個,測試也順利的通過了:
這裡筆者不會說明如何使用 react-testing-library 來撰寫 React 元件的測試,如果有需要的話,推薦可以看 React Testing Library Tutorial @ Youtube。
但等等,這個測試實際上真的有去打 API 嗎?你覺得呢?
我們可以在 <App />
元件中的 useEffect
中加上 console.log('[App] users', users);
,看看取得的資料是否和從 jsonplaceholder 回來的資料相同:
useEffect(() => {
fetchUser<User[]>().then((users) => {
console.log('[App] users', users);
return setUsers(users);
});
}, []);
當郎~的確是打 API 回來的資料:
為什麼要建立 Mock Module/Function
從上面的例子中可以看到,我們真的對 jsonplaceholder 發出了請求,但等等,這樣是不是表示我們的測試以後都需要連上網路才能執行?就算連網本身不是問題,未來如果 jsonplaceholder 的網站維修的話,是不是表示我們的測試也會連帶無法進行。
除非是後期的 E2E 測試,否則當我們的測試依賴外部的資源時,是一件不太好的事,因為測試 failed 的時候,開發者將沒辦法馬上知道現在測試 failed 的原因是我們的程式寫壞了,還是外部的網站掛了。邏輯上同樣的程式碼每次執行測試得到的結果應該要是穩定的,不會現在跑沒事,下次跑卻莫名失敗。
未來確保執行測試時能夠控制不要讓外部的變數影響到測試的穩定度,我們可以透過 Jest 的 Mock Function 來模擬 API 回傳的結果,讓這個元件不需要實際發送請求。
實際上,不只是串接 API 才需要使用到 Mock Function,如果要測試的內容包含會持續變動的時間、路由(router)、custom hooks 等等,也都可也透過 Mock Function 模擬。