使用 Mocha 和 Supertest 测试 Express 错误

11

我需要在验收测试中测试服务器错误(Express),但这些错误不能(或不应该)与响应一起发送,例如:

Error: Can't set headers after they are sent.

通过使用错误处理程序捕获错误并响应 5XX 状态码将提供有价值的反馈信息,但问题在于头已经被发送。

这种错误可能是非关键性的,很难发现,通常是从日志中找出来的。

规范是

it('should send 200', function (done) {
    request(app).get('/').expect(200, done);
});

已测试的应用程序是:

app.get('/', function (req, res, next) {
    res.sendStatus(200);
    next();
});

app.use(function (req, res) {
    res.sendStatus(200);
});

在类似情况下,Express app 实例和请求测试库(如Supertest)之间进行通信的最合适方式是什么?

该问题并不限于Supertest。如果有其他能够解决Supertest无法解决的问题的软件包,也可以考虑使用。


1
@KlwntSingh 当然可以。问题是如何在测试中检测出这种问题。你需要了解问题才能够修复它。 - Estus Flask
@estus 你好,现在我明白了问题。使用测试库时,即使API有问题,测试也会通过,因为API已经返回了响应,但是服务器会停止,是吗? - KlwntSingh
@KlwntSingh 更糟糕的是,这样做并不能停止服务器。因此,唯一发现出了问题的方式是在服务器日志中无意中注意到错误。即使它不会导致测试失败,这显然意味着某些中间件存在冲突。这就是我试图避免的情况。我的目标是,如果Express错误处理程序收到错误(正如上面所说,它不能仅回复500,因为头已经发送),则测试失败。 - Estus Flask
也许在代码中使用response.finished可能会有所帮助? - Myonara
当response.finished工作时,下一个/稍后的路由器例程可以检查这一点,以避免错误并不发送任何信息。 - Myonara
显示剩余8条评论
3个回答

1
尝试仅设置状态码而不发送它,并避免通过使用res.status()两次发送错误。
正如express文档所说:

设置响应的HTTP状态。 它是Node response.statusCode的可链接别名。

在我看来,如果您想在端到端(E2E)测试工具(如supertestSelenium)中检测它,您必须处理express错误并发送正确的输出(500状态错误,一些消息...)以便检测它。
或者使用单元测试,使用chai或本地断言测试控制器函数是否未引发任何错误。

因此,响应不会被发送。问题的重点是检测错误,而不是修改应用程序代码。 - Estus Flask
谢谢。关于500错误,问题仍然存在 - 一旦发送了200响应,就无法将其更改为500。而单元测试应该在与其他单元隔离的情况下进行。这意味着测试可能会针对特定中间件通过,但当多个中间件堆叠时会导致错误。 - Estus Flask
是的,它破坏了中间件链,你只能检查最终输出是否与你预期的不同。 - Dario

1

我在这里回答了一个类似的问题。"can't set headers after they have been set"错误应该由express作为未处理的异常引发。因此,您应该能够通过进程引发的unhandledException事件来访问它。

但是,由于时间关系,这变得更加棘手。您的测试用例的expect函数和done函数将在第一次res.statusCode调用后的下一个tick上排队进行处理。不幸的是,下一次对res.statusCode的调用可能会在不确定的时间之后发生。例如,如果第二个路由处理程序调用了一个非常慢的Web服务或数据库,然后调用了res.statusCode。

考虑到这一点,您的选择非常困难。暴力的方法是在测试代码中等待一定的时间,然后检查。这很有效,但速度很慢,不确定性很大,这将导致您的测试变得不稳定。

另一种选择是检查您在express中可能拥有的任何仪表代码。 如果您在express中编写了代码以记录各个路由处理程序的进程调用数量指标,则可以将这些指标暴露给测试代码。 然后,您完成测试的条件之一是所有进程路由调用的指标均为0。 第二个选项允许您编写确定性测试,并且速度更快,因为您可以轮询指标。
最后一个选项是通过单元测试来处理此测试用例。 这可能是最好的解决方案,因为它是确定性的,不需要任何轮询。 但是,缺点是您需要知道两个函数按顺序调用,这会使您在测试代码中尝试重新创建express用于调用路由处理程序的逻辑。

-2

我使用了HAPI而不是Express来完成这个任务,但是我解决了同样的问题。我使用了一个外部库来进行调用(例如request-promise),并且它起作用了。在request-promise响应中捕获错误。


1
我不确定你的意思。在这个例子中,request-promise无法捕获错误,因为响应本身没有错误——它是200。请参见评论,这已经在那里讨论过了。 - Estus Flask

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接