使用 mocha 测试 express 服务器并使用异步初始化程序进行 supertest 调用请求两次

Posted

技术标签:

【中文标题】使用 mocha 测试 express 服务器并使用异步初始化程序进行 supertest 调用请求两次【英文标题】:Testing express server with mocha and supertest with async initializer calls the request twice 【发布时间】:2020-10-27 16:00:09 【问题描述】:

我正在尝试为快速服务器建立一个测试框架,并且需要异步初始化的测试给我带来了问题。最初我尝试使用磁带和超测试,但由于磁带似乎对异步操作的整体支持很差,我改用 mocha。现在我至少收到了一些错误消息,但测试仍然无法正常工作。

我正在尝试测试一个虚拟 ping 端点 /auth_ping,它应该需要一个有效的令牌来响应 pong,否则应该响应 403。目前身份验证尚未实现,所以我正在尝试设置一个最初失败的测试,因为服务器在没有令牌的情况下响应 200 而不是 403。

我的第一次尝试是这样的

'use strict';

const request = require('supertest');
const app = require('../server');
const keycloak = require('./util/keycloak-mock');

describe("authenticated ping", function() 
    it("should respond 403 when no token is provided", function(done) 

        keycloak.fetchToken()
            .then((token) => request(app)
                 .get("/auth_ping")
                 .expect(403)
                 .end(function (err, res) 
                   if (err) 
                       done(err)
                   ;
                   done();
                 ));
    );
);

测试用例从通过异步调用从模拟身份验证器获取有效令牌开始。在此测试中,未使用令牌,但我还是想进行调用以确保已初始化模拟身份验证器。无论如何,令牌会在进一步的测试用例中使用,因此获取需要工作。

我使用这个测试得到的输出如下

  authenticated ping
Warning: superagent request was sent twice, because both .end() and .then() were called. Never call .end() if you use promises
Warning: .end() was called twice. This is not supported in superagent
superagent: double callback bug
(node:21) UnhandledPromiseRejectionWarning: SyntaxError: Unexpected token  in JSON at position 29
    at JSON.parse (<anonymous>)
    at IncomingMessage.res.on (/usr/src/app/node_modules/superagent/lib/node/parsers/json.js:11:35)
    at IncomingMessage.emit (events.js:203:15)
    at endReadableNT (_stream_readable.js:1145:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)
(node:21) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:21) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
    1) should respond 403 when no token is provided


  0 passing (2s)
  1 failing

  1) authenticated ping
       should respond 403 when no token is provided:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/usr/src/app/test/index.js)

我不太确定这里的流程是如何工作的,以及为什么会发出两次请求。是的,我同时调用 then() 和 end(),但是因为 then() 与超测无关,我不知道它如何影响请求。另外 end() 似乎是我为 mocha 调用 done() 的唯一地方,所以我看不出没有它测试如何工作。

无论如何,我尝试通过删除 end() 调用来修改测试:

'use strict';

const request = require('supertest');
const app = require('../server');
const keycloak = require('./util/keycloak-mock');

describe("authenticated ping", function() 
    it("should respond 403 when no token is provided", function(done) 

        keycloak.fetchToken()
            .then((token) => request(app)
                 .get("/auth_ping")
                 .expect(403)
            );
    );
);

之后的输出是这样的:

  authenticated ping
(node:21) UnhandledPromiseRejectionWarning: Error: expected 403 "Forbidden", got 200 "OK"
    at Test._assertStatus (/usr/src/app/node_modules/supertest/lib/test.js:268:12)
    at Test._assertFunction (/usr/src/app/node_modules/supertest/lib/test.js:283:11)
    at Test.assert (/usr/src/app/node_modules/supertest/lib/test.js:173:18)
    at Server.localAssert (/usr/src/app/node_modules/supertest/lib/test.js:131:12)
    at Object.onceWrapper (events.js:286:20)
    at Server.emit (events.js:198:13)
    at emitCloseNT (net.js:1619:8)
    at process._tickCallback (internal/process/next_tick.js:63:19)
(node:21) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:21) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
    1) should respond 403 when no token is provided


  0 passing (2s)
  1 failing

  1) authenticated ping
       should respond 403 when no token is provided:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/usr/src/app/test/index.js)

在堆栈跟踪中,我可以看到达到了预期的测试结果,因为将响应 200 与预期的 403 进行了比较,该部分现在看起来很好,但之后测试用例超时并因此而失败。我认为这是意料之中的,因为从未调用过done(),我觉得我明白这里发生了什么。

但是在我的第一次尝试中会发生什么以及如何解决这个问题?它看起来比后一种尝试更有效,但我不明白为什么请求被发送两次?

【问题讨论】:

【参考方案1】:

我相信我现在已经解决了这个问题,它与异步调用无关。

方法的正确写法似乎是

'use strict';

const request = require('supertest');
const app = require('../server');
const keycloak = require('./util/keycloak-mock');

describe("authenticated ping", function() 
    it("should respond 403 when no token is provided", function(done) 

        keycloak.fetchToken()
            .then((token) => request(app)
                 .get("/auth_ping")
                 .expect(403)
                 .end(done);
            );
    );
);

似乎我发现的关于在 mocha 中使用 supertest 的简短教程包括建议将我自己的方法添加到 end() 函数中可能是一种不好的做法。这在测试成功时可以正常工作,但如果测试在期望之一上失败,则会中断。

我没有在任何地方找到它,但我测试过的似乎你必须以 end(done) 结束命令链或将 done 作为附加参数添加到最后期望在链中(但不是任何早期的)。因此,除了添加 end(done) 之外,我还可以将其省略并以 expect(403, done) 结束链。现在我更喜欢显式结束,因为这样更容易在不破坏任何东西的情况下向链中添加额外的期望。

【讨论】:

以上是关于使用 mocha 测试 express 服务器并使用异步初始化程序进行 supertest 调用请求两次的主要内容,如果未能解决你的问题,请参考以下文章

在 Mocha 测试中使用 Superagent/Supertest 和 Express 应用程序

使用 Express、Socket.io 和 Node-Telegram-Bot-Api 结束 Mocha 测试

单元/集成测试 Express REST API, mongoose, mocha, sinon, chai, supertest

如何在 express 上创建一个从不响应的端点

Node Express Mocha 测试:TypeError: chai.request is not a function

Mocha 测试执行时间过长