验证是否使用 Mocha / Chai 和 async/await 引发异常



我正在努力找出最好的方法来验证在使用 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);
});

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

await解决Promise。如果Promise拒绝,则会抛出拒绝的值。

所以(await fails()).should.throw(Error)永远行不通:如果fails()拒绝,就会抛出错误,并且永远不会执行.should.throw(Error)

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

下面是一个快速示例。与您在问题中展示的内容没有太大区别;我只是将async函数用于wins()fails()expect,而不是should.当然,您可以使用返回Promise的函数,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)的承诺。因此,您必须在async测试函数的上下文中await它们,否则失败条件仍将通过:

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)

正如你所看到的,柴断言实际上失败了,但它们在一个没有人awaitcatch编辑的承诺的背景下失败了。因此,Mocha 没有看到失败并将测试标记为通过,但 Node.js(如上所述,将来会发生变化的行为)将未处理的拒绝打印到终端。

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

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"))
})
})
  1. 安装柴 (npm 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; 
});
});

此示例仅适用于 Node!

当你在 Node 上使用 Mocha 时.js你可以使用doesNotReject()rejects()两者都需要一个返回 promise 的函数。


何时应拒绝的示例:

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

您可以使用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");
});

如果测试你的承诺函数,在测试中必须将代码包装在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...');
}
});

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

我带来了这个解决方案:

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

在柴上,我得到错误Property 'rejectedWith' does not exist on type 'Assertion'为最高答案。下面是一个快速的解决方案,柴如承诺可能更好长期。

const fail = () => { expect(true).to.eq(false) }
it('passes', async () => {
return wins().then((res) => { expect(res).to.eq('Winner') }, fail)
})
it('throws an error', async () => {
return fails().then(fail, (err) => { expect(err).to.eq('Contrived Error') })
})

这是我的问题解决方案.

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

除了摩卡的例子之外,不依赖于任何东西

抛出已知错误,捕获所有错误,仅重新引发已知错误。

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

你可以编写一个函数来交换solve和reject handler,并正常执行任何操作

const promise = new Promise((resolve, rejects) => {
YourPromise.then(rejects, resolve);
})
const res = await promise;
res.should.be.an("error");

另一种方法(适用于异步函数,但在测试中不使用 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();
});
});

在文件顶部添加以下内容:

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

下面是解决方案的 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的解决方案

我尝试了此页面上的每个答案,但我真的不想安装额外的 chai 包只是为了在一次测试中捕获一个错误。所以我采用了这种方式,它似乎与基本的 chai 设置一起工作很顺利:

try {
await mockClass.errorPromise();
}
catch (error: any) {
expect(error).to.be.instanceof(Error);
expect(error.message).to.equal("Argument is required");
}

最新更新