JavaScript / Mocha-如何测试是否已等待函数调用

Ada*_*dam 7 javascript testing mocha.js node.js sinon

我想编写一个测试,检查我的函数是否使用await关键字调用了其他函数。

我希望考试失败

async methodA() {
   this.methodB();
   return true; 
},
Run Code Online (Sandbox Code Playgroud)

我希望我的测试能够成功

async methodA() {
   await this.methodB();
   return true;
},
Run Code Online (Sandbox Code Playgroud)

我也希望我的测试也能成功

methodA() {
   return this.methodB()
       .then(() => true);
},
Run Code Online (Sandbox Code Playgroud)

我有一个解决方案,方法是使用stub方法并强制其使用来返回其内部的伪诺言process.nextTick,但这似乎很丑陋,我不想在测试中使用process.nextTicknor setTimeout等。

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);
    });
});
Run Code Online (Sandbox Code Playgroud)

测试是否await在函数调用中使用的智能方法是什么?

Bri*_*ams 4

总长DR

如果methodA调用awaitmethodB返回Promise的 bymethodA将不会解析,直到Promise返回的 bymethodB解析为止。

另一方面,如果methodA不调用awaitmethodB返回Promise的 bymethodA将立即解析,无论Promise返回的 bymethodB是否已解析

因此,测试 ifmethodA调用只是测试返回的 by 是否await等待返回的by解析后再解析:methodBPromisemethodAPromisemethodB

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
  });
});
Run Code Online (Sandbox Code Playgroud)

细节

在所有三个代码示例中methodAmethodB都返回一个Promise.

我将参考asPromise返回的和as返回的methodApromiseAPromisemethodBpromiseB值。

您正在测试的是是否promiseA等待解决直到promiseB解决。


首先,让我们看看如何测试promiseANOT wait for promiseB


测试是否promiseA不等待promiseB

测试负面情况(promiseA不等待promiseB)的一个简单方法是模拟methodB返回一个Promise永远不会解析的

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();
  });

});
Run Code Online (Sandbox Code Playgroud)

这是一个非常干净、简单且直接的测试。


如果我们可以返回相反的结果,那就太棒了……如果测试失败,则返回true

不幸的是,这不是一个合理的方法,因为如果确实的话,这个测试就会超时promiseAawait promiseB

我们需要一种不同的方法。


背景资料

在继续之前,这里有一些有用的背景信息:

JavaScript 使用消息队列。当前消息在下一条消息开始之前 运行完成。当测试运行时,测试是当前消息

ES6 引入了PromiseJobs 队列,它处理“对 Promise 结算的响应”的作业。PromiseJobs 队列中的所有作业都会在当前消息完成之后、下一条消息开始之前运行。

因此,当 aPromise解析时,它的then回调会被添加到 PromiseJobs 队列中,并且当当前消息完成时,PromiseJobs 中的任何作业都将按顺序运行,直到队列为空。

asyncawait只是Promise 和 Generator 的语法糖。调用awaitaPromise本质上是将函数的其余部分包装在回调中,以便在等待的Promise解决时在 PromiseJobs 中安排。


我们需要的是一个能够在不超时的情况下告诉我们是否promiseA确实等待的测试promiseB

由于我们不希望测试超时,因此promiseA和 都promiseB 必须解决。

那么,我们的目标是找出一种方法来判断是否在它们都正在解决时promiseA等待。promiseB

答案是利用 PromiseJobs 队列。

考虑这个测试:

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
});
Run Code Online (Sandbox Code Playgroud)

Promise.resolve() 返回一个已解决的结果Promise,以便两个回调立即添加到 PromiseJobs 队列中。一旦当前消息(测试)暂停以等待 PromiseJobs 中的作业,它们就会按照添加到 PromiseJobs 队列的顺序运行,并且当测试在数组按预期包含后继续await Promise.all运行order['1', '2']

现在考虑这个测试:

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
});
Run Code Online (Sandbox Code Playgroud)

resolve在这种情况下,我们从第一个开始保存Promise,以便稍后调用它。 由于第一个Promise尚未解决,因此then回调不会立即添加到 PromiseJobs 队列中。另一方面,第二个Promise已经解决,因此它的then回调被添加到 PromiseJobs 队列中。一旦发生这种情况,我们调用保存的resolve第一个Promise解析,这会将其then回调添加到 PromiseJobs 队列的末尾。一旦当前消息(测试)暂停以等待 PromiseJobs 中的作业,order数组就会按预期包含['2', '1']


await测试函数调用中是否使用了 的智能方法是什么?

测试await函数调用中是否使用了 was 的聪明方法是thenpromiseA和 都添加回调promiseB,然后延迟解析promiseB。如果promiseA等待,promiseB那么它的回调将始终位于PromiseJobs 队列的最后。另一方面,如果promiseA不等待,那么它的回调将首先promiseB排队

最终解决方案在TLDR部分。

请注意,当 ismethodA是一个async调用awaiton的函数时methodB,以及当 ismethodA是一个正常的(不是async)函数,该函数返回Promise链接到Promise返回的 by 时,这种方法都有效methodB(正如预期的那样,因为再次强调,async / await只是语法糖 overPromises和生成器)。