异步外部函数留下开放句柄 - Jest、Supertest、Express

问题描述 投票:0回答:4

我开始使用 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
处于同一层级。所以,如果我想模拟这个函数,我必须在一侧设置带有不记名令牌的授权标头(这很容易用 Supertest 完成),在另一侧设置由 Auth0 提供的
user
信息;但这最后一步是不可能的(至少据我所知;否则,如何在与主体相同的层次结构级别而不是在主体内部设置
user
信息属性?)

我尝试过的事情

我尝试为测试和

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

任何想法将不胜感激!

node.js express jestjs supertest
4个回答
6
投票

我今天也遇到了类似的问题,未能找到明确的解决方案,但找到了解决方法。解决方法(由 alfreema 发布)是在拨打 axios.post 之前

添加以下行:

await process.nextTick(() => {});
这似乎让 Axios 完成了它的内务处理,并准备好跟踪随后打开的新连接。这只是我的猜测,我希望其他人可以提供更多信息并提供正确的解决方案。


2
投票
谢谢@RocketR,这真的很有效。

await process.nextTick(() => { }); const newData = await axios.post(`${url}`, pattern); const token = await axios.post(`${url}/token`, tokenData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
    

2
投票
每次电话我都会拨打

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

示例:

await process.nextTick(() => {}); await supertest(app) .get("/api/getBooking") .set({ Authorization: `Bearer ${TOKEN}` }) .then((response) => { expect(response.statusCode).toBe(200); })
    

0
投票
Mongoose 似乎有一个奇怪的行为以及对

nextTick

 的内部调用,按照此处的建议,最后我通过在调用 
nextTick()
 POST 之前添加 
supertest
 解决了问题。

afterAll(async () => { await dbMock.closeDatabase(); server.close(); }); it(`POST "${v1}/products" responds with the entity created`, async () => { const payload: IProduct = { /* ... */ }; // odd fix for Jest open handle error await Promise.resolve(process.nextTick(Boolean)); const reply = await request(server).post(`${v1}/products`).send(payload); expect(reply.statusCode).toEqual(200); });
由于 

process.nextTick()

 方法不返回 
Promise
 而是返回 
void
,我将调用包装在 
Promise.resolve()
 中,现在它变为可等待的。

© www.soinside.com 2019 - 2024. All rights reserved.