63

我正在努力寻找在使用 async/await 时验证承诺在 Mocha 测试中被拒绝的最佳方法。

这是一个有效的示例,但我不喜欢它should.be.rejectedWith返回一个需要从测试函数返回才能正确评估的承诺。使用 async/await 消除了对测试值的这种要求(就像我对下面的结果所做的wins()那样),我觉得我很可能会在某些时候忘记 return 语句,在这种情况下测试总是会通过。

// Always succeeds
function wins() {
  return new Promise(function(resolve, reject) {
    resolve('Winner');
  });
}

// Always fails with an error
function fails() {
  return new Promise(function(resolve, reject) {
    reject('Contrived Error');
  });
}

it('throws an error', async () => {
  let r = await wins();
  r.should.equal('Winner');

  return fails().should.be.rejectedWith('Contrived Error');
});

感觉应该可以使用 async/await 将拒绝转换为异常并将其与 Chai 的 should.throw 相结合的事实,但我无法确定正确的语法。

理想情况下,这会起作用,但似乎不起作用:

it('throws an error', async () => {
  let r = await wins();
  r.should.equal('Winner');

  (await fails()).should.throw(Error);
});
4

13 回答 13

84

这种方法的问题是(await fails()).should.throw(Error)没有意义。

await解决一个Promise. 如果Promise拒绝,则抛出拒绝的值。

所以(await fails()).should.throw(Error)永远无法工作:如果fails()拒绝,则会引发错误,并且.should.throw(Error)永远不会执行。

正如您在问题中所显示的那样,您拥有的最惯用的选择是使用 Chai 的rejectedWith属性。

这是一个简单的例子。与您在问题中展示的内容没有什么不同;我只是使用async函数 for wins()andfails()expect不是should. 当然,您可以使用返回 aPromise并且chai.should很好的函数。

const chai = require('chai')
const expect = chai.expect
chai.use(require('chai-as-promised'))

// Always succeeds
async function wins() {
  return 'Winner'
}

// Always fails with an error
async function fails() {
  throw new Error('Contrived Error')
}

it('wins() returns Winner', async () => {
  expect(await wins()).to.equal('Winner')
})

it('fails() throws Error', async () => {
  await expect(fails()).to.be.rejectedWith(Error)
})

如果您希望您的测试更接近wins()您的fails()测试,您可以这样编写您的wins()测试:

it('wins() returns Winner', async () => {
  await expect(wins()).to.eventually.equal('Winner')
})

在这两个示例中要记住的关键是chai-as-promised返回对其函数的承诺,例如rejectedWitheventually.something。因此,您必须await在测试函数的上下文中使用它们async,否则失败的条件仍然会通过:

async function wins() {
  return 'Loser'
}

async function fails() {
  return 'Winner'
}

it('wins() returns Winner', async () => {
  expect(wins()).to.eventually.equal('Winner')
})

it('fails() throws Error', async () => {
  expect(fails()).to.be.rejectedWith(Error)
})

如果您使用上面的代码运行测试,您将得到以下结果:

$ npm test

> mocha-chai-async@1.0.0 test /home/adaline/code/mocha-chai-async
> mocha .



  √ wins() returns Winner
(node:13836) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rej
ection id: 1): AssertionError: expected 'Loser' to equal 'Winner'
(node:13836) [DEP0018] DeprecationWarning: Unhandled promise rejections are dep
recated. In the future, promise rejections that are not handled will terminate
the Node.js process with a non-zero exit code.
  √ fails() throws Error
(node:13836) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rej
ection id: 2): AssertionError: expected promise to be rejected with 'Error' but
 it was fulfilled with 'Winner'

  2 passing (11ms)

如您所见,chai 断言实际上失败了,但它们在没有人await编辑或catch编辑的 Promise 的上下文中失败了。所以 Mocha 没有看到失败,并将测试标记为通过,但是 Node.js(在未来会发生变化的行为,如上所述)将未处理的拒绝打印到终端。

于 2017-08-04T00:28:10.390 回答
24

我使用这样的自定义函数:

const expectThrowsAsync = async (method, errorMessage) => {
  let error = null
  try {
    await method()
  }
  catch (err) {
    error = err
  }
  expect(error).to.be.an('Error')
  if (errorMessage) {
    expect(error.message).to.equal(errorMessage)
  }
}

然后,对于常规的异步函数,例如:

const login = async (username, password) => {
  if (!username || !password) {
    throw new Error("Invalid username or password")
  }
  //await service.login(username, password)
}

我这样写测试:

describe('login tests', () => {
  it('should throw validation error when not providing username or passsword', async () => {

    await expectThrowsAsync(() => login())
    await expectThrowsAsync(() => login(), "Invalid username or password")
    await expectThrowsAsync(() => login("username"))
    await expectThrowsAsync(() => login("username"), "Invalid username or password")
    await expectThrowsAsync(() => login(null, "password"))
    await expectThrowsAsync(() => login(null, "password"), "Invalid username or password")

    //login("username","password") will not throw an exception, so expectation will fail
    //await expectThrowsAsync(() => login("username", "password"))
  })
})
于 2019-05-13T00:29:39.550 回答
14
  1. 为 chai ( )安装chai-as-promisednpm i chai-as-promised -D
  2. 只需调用你的承诺,不应该应用等待!
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';

chai.use(chaiAsPromised);

const expect = chai.expect;

describe('MY_DESCR', () => {
  it('MY_TEST', async () => {
    expect(myAsyncFunctionThatWillReject()).to.eventually.be.rejected; 
  });
});
于 2020-07-31T11:24:33.140 回答
9

此示例仅适用于 Node!

当您在 Node.js 上使用 Mocha 时,您可以使用doesNotReject()rejects()两者都需要返回承诺的函数。


何时应该拒绝的示例:

await rejects(testFunction());

见:https ://nodejs.org/api/assert.html#assert_assert_rejects_asyncfn_error_message

何时不应拒绝的示例:

await doesNotReject(testFunction());

见:https ://nodejs.org/api/assert.html#assert_assert_doesnotreject_asyncfn_error_message

于 2019-09-27T21:18:39.047 回答
5

您可以使用async/awaitshould进行简单的验证

it('should not throw an error', async () => {
  try {
    let r = await wins();
    r.should.equal('Winner');
  } catch (error) {
    error.should.be.null(); //should.not.exist(error) can also be used
  }
});

it('throws an error', async () => {
  let err;
  try {
    await fails();
  } catch (error) {
    err = error;
  }
    err.should.be.Error();
    err.should.have.value("message", "Contrived Error");
});
于 2019-10-13T07:54:45.287 回答
3

我有这个解决方案:

import { assert, expect, use } from "chai";
import * as chaiAsPromised from "chai-as-promised";

describe("using chaiAsPromised", () => {
    it("throws an error", async () => {
        await expect(await fails()).to.eventually.be.rejected;
    });
});
于 2019-12-06T09:15:58.213 回答
1

如果测试你的 Promised 函数,在测试中必须将代码包装在 try/catch 中,并且 expect() 必须在 catch 错误块内

const loserFunc = function(...args) {
  return new Promise((resolve, rejected) => {
    // some code
    return rejected('fail because...');
  });
};

那么,在你的测试中

it('it should failt to loserFunc', async function() {
  try {
    await loserFunc(param1, param2, ...);
  } catch(e) {
    expect(e).to.be.a('string');
    expect(e).to.be.equals('fail because...');
  }
});

这是我的方法,不知道更好的方法。

于 2019-04-14T04:37:18.460 回答
0

除了 Mocha示例之外,不依赖任何东西。

抛出一个已知错误,捕获所有错误,然后只重新抛出已知错误。

  it('should throw an error', async () => {
    try {
      await myFunction()
      throw new Error('Expected error')
    } catch (e) {
      if (e.message && e.message === 'Expected error') throw e
    }
  })

如果您经常测试错误,请将代码包装在自定义it函数中。

function itThrows(message, handler) {
  it(message, async () => {
    try {
      await handler()
      throw new Error('Expected error')
    } catch (e) {
      if (e.message && e.message === 'Expected error') throw e
    }
  })
}

然后像这样使用它:

  itThrows('should throw an error', async () => {
    await myFunction()
  })
于 2019-11-18T14:49:54.397 回答
0

在文件顶部添加:

import * as chai from 'chai';
import chaiAsPromised from 'chai-as-promised';

chai.use(chaiAsPromised)

那么断言应该是这样的:

await expect(
    yourFunctionCallThatReturnsAnAwait()
).to.eventually.be.rejectedWith("revert"); // "revert" in case of web3
于 2021-09-26T13:42:17.030 回答
0

您可以编写一个函数来交换解析和拒绝处理程序,并正常执行任何操作

const promise = new Promise((resolve, rejects) => {
    YourPromise.then(rejects, resolve);
})
const res = await promise;
res.should.be.an("error");
于 2019-11-25T09:46:48.820 回答
0

这是我解决问题的方法。

    try {
        // here the function that i expect to will return an errror
        let walletid = await Network.submitTransaction(transaction)
    } catch (error) {
        //  assign error.message to ErrorMessage
        var ErrorMessage = error.message;
        //  catch it and  re throw it in assret.throws fn and pass the error.message as argument and assert it is the same message expected
        assert.throws(() => { throw new Error(ErrorMessage) },'This user already exists');
    }
    // here assert that ErrorMessage is Defined ; if it is not defined it means that no error occurs
    assert.isDefined(ErrorMessage);
于 2019-01-28T23:55:27.060 回答
0

这是解决方案的 TypeScript 实现:

import { expect } from 'chai';

export async function expectThrowsAsync(
  method: () => Promise<unknown>,
  errorMessage: string,
) {
  let error: Error;
  try {
    await method();
  } catch (err) {
    error = err;
  }
  expect(error).to.be.an(Error.name);
  if (errorMessage) {
    expect(error.message).to.equal(errorMessage);
  }
}

灵感来自@kord 的解决方案

于 2022-01-21T20:32:06.480 回答
0

另一种方式(适用于 async 函数,但不在测试中使用 await)是调用done一个断言错误:

it('should throw Error', (done) => {
  myService.myAsyncMethod().catch((e) => {
    try {
      // if you want to check the error message for example
      assert.equal(e.message, 'expected error');
    } catch (assertionError) {
      done(assertionError); // this will fail properly the test
      return; // this prevents from calling twice done()
    }

    done();
  });
});
于 2020-07-24T08:14:47.563 回答