我开始使用 Jest 和 Supertest(针对端点)测试我的应用程序。测试工作顺利,但Jest 在运行测试后检测到 2 个打开的句柄,这会阻止 Jest 干净退出。
此打开句柄是由我的测试文件中调用的外部异步函数生成的。我正在使用外部函数从 Auth0 API 请求 JWT 令牌;但对 Auth0 的请求还在其响应中提供了传递端点中间件的关键信息(有关此的更多信息如下)。这里要记住两件事:
user
对象。 Auth0 将此对象设置在主体响应之外的同一级别,但不在其中。该信息是传递端点中间件的关键。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 的请求过程是否在响应后关闭,并且实际上,该连接关闭,但在运行测试后我仍然收到打开句柄错误。
任何想法将不胜感激!
我今天也遇到了类似的问题,未能找到明确的解决方案,但找到了解决方法。解决方法(由 alfreema 发布)是在拨打 axios.post
之前
添加以下行:
await process.nextTick(() => {});
这似乎让 Axios 完成了它的内务处理,并准备好跟踪随后打开的新连接。这只是我的猜测,我希望其他人可以提供更多信息并提供正确的解决方案。
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'
}
});
await process.nextTick(() => {});
。示例:
await process.nextTick(() => {});
await supertest(app)
.get("/api/getBooking")
.set({ Authorization: `Bearer ${TOKEN}` })
.then((response) => {
expect(response.statusCode).toBe(200);
})
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()
中,现在它变为可等待的。