验证是否使用 Mocha/Chai 和 sync/wait 引发了异常

我正在努力寻找在使用异步/等待时验证承诺是否在 Mocha 测试中被拒绝的最佳方法。

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

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

感觉应该可以使用这样一个事实,即异步/等待将拒绝转换为异常,并将其与 Chai 的 should. throw 结合起来,但是我还不能确定正确的语法。

理想情况下,这种做法会奏效,但似乎并不奏效:

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


(await fails()).should.throw(Error);
});
64186 次浏览

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

await解析一个 Promise。如果 Promise拒绝,它将抛出被拒绝的值。

因此,(await fails()).should.throw(Error)永远不能工作: 如果 fails()拒绝,将抛出一个错误,并且 .should.throw(Error)永远不会执行。

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

举个简单的例子。与您在问题中演示的内容没有太大的不同; 我只是对 wins()fails()expect使用 async函数,而不是对 should使用 async函数。当然,您可以使用返回 Promisechai.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)

正如您所看到的,chai 断言实际上是失败的,但是它们在一个从来没有人 await0或 catched 的承诺的上下文中是失败的。因此,Mocha 没有发现任何失败,并且将测试标记为通过,但是 Node.js (如上所述,其行为将在未来发生变化)将未处理的拒绝打印到终端。

这是我解决这个问题的方法。

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

如果测试的是您承诺的函数,那么测试中的代码必须包装在 try/catch 中,而且 Expal ()必须包装在 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...');
}
});

这就是我的方法,没有更好的方法。

我使用这样一个自定义函数:

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

此示例只适用于 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

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

一个 除了摩卡什么都不依赖的例子。

抛出已知错误,捕获所有错误,只重新抛出已知错误。

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

您可以编写一个函数来交换解析和拒绝处理程序,并正常地做任何事情

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

我带来了这个解决方案:

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

另一种方法(适用于异步函数,但在测试中不使用 wait)是使用断言错误调用 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();
});
});
  1. 为 chai 安装 说好的印度茶(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;
});
});

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

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

在印度,我得到的错误 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') })
})