异步工作
在现代 Javascript 应用中,异步代码非常常见。它的测试方式与同步代码的测试方式大多相同,但有一个关键区别:Jasmine 需要在异步工作完成时收到通知。
Jasmine 支持 3 种管理异步工作的方法:async
/await
、Promise 和回调。如果 Jasmine 未检测到其中一种,则它会假定该工作为同步工作,并且在函数返回后立即开始处理队列中的下一项工作。所有这些机制都适用于 beforeEach
、afterEach
、beforeAll
、afterAll
和 it
。
async/await
通常,编写异步测试的最便捷方法是使用 async
/await
。async
函数隐式返回一个 Promise。在处理队列中的下一项工作之前,Jasmine 会等到返回的 Promise 被解析或拒绝。拒绝的 Promise 会导致规范失败,或者在 beforeAll
或 afterAll
的情况下导致套件级失败。
beforeEach(async function() {
await someLongSetupFunction();
});
it('does a thing', async function() {
const result = await someAsyncFunction();
expect(result).toEqual(someExpectedValue);
});
Promise
如果你需要更多控制权,你可以显式返回一个 Promise。Jasmine 认为任何具有 then
方法的对象都是一个 Promise,因此你可以使用 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);
});
});
回调
还可以使用回调编写异步测试。这是一种较低级别机制,并且往往更容易出错,但对于测试基于回调的代码或难以用 Promise 表达的测试,它可能很有用。如果传递给 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 失败
拒绝的 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
函数可以通过返回拒绝的 Promise 或抛出错误来表示失败。
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);
});
});