在網頁的開發過程中,我們經常用到非同步的程式來構建所需的功能,為了確保程式的穩定度,當然要了解如何測試非同步的程式碼。
JavaScript是單線程的程式語言,每當非同步的任務執行,這些任務會被放到事件序列中(task queue),直到主線程的任務處理後才會被執行。或許有人認為非同步測試很複雜,其實非同步測試一點也不難!
接下來讓在這篇文章中讓你了解如何使用Jest測試非同步程式。
這邊文章將介紹如何使用Jest做非同步測試,Jest的配置方式與基礎介紹,以及JavaScript非同步的概念可以參考下面文章
如何使用Jest測試非同步
非同步的程式在JavaScript中很常見,如如ajax、setTimeout等。Jest提供了測試非同步的幾種方式協助我們測試非同步的程式。
以下是幾種Jest非同步測試方法:
- Callbacks
- Promise
- Async/Await
Callbacks
讓我們先從下面的setTimeout例子開始。
例子中,我們將模擬API的收發過程,透過setTimeout 模擬API的收發時需等待的時間。
當API發送成功,便會呼叫我們傳入的callback函式,然後得到回應的字串apple pie,代表從API取得資料。
非同步的範例程式:
// async_callback.js
const fetchData = (callback) => {
console.log('-----fetchData start');
setTimeout(() => {
callback('apple pie');
}, 2000);
console.log('-----fetchData end');
};
module.exports = fetchData;
非同步的測試程式:
// async_callback.test.js
const fetchData = require('../src/async_callback');
describe('callback function test', () => {
test('return message "apple pie"', () => {
const callback = (data) => {
console.log('-----callback start');
expect(data).toBe('apple pie');
console.log('-----callback end');
};
fetchData(callback);
});
});
測試結果:
執行測試後,我們得到了通過的結果。
但顯示的console卻少了callback函式裡的訊息 ! 為何測試時沒有執行callback函式?,問題到底在哪裡?
範例的問題
問題在於,一旦fetchData執行完畢,Jest便會在callback被呼叫前完成。
原因與JavaScript的同步與非同步概念有關。
由於JavaScript是單線程的程式語言。為了避免主線程被阻塞而影響使用者體驗,JavaScript在執行過程遇到非同步任務時,會將其放到Task Queue中處理。然後,繼續處理後續的程式,當堆疊中的程式執行完畢後才開始處理非同步任務。
如果對JavaScript的執行概念不清楚,可以參考這篇文章。
而Jest默認情況下不會等待非同步任務。例子中,當Jest執行到fetchData時,就會把內部的非同步任務(setTimeout)放到Task Queue,沒等setTimeout處理完便開始產生測試報告,導致了非我們預期的測試結果。
範例的修改方式
為了解決這問題,Jest建議使用done參數,Jest執行時會等待done被呼叫時才會結束測試。
修改方式以及測試結果如下:
// async_callback.test.js
const fetchData = require('../src/async_callback');
describe('callback function test', () => {
// test 加入done的參數
test('return message "apple pie"', (done) => {
const callback = (data) => {
try {
console.log('-----callback start');
expect(data).toBe('apple pie');
console.log('-----callback end');
// 使用done
done();
} catch (error) {
// 使用done
done(error);
}
};
fetchData(callback);
});
});
Promise
如果測試回傳一個Promise,Jest會等待該Promise的resolve,如果該Promise被reject,測試便會自動失敗。
在接下來Promise的測試中,我們的目標是使用Ajax獲取遠端資料,並確保取得的資料符合預期。
那麼該如何測試實際的Ajsx行為 ? 以及是否有現成的API可供我們測試呢?
阿建在這邊介紹一個可以滿足我們測試API的網站JSONPlaceholder。
JSONPlaceholder
JSONPlaceholder是一個免費的線上 REST API服務。提供了可以免費假資料,方便前端模擬與練習Ajax的行為,包括GET、POST、PUT與DELETE等。
接下來,我們將透過Axios來獲取JSONPlaceholder的資料,在透過Jest來進行API的非同步測試。
Axios
Axios是基於Promise開發的HTTP請求工具,可以在瀏覽器與Node.js上使用。它可以協助我們取得JSONPlaceholder的API資料。
下面的例子將使用axios來做非同步測試,因此需要在環境中先安裝axios。
安裝Axios的指令如下:
npm install axios
安裝後,在範例檔案中引入axios,並取得JSONPlaceholder的資料。
// async_axios.js
const axios = require('axios');
const fetchData = (id) => {
return axios.get(`https://jsonplaceholder.typicode.com/todos/${id}`).then((res) => res.data);
};
module.exports = fetchData;
例子的測試程式:
// async_axios.test.js
const fetchData = require('../src/async_axios');
describe('Axios Testing', () => {
test('回傳 delectus aut autem', () => {
// 斷言,確保非同步正確取得資料
expect.assertions(1);
return fetchData(1).then((data) => {
expect(data.title).toEqual('delectus aut autem');
});
});
});
測試結果如下:
Async/Await
我們也可以直接在test的函式中使用async與await,只需要在傳遞給test的函式前面加上async即可,我們可以將上面axios的例子改用async的做法。
修改方式如下:
// async_axios.js
const axios = require('axios');
const fetchData = async (id) => {
const results = await axios.get(`https://jsonplaceholder.typicode.com/todos/${id}`);
return results.data;
};
module.exports = fetchData;
例子的測試程式:
// async_axios.test.js
const fetchData = require('../src/async_axios');
describe('在test函式使用asyn與await的非同步測試', () => {
test('回傳 delectus aut autem', async () => {
// 確保非同步正確取得資料
expect.assertions(1);
try {
const result = await fetchData(1);
return expect(result.title).toEqual('delectus aut autem');
} catch (e) {
expect(e).toMatch('error');
}
});
});
參考資料
JavaScript Unit Testing
React – The Complete Guide
Jest-Testing Asynchronous Code
Jest:非同步測試
Asynchronous Code Test in JEST
JSONPlaceholder