前端測試框架Jest系列教程 -- Asynchronous(測試非同步程式碼)
寫在前面:
在JavaScript程式碼中,非同步執行是很常見的。當你有非同步執行的程式碼時,Jest需要知道它測試的程式碼何時完成,然後才能繼續進行另一個測試。Jest提供了幾種方法來處理這個問題。
測試非同步程式碼的三種實現方式:
方法一:回撥函式
這是非常常見的通用處理方式,比如你有一個fetchData(callback)的
function用來獲取資料,並且在獲取完成的時候呼叫callback 函式,你想測試返回的資料是“peanut butter” ,預設情況下當fetchData執行完成的時候Jest的測試就完成了,這並不是你所期望的那樣的去執行。
// Don't do this!test('the data is peanut butter', () => { function callback(data) { expect(data).toBe('peanut butter'); } fetchData(callback); });
上面程式碼的問題就在於一旦fetchData完成,測試也就執行完成,然後再呼叫回撥。
Jest提供了一種用於測試的實現方式,下面程式碼 done() 被執行則意味著callback函式被呼叫。
test('the data is peanut butter', done => {function callback(data) { expect(data).toBe('peanut butter'); done(); } fetchData(callback); });
如果 done 永遠都不被呼叫,那麼的測試將會失敗,這也正是我們期望的(我們希望callback被呼叫,並且返回的data是我們期望的值)
方法二:承諾驗證
如果你的程式碼中使用了承諾的方式,處理非同步測試將會變得更加簡單。Jest從你的測試中返回一個承諾,然後等待承諾被實現,如果沒有實現,那麼就判斷測試是失敗的。
還是上面的例子,如果用承諾驗證,那麼實現將是下面的樣子:
test('the data is peanut butter', () => { expect.assertions(1); return fetchData().then(data => { expect(data).toBe('peanut butter'); }); });
assertions(1)代表的是在當前的測試中至少有一個斷言是被呼叫的,否則判定為失敗。
如果刪掉return語句,那麼你的測試將在fetchData完成之前結束。
如果斷言的承諾並沒有被實現,那麼你可以新增 .catch 方法。一定要新增expect,斷言驗證一定數量的斷言被呼叫。否則一個實現的承諾就不會失敗。
test('the fetch fails with an error', () => { expect.assertions(1); return fetchData().catch(e => expect(e).toMatch('error')); });
在Jest 20.0.0+ 的版本中你可以使用 .resolves 匹配器在你的expect語句中,Jest將會等待一直到承諾被實現,如果承諾沒有被實現,測試將自動失敗。
test('the data is peanut butter', () => { expect.assertions(1); return expect(fetchData()).resolves.toBe('peanut butter'); });
如果你期望你的承諾是不被實現的,你可以使用 .rejects ,它的原理和 .resolves類似
test('the fetch fails with an error', () => { expect.assertions(1); return expect(fetchData()).rejects.toMatch('error'); });
第三種:使用 Async/Await
我相信大家最Async/Await 是比較熟悉的,你可以在測試中使用非同步和等待。要編寫一個async測試,只需在傳遞到測試的函式前面使用async關鍵字。例如上面同樣的fetchData場景可以使用下面的實現:
test('the data is peanut butter', async () => { expect.assertions(1); const data = await fetchData(); expect(data).toBe('peanut butter'); }); test('the fetch fails with an error', async () => { expect.assertions(1); try { await fetchData(); } catch (e) { expect(e).toMatch('error'); } });
當然你也可以將Async Await和 .resolves .rejects 結合起來(Jest 20.0.0+ 的版本)
test('the data is peanut butter', async () => { expect.assertions(1); await expect(fetchData()).resolves.toBe('peanut butter'); }); test('the fetch fails with an error', async () => { expect.assertions(1); await expect(fetchData()).rejects.toMatch('error'); });
寫在最後:
在這些情況下,非同步和等待實際上只是與承諾示例使用的相同邏輯的語法糖。
這幾種方法中沒有一個特別優於另外一個,你可以將它們組合在一個程式碼庫中,甚至可以在單個檔案中進行匹配。它只是取決於哪種樣式使你的測試更簡單。