JavaScript/Mocha-如何测试是否已等待函数调用 [英] JavaScript / Mocha - How to test if function call was awaited

查看:80
本文介绍了JavaScript/Mocha-如何测试是否已等待函数调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想编写一个测试,检查我的函数是否使用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.nextTicksetTimeout

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

如果methodAmethodB上调用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
  });
});



详细信息

在所有三个代码示例中,methodAmethodB都返回一个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.

不幸的是,这不是一个合理的方法,因为如果promiseAawait 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.

asyncawait只是语法在诺言和生成器上撒糖.在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.

由于我们不希望测试超时,因此promiseApromiseB 都必须解决.

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的聪明方法是向promiseApromiseB都添加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上调用awaitasync函数时,以及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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆