首页 > 解决方案 > 异步外部函数留下开放句柄 - Jest、Supertest、Express

问题描述

我开始使用 Jest 和 Supertest(用于端点)测试我的应用程序。测试工作顺利,但Jest 在运行测试后检测到 2 个打开的句柄,这会阻止 Jest 干净地退出。

这个打开的句柄是由在我的测试文件中调用的外部异步函数生成的。我正在使用外部函数从 Auth0 API 请求 JWT 令牌;但是对 Auth0 的请求还在其响应中提供了传递端点中间件的关键信息(有关此内容的更多信息,请参见下文)。这里要记住两件事:

  1. 到目前为止,我无法避免从 Auth0 请求令牌,因为正如我所说,该响应还包含一个user带有关键信息的对象。Auth0 将此对象设置在主体响应之外,在同一级别,但不在其中。该信息是传递端点中间件的关键。
  2. 我已经隔离了所有错误,以确保仅当我调用从 Auth0 API 请求令牌和用户信息的外部异步函数时才会出现问题;getToken该问题是通过在测试文件中使用该函数(称为)生成的。

测试文件代码

import app from "../app";
import mongoose from "mongoose";
import supertest from "supertest";
import { getToken } from "../helpers";
import dotenv from "dotenv";
import * as config from "../config";

dotenv.config();

const api = supertest(app);

let authToken: any;
let db: any;

beforeAll(async() => {
  try {
    mongoose.connect(config.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true,
    });
    db = mongoose.connection;
    db.on("error", console.error.bind(console, "Console Error:"));
    db.once("open", () =>
      console.log(`App connected to "${db.name}" database`)
    );
    authToken = await getToken()
  } catch (err) {
    return err
  }
});

describe("GET /interview/:idCandidate", () => {
  test("With auth0 and read permissions", async () => {
       await api
        .get("/interview/1")
        .set("Authorization", "Bearer " + authToken)
        .expect(200)
  });
});

afterAll(async () => {
  try {
    await db.close();
  } catch (err) {
    return err;
  }
});

getToken向 Auth0 API 请求信息的外部函数

从外部模块导入的getToken函数如下:

import axios from 'axios'

var options = {
    url: //url goes here,
    form:
    {
      // form object goes here
    },
    json: true
  };
  
  const getToken = async () => {
    try {
      const tokenRequest = await axios.post(options.url, options.form)
      return tokenRequest.data.access_token
    } catch (err){
      return err
    }
  } 


export default getToken;

问题

运行我的测试后,它们会按预期运行,直到 Jest 的--detectOpenHandles配置检测到以下两个打开的句柄:

Jest has detected the following 2 open handles potentially keeping Jest from exiting:

  ●  TLSWRAP

      60 |             case 0:
      61 |                 _a.trys.push([0, 2, , 3]);
    > 62 |                 return [4 /*yield*/, axios_1.default.post(options.url, options.form)
         |                                                      ^
      63 |                 ];  
      64 |             case 1:    

      at RedirectableRequest.Object.<anonymous>.RedirectableRequest._performRequest (node_modules/follow-redirects/index.js:265:24)
      at new RedirectableRequest (node_modules/follow-redirects/index.js:61:8)
      at Object.request (node_modules/follow-redirects/index.js:456:14)
      at dispatchHttpRequest (node_modules/axios/lib/adapters/http.js:202:25)
      at httpAdapter (node_modules/axios/lib/adapters/http.js:46:10)
      at dispatchRequest (node_modules/axios/lib/core/dispatchRequest.js:53:10)
      at Axios.request (node_modules/axios/lib/core/Axios.js:108:15)
      at Axios.<computed> [as post] (node_modules/axios/lib/core/Axios.js:140:17)
      at Function.post (node_modules/axios/lib/helpers/bind.js:9:15)
      at call (dist/helpers/getToken.js:62:54)
      at step (dist/helpers/getToken.js:33:23)
      at Object.next (dist/helpers/getToken.js:14:53)
      at dist/helpers/getToken.js:8:71
      at __awaiter (dist/helpers/getToken.js:4:12)
      at Object.token (dist/helpers/getToken.js:56:34)
      at call (dist/test/api.test.js:87:48)
      at step (dist/test/api.test.js:52:23)
      at Object.next (dist/test/api.test.js:33:53)
      at dist/test/api.test.js:27:71
      at __awaiter (dist/test/api.test.js:23:12)
      at dist/test/api.test.js:72:32


  ●  TLSWRAP

      141 |             switch (_a.label) {
      142 |                 case 0: return [4 /*yield*/, api
    > 143 |                         .get("/interview/1")
          |                          ^
      144 |                         .set("Authorization", "Bearer " + authToken)
      145 |                         .expect(200)];
      146 |                 case 1:

      at Test.Object.<anonymous>.Test.serverAddress (node_modules/supertest/lib/test.js:61:33)
      at new Test (node_modules/supertest/lib/test.js:38:12)
      at Object.get (node_modules/supertest/index.js:27:14)
      at call (dist/test/api.test.js:143:26)
      at step (dist/test/api.test.js:52:23)
      at Object.next (dist/test/api.test.js:33:53)
      at dist/test/api.test.js:27:71
      at __awaiter (dist/test/api.test.js:23:12)
      at Object.<anonymous> (dist/test/api.test.js:139:70)

我确定错误来自这个getToken异步函数。

为什么我不嘲笑这个功能?

您可能想知道为什么我不嘲笑该函数,正如我之前所说,当 Auth0 使用令牌响应时(顺便说一下,它经常刷新),它还会响应有关用户的信息,并且该信息超出了response.body. 事实上,它与body. 所以,如果我想模拟这个函数,我必须在一侧设置 Authorization 标头和不记名令牌(这对于 Supertest 很容易做到),user另一侧是 Auth0 提供的信息;但是这最后一步是不可能的(至少据我所知;否则,您如何user在与主体相同的层次结构级别而不是在其中设置信息属性?)

我尝试过的事情

我尝试在测试中添加更长的超时时间beforeAll();我尝试添加done回调而不是使用async/awaitinsidebeforeAll()和其他一些不太重要的东西,但它们都没有解决打开句柄问题。事实上,我已经检查了对 Auth0 API 的请求过程是否在响应后关闭,并且有效地关闭了连接,但在运行测试后我仍然收到打开句柄错误。

任何想法将不胜感激!

标签: node.jsexpressjestjssupertest

解决方案


我今天也一直在努力解决类似的问题,未能找到明确的解决方案,但找到了解决方法。解决方法(由alfreema发布)是在调用之前添加以下行axios.post

await process.nextTick(() => {});

这似乎允许 Axios 完成其内部管理并准备好跟踪之后打开的新连接。这只是我的猜测,我希望其他人能对此有所了解并提供适当的解决方案。


推荐阅读