非同步工作
非同步碼在現代 JavaScript 應用程式中很常見。其測試方式大多與測試同步碼相同,只有一個主要區別:Jasmine 需要知道非同步工作何時完成。
Jasmine 支援使用三種方式管理非同步工作:async
/await
、承諾和回呼。如果 Jasmine 沒有偵測到其中一種方式,它會假設這項工作是同步的,並開始處理佇列中的下一項,只要該功能能返回即可。這些機制都適用於 beforeEach
、afterEach
、beforeAll
、afterAll
和 it
。
async/await
通常,撰寫非同步測試最簡便的方式是使用 async
/await
。 async
功能會隱式傳回一個承諾。Jasmine 會等到傳回的承諾已解決或拒絕,才會繼續處理佇列中的下一項。被拒絕的承諾會導致規格失敗,或針對 beforeAll
或 afterAll
會導致套件層級失敗。
beforeEach(async function() {
await someLongSetupFunction();
});
it('does a thing', async function() {
const result = await someAsyncFunction();
expect(result).toEqual(someExpectedValue);
});
承諾
如果需要更嚴密的控制,您可以顯式傳回承諾。Jasmine 會視任何含有 then
方法的物件為承諾,因此您可以使用 JavaScript 執行時間內建的 Promise
類型,或使用函式庫。
beforeEach(function() {
return new Promise(function(resolve, reject) {
// do something asynchronous
resolve();
});
});
it('does a thing', function() {
return someAsyncFunction().then(function (result) {
expect(result).toEqual(someExpectedValue);
});
});
回呼
使用回呼撰寫非同步測試也是可行的。這是一個較低層級的機制,而且容易發生錯誤,但它可用於測試以回呼為基礎的碼,或測試那些無法用承諾表達的碼。如果傳送至 Jasmine 的功能需要參數(傳統上稱為 done
),則 Jasmine 會傳送一個非同步工作完成後要啟用的功能。
至為重要的是,done
回呼必須只呼叫一次,而且呼叫 done
必須是非同步功能,或任何它所呼叫的功能所執行的最後一項工作。在撰寫回呼式非同步測試時,一個常見的錯誤是在測試中的碼仍執行時呼叫 done
。在這種情況下,done
呼叫後產生的錯誤,可能會與造成錯誤的規格無關,甚至根本不會報告。
beforeEach(function(done) {
setTimeout(function() {
// do some stuff
done();
}, 100);
});
it('does a thing', function(done) {
someAsyncFunction(function(result) {
expect(result).toEqual(someExpectedValue);
done();
});
});
處理失敗
有時候,非同步碼無法正常運作,而您會希望規格正確失敗。所有未處理的錯誤會被 Jasmine 擷取,並傳送到目前執行的規格。有時您需要明確地使您的規格失敗。
使用承諾失敗
被拒絕的 Promise
會導致規格失敗,就像執行錯誤後發生的情況一樣。
beforeEach(function() {
return somePromiseReturningFunction();
});
it('does a thing', function() {
// Since `.then` propagates rejections, this test will fail if
// the promise returned by asyncFunctionThatMightFail is rejected.
return asyncFunctionThatMightFail().then(function(value) {
// ...
});
});
function somePromiseReturningFunction() {
return new Promise(function(resolve, reject) {
if (everythingIsOk()) {
resolve();
} else {
reject();
}
});
}
使用 async
/await
失敗
async
/await
功能可以透過傳回被拒絕的承諾,或執行錯誤來指出失敗。
beforeEach(async function() {
// Will fail if the promise returned by
// someAsyncFunction is rejected.
await someAsyncFunction();
});
it('does a thing', async function() {
// Will fail if doSomethingThatMightThrow throws.
doSomethingThatMightThrow();
// Will fail if the promise returned by
// asyncFunctionThatMightFail is rejected.
const value = await asyncFunctionThatMightFail();
// ...
});
使用回呼失敗
當作回呼傳遞的 done
函式也可以使用 done.fail()
,來讓規範失敗,可選擇傳遞訊息或 Error
物件。
beforeEach(function(done) {
setTimeout(function() {
try {
riskyThing();
done();
} catch (e) {
done.fail(e);
}
});
});
done
函式也會偵測直接傳遞給它的 Error
,來讓規範失敗。
beforeEach(function(done) {
setTimeout(function() {
let err = null;
try {
riskyThing();
} catch (e) {
err = e;
}
done(err);
});
});
回報器
回報器事件處理器也可以使用任何一種方法來變成非同步的。請注意,所有回報器事件都已接收資料,因此如果您使用回呼方法,done
回呼應該是最後一個參數。
使用模擬時鐘來避免撰寫非同步測試
如果某個操作變成非同步只因為它仰賴 setTimeout 或其他基於時間的行為,要測試它的好方法就是使用 Jasmine 的 模擬時鐘,讓它同步執行。這種測試的撰寫方式比較簡單,而且執行速度會比實際等待時間到期的非同步測試還快。
function doSomethingLater(callback) {
setTimeout(function() {
callback(12345);
}, 10000);
}
describe('doSomethingLater', function() {
beforeEach(function() {
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it('does something after 10 seconds', function() {
const callback = jasmine.createSpy('callback');
doSomethingLater(callback);
jasmine.clock().tick(10000);
expect(callback).toHaveBeenCalledWith(12345);
});
});