You are currently viewing [筆記]-JavaScript 要如何使用Jest測試非同步?

[筆記]-JavaScript 要如何使用Jest測試非同步?

在網頁的開發過程中,我們經常用到非同步的程式來構建所需的功能,為了確保程式的穩定度,當然要了解如何測試非同步的程式碼。

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);
    });
});

測試結果:

Test Callback Function
Test Callback Function

執行測試後,我們得到了通過的結果。
但顯示的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);
    });
});
修正Callback Function
修正Callback Function

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');
        });
    });
});

測試結果如下:

Axios範例通過測試
Axios範例通過測試

Async/Await

我們也可以直接在test的函式中使用asyncawait,只需要在傳遞給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

發佈留言