JavaScript/Mocha-如何测试是否已等待函数调用 [英] JavaScript / Mocha - How to test if function call was awaited
问题描述
我想编写一个测试,检查我的函数是否使用await
关键字调用其他函数.
I would like to write a test that check if my function calls other functions using the await
keyword.
我希望测试失败:
async methodA() {
this.methodB();
return true;
},
我希望测试成功:
async methodA() {
await this.methodB();
return true;
},
我也希望测试也成功:
methodA() {
return this.methodB()
.then(() => true);
},
我有一个解决方案,方法是使用process.nextTick
强制使用该方法并强制其在其中返回假诺言,但这似乎很丑陋,我不想在测试中使用process.nextTick
或setTimeout
等
I have a solution by stubbing the method and force it to return fake promise inside it using process.nextTick
, but it seems to be ugly, and I do not want to use process.nextTick
nor setTimeout
etc in my tests.
ugly-async-test.js
const { stub } = require('sinon');
const { expect } = require('chai');
const testObject = {
async methodA() {
await this.methodB();
},
async methodB() {
// some async code
},
};
describe('methodA', () => {
let asyncCheckMethodB;
beforeEach(() => {
asyncCheckMethodB = stub();
stub(testObject, 'methodB').returns(new Promise(resolve => process.nextTick(resolve)).then(asyncCheckMethodB));
});
afterEach(() => {
testObject.methodB.restore();
});
it('should await methodB', async () => {
await testObject.methodA();
expect(asyncCheckMethodB.callCount).to.be.equal(1);
});
});
测试在函数调用中是否使用await
的聪明方法是什么?
What is the smart way to test if await
was used in the function call?
推荐答案
TLDR
如果methodA
在methodB
上调用await
,则 methodA
返回的Promise
不会解析,直到methodB
返回的Promise
解析.
TLDR
If methodA
calls await
on methodB
then the Promise
returned by methodA
will not resolve until the Promise
returned by methodB
resolves.
另一方面,如果methodA
没有在methodB
上调用await
,则 methodA
返回的Promise
将立即解决methodB
返回的Promise
是否已解决.
On the other hand, if methodA
does not call await
on methodB
then the Promise
returned by methodA
will resolve immediately whether the Promise
returned by methodB
has resolved or not.
因此,测试methodA
是否在methodB
上调用await
只是测试methodA
返回的Promise
是否在解析之前等待methodB
返回的Promise
解析的问题:
So testing if methodA
calls await
on methodB
is just a matter of testing whether the Promise
returned by methodA
waits for the Promise
returned by methodB
to resolve before it resolves:
const { stub } = require('sinon');
const { expect } = require('chai');
const testObject = {
async methodA() {
await this.methodB();
},
async methodB() { }
};
describe('methodA', () => {
const order = [];
let promiseB;
let savedResolve;
beforeEach(() => {
promiseB = new Promise(resolve => {
savedResolve = resolve; // save resolve so we can call it later
}).then(() => { order.push('B') })
stub(testObject, 'methodB').returns(promiseB);
});
afterEach(() => {
testObject.methodB.restore();
});
it('should await methodB', async () => {
const promiseA = testObject.methodA().then(() => order.push('A'));
savedResolve(); // now resolve promiseB
await Promise.all([promiseA, promiseB]); // wait for the callbacks in PromiseJobs to complete
expect(order).to.eql(['B', 'A']); // SUCCESS: 'B' is first ONLY if promiseA waits for promiseB
});
});
详细信息
在所有三个代码示例中,methodA
和methodB
都返回一个Promise
.
Details
In all three of your code examples methodA
and methodB
both return a Promise
.
我将methodA
返回的Promise
称为promiseA
,并将methodB
返回的Promise
称为promiseB
.
I will refer to the Promise
returned by methodA
as promiseA
, and the Promise
returned by methodB
as promiseB
.
您要测试的是promiseA
等待解决直到promiseB
解决.
What you are testing is if promiseA
waits to resolve until promiseB
resolves.
首先,让我们看一下如何测试promiseA
不等待promiseB
.
First off, let's look at how to test that promiseA
did NOT wait for promiseB
.
测试promiseA
是否不等待promiseB
Test if promiseA
does NOT wait for promiseB
测试否定情况(promiseA
没有等待promiseB
)的一种简单方法是模拟methodB
返回从不解决的Promise
:>
An easy way to test for the negative case (that promiseA
did NOT wait for promiseB
) is to mock methodB
to return a Promise
that never resolves:
describe('methodA', () => {
beforeEach(() => {
// stub methodB to return a Promise that never resolves
stub(testObject, 'methodB').returns(new Promise(() => {}));
});
afterEach(() => {
testObject.methodB.restore();
});
it('should NOT await methodB', async () => {
// passes if promiseA did NOT wait for promiseB
// times out and fails if promiseA waits for promiseB
await testObject.methodA();
});
});
这是一个非常干净,简单而直接的测试.
This is a very clean, simple, and straightforward test.
如果我们可以返回相反的结果,那就太棒了……如果此测试失败,则返回 true .
It would be awesome if we could just return the opposite...return true if this test would fail.
不幸的是,这不是一个合理的方法,因为如果promiseA
做await
promiseB
,则该测试超时.
Unfortunately, that is not a reasonable approach since this test times out if promiseA
DOES await
promiseB
.
我们将需要一种不同的方法.
We will need a different approach.
背景信息
在继续之前,这里有一些有用的背景信息:
Before continuing, here is some helpful background information:
JavaScript使用 消息队列 > .当前消息运行至完成在下一个开始之前. 正在运行测试时,该测试是当前消息.
JavaScript uses a message queue. The current message runs to completion before the next one starts. While a test is running, the test is the current message.
ES6引入了 PromiseJobs队列 处理作为对Promise结算的响应"的作业. PromiseJobs队列中的所有作业都在当前消息完成之后且下一条消息开始之前运行 .
因此当Promise
解析时,其then
回调将添加到PromiseJobs队列中,并且当当前消息完成时,PromiseJobs中的所有作业将依次运行 . >,直到队列为空.
So when a Promise
resolves, its then
callback gets added to the PromiseJobs queue, and when the current message completes any jobs in PromiseJobs will run in order until the queue is empty.
async
和await
只是语法在诺言和生成器上撒糖.在Promise
上调用await
本质上是在等待的Promise
解析时将其余函数包装在PromiseJobs中计划的回调中.
async
and await
are just syntactic sugar over promises and generators. Calling await
on a Promise
essentially wraps the rest of the function in a callback to be scheduled in PromiseJobs when the awaited Promise
resolves.
我们需要的是一项测试,如果promiseA
DID等待promiseB
,它会告诉我们,而不会超时.
What we need is a test that will tell us, without timing out, if promiseA
DID wait for promiseB
.
由于我们不希望测试超时,因此promiseA
和promiseB
都必须解决.
Since we don't want the test to timeout, both promiseA
and promiseB
must resolve.
然后,目标是找出一种方法来判断promiseA
是否正在等待promiseB
,因为它们都在解决.
The objective, then, is to figure out a way to tell if promiseA
waited for promiseB
as they are both resolving.
答案是利用PromiseJobs队列.
The answer is to make use of the PromiseJobs queue.
考虑此测试:
it('should result in [1, 2]', async () => {
const order = [];
const promise1 = Promise.resolve().then(() => order.push('1'));
const promise2 = Promise.resolve().then(() => order.push('2'));
expect(order).to.eql([]); // SUCCESS: callbacks are still queued in PromiseJobs
await Promise.all([promise1, promise2]); // let the callbacks run
expect(order).to.eql(['1', '2']); // SUCCESS
});
Promise.resolve()
返回已解析的Promise
,因此两个回调将立即添加到PromiseJobs队列中.一旦当前消息(测试)暂停以等待PromiseJobs中的作业,它们将按照添加到PromiseJobs队列中的顺序运行,并且当测试在await Promise.all
之后继续运行时,order
数组包含['1', '2']
,如下所示:预期的.
Promise.resolve()
returns a resolved Promise
so the two callbacks get added to the PromiseJobs queue immediately. Once the current message (the test) is paused to wait for the jobs in PromiseJobs, they run in the order they were added to the PromiseJobs queue and when the test continues running after await Promise.all
the order
array contains ['1', '2']
as expected.
现在考虑进行此测试:
it('should result in [2, 1]', async () => {
const order = [];
let savedResolve;
const promise1 = new Promise((resolve) => {
savedResolve = resolve; // save resolve so we can call it later
}).then(() => order.push('1'));
const promise2 = Promise.resolve().then(() => order.push('2'));
expect(order).to.eql([]); // SUCCESS
savedResolve(); // NOW resolve the first Promise
await Promise.all([promise1, promise2]); // let the callbacks run
expect(order).to.eql(['2', '1']); // SUCCESS
});
在这种情况下,我们保存第一个Promise
中的resolve
,以便稍后使用. 由于第一个Promise
尚未解决,因此then
回调不会立即添加到PromiseJobs队列中.另一方面,第二个Promise
已经解决,因此其then
回调被添加到PromiseJobs队列中.一旦发生这种情况,我们将调用保存的resolve
,以便第一个Promise
解析,这会将其then
回调添加到PromiseJobs队列的末尾.一旦将当前消息(测试)暂停以等待PromiseJobs中的作业,order
数组将按预期包含['2', '1']
.
In this case we save the resolve
from the first Promise
so we can call it later. Since the first Promise
has not yet resolved, the then
callback does not immediately get added to the PromiseJobs queue. On the other hand, the second Promise
has already resolved so its then
callback gets added to the PromiseJobs queue. Once that happens, we call the saved resolve
so the first Promise
resolves, which adds its then
callback to the end of the PromiseJobs queue. Once the current message (the test) is paused to wait for the jobs in PromiseJobs, the order
array contains ['2', '1']
as expected.
测试在函数调用中是否使用
await
的聪明方法是什么?
What is the smart way to test if
await
was used in the function call?
测试是否在函数调用中使用了await
的聪明方法是向promiseA
和promiseB
都添加then
回调,然后延迟解析promiseB
.如果promiseA
等待promiseB
,则其回调将在PromiseJobs队列中总是最后.另一方面,如果promiseA
不等待promiseB
,则其回调将在PromiseJobs中排在第一队列中.
The smart way to test if await
was used in the function call is to add a then
callback to both promiseA
and promiseB
, and then delay resolving promiseB
. If promiseA
waits for promiseB
then its callback will always be last in the PromiseJobs queue. On the other hand, if promiseA
does NOT wait for promiseB
then its callback will get queued first in PromiseJobs.
最终解决方案位于上面的 TLDR 部分.
The final solution is above in the TLDR section.
请注意,当methodA
是在methodB
上调用await
的async
函数时,以及methodA
是返回Promise
链接到methodB
返回的Promise
(可以预料,因为async / await
只是Promises
和生成器上的语法糖).
Note that this approach works both when methodA
is an async
function that calls await
on methodB
, as well as when methodA
is a normal (not async
) function that returns a Promise
chained to the Promise
returned by methodB
(as would be expected since, once again, async / await
is just syntactic sugar over Promises
and generators).
这篇关于JavaScript/Mocha-如何测试是否已等待函数调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!