跳到主要內容
版本:29.7

測試非同步程式碼

在 JavaScript 中,程式碼以非同步方式執行是很常見的。當您有以非同步方式執行的程式碼時,Jest 需要知道它正在測試的程式碼何時完成,才能繼續進行另一個測試。Jest 有好幾種方法可以處理這個問題。

Promises

從您的測試中傳回一個 Promise,而 Jest 將會等待該 Promise 解析。如果 Promise 被拒絕,測試將會失敗。

例如,假設 fetchData 傳回一個 Promise,而該 Promise 應該解析為字串 'peanut butter'。我們可以用以下方式測試它

test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});

Async/Await

或者,您可以在測試中使用 asyncawait。若要撰寫非同步測試,請在傳遞給 test 的函式前面使用 async 關鍵字。例如,可以透過以下方式測試相同的 fetchData 情境

test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (error) {
expect(error).toMatch('error');
}
});

您可以將 asyncawait.resolves.rejects 結合使用。

test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});

在這些情況下,asyncawait 實際上是與承諾範例使用的相同邏輯的語法糖。

注意

務必傳回 (或 await) 承諾 - 如果您省略 return/await 陳述式,您的測試將在從 fetchData 傳回的承諾解析或拒絕之前完成。

如果您預期承諾會被拒絕,請使用 .catch 方法。務必新增 expect.assertions 以驗證呼叫了特定數量的斷言。否則,已完成的承諾不會讓測試失敗。

test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(error => expect(error).toMatch('error'));
});

回呼

如果您不使用承諾,您可以使用回呼。例如,假設 fetchData 不是傳回承諾,而是預期回呼,也就是在完成時擷取一些資料並呼叫 callback(null, data)。您想要測試這個傳回的資料是否為字串 'peanut butter'

預設情況下,Jest 測試在執行結束後完成。這表示這個測試不會按預期工作

// Don't do this!
test('the data is peanut butter', () => {
function callback(error, data) {
if (error) {
throw error;
}
expect(data).toBe('peanut butter');
}

fetchData(callback);
});

問題在於測試會在 fetchData 完成後立即完成,在呼叫回呼之前。

有一個 test 的替代形式可以解決這個問題。不要將測試放在具有空引數的函式中,而是使用一個稱為 done 的單一引數。Jest 會等到呼叫 done 回呼後才會完成測試。

test('the data is peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}

fetchData(callback);
});

如果從未呼叫 done(),測試將會失敗 (逾時錯誤),這正是您想要的結果。

如果 expect 陳述式失敗,它會擲回錯誤,而且不會呼叫 done()。如果我們想要在測試記錄中看到它失敗的原因,我們必須將 expect 包裝在 try 區塊中,並將 catch 區塊中的錯誤傳遞給 done。否則,我們會得到一個不透明的逾時錯誤,而不會顯示 expect(data) 收到的值。

注意

如果同一個測試函式傳遞 done() 回呼並傳回承諾,Jest 會擲回錯誤。這是為了預防您的測試發生記憶體外洩。

.resolves / .rejects

您也可以在 expect 陳述中使用 .resolves 比對器,而 Jest 將會等待該承諾解析。如果承諾被拒絕,測試將會自動失敗。

test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});

務必傳回斷言,如果您省略此 return 陳述,您的測試將會在從 fetchData 傳回的承諾解析之前完成,而 then() 將有機會執行回呼。

如果您預期承諾會被拒絕,請使用 .rejects 比對器。它的運作方式類似於 .resolves 比對器。如果承諾已履行,測試將會自動失敗。

test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});

這些形式沒有任何一種特別優於其他形式,您可以在程式碼庫中或甚至在單一檔案中混合搭配它們。這取決於您覺得哪一種風格讓您的測試更為簡潔。