Javascript 开玩笑不';无法使用util.promisify(设置超时)

Javascript 开玩笑不';无法使用util.promisify(设置超时),javascript,node.js,jestjs,es6-promise,Javascript,Node.js,Jestjs,Es6 Promise,我知道有很多类似的问题,但我相信我的问题是不同的,没有任何当前的答案回答 我正在Express.JS中测试REST API。下面是一个简单的工作示例和几个不同编号的测试用例 const express = require("express"); let request = require("supertest"); const { promisify } = require("util"); const app = express(); request = request(app); cons

我知道有很多类似的问题,但我相信我的问题是不同的,没有任何当前的答案回答

我正在Express.JS中测试REST API。下面是一个简单的工作示例和几个不同编号的测试用例

const express = require("express");
let request = require("supertest");
const { promisify } = require("util");

const app = express();
request = request(app);
const timeOut = promisify(setTimeout);

const timeOut2 = time =>
  new Promise(resolve => {
    setTimeout(resolve, time);
  });

app.locals.message = "Original string";

app.get("/one", async (req, res) => {
  await timeOut(1000);
  res.send(app.locals.message);
});

app.get("/two", (req, res) => {
  res.send(app.locals.message);
});

app.get("/three", async (req, res) => {
  await timeOut2(1000);
  res.send(app.locals.message);
});

test("1. test promisify", async () => {
  expect.assertions(1);
  const response = await request.get("/one");
  expect(response.text).toEqual("Original string");
});

test("2. test promisify with fake timers", () => {
  expect.assertions(1);
  jest.useFakeTimers();
  request.get("/one").then(res => {
    expect(res.text).toEqual("Original string");
  });
  jest.runAllTimers();
});

test("3. test promisify with fake timers and returning pending promise", () => {
  expect.assertions(1);
  jest.useFakeTimers();
  const response = request.get("/one").then(res => {
    expect(res.text).toEqual("Original string");
  });
  jest.runAllTimers();
  return response;
});

test("4. test no timeout", async () => {
  expect.assertions(1);
  const response = await request.get("/two");
  expect(response.text).toEqual("Original string");
});

test("5. test custom timeout", async () => {
  expect.assertions(1);
  const response = await request.get("/three");
  expect(response.text).toEqual("Original string");
});

test("6. test custom timeout with fake timers", () => {
  expect.assertions(1);
  jest.useFakeTimers();
  const response = request.get("/three").then(res => {
    expect(res.text).toEqual("Original string");
  });
  jest.runAllTimers();
  return response;
});
在虚拟环境中运行测试表明只有测试5通过。 那么我的第一个问题是为什么测试5通过而不是测试1,因为它们是完全相同的测试,而不是基于承诺的延迟的不同实现。 这两种实现在Jest测试之外都能完美地工作(使用Supertest测试而不使用Jest)

虽然测试5通过了,但它使用的是实时定时器,因此并不理想。就我所见,测试6应该是伪计时器(我还尝试了一个在
然后
正文中调用done()的版本),但这也失败了

我的web应用程序有一个使用
util.promisify(setTimeout)
的处理程序的路由,因此,Jest试图测试它,即使是使用实时程序,这一事实使该框架对我的用处大大降低。考虑到定制实现(测试5)确实有效,这似乎是一个bug

尽管如此,Jest仍然无法使用模拟计时器在测试6上工作,因此即使我重新实现我的应用程序中的延迟(我不想这样做),我仍然必须忍受无法加速的缓慢测试


这两个问题中有一个是预期行为吗?如果不是,我做错了什么?

这是一个有趣的问题。它一直到核心内置函数的实现


为什么测试5通过而测试1不通过 这花了一段时间才追上

jsdom
提供

jsdom
测试环境中调用
promisify(setTimeout)
将返回在
jsdom
提供的
setTimeout
上运行创建的函数

相反,如果
Jest
正在
节点中运行,则调用
promisify(setTimeout)
只返回

这个简单的测试通过了
节点
测试环境,但挂起在
jsdom

const{promisify}=require('util');
测试('promisify(setTimeout)',()=>{
返回promisify(setTimeout)(0)。然后(()=>{
期望(真),期望(真);
});
});
结论:由
jsdom
提供的
setTimeout
promisify
-ed版本不起作用

如果在
节点
测试环境中运行,测试1和测试5都通过


使用
promisify(setTimeout)
和计时器模拟的测试代码 听起来真正的问题是如何使用以下工具测试这样的代码:

app.js

const express=require(“express”);
const{promisify}=require(“util”);
常量app=express();
const timeOut=promisify(设置超时);
app.locals.message=“原始字符串”;
app.get(“/one”,异步(请求,res)=>{
等待超时(10000);//等待10秒
res.send(app.locals.message);
});
导出默认应用程序;
我花了一段时间才弄明白,我将逐一介绍每个部分

模拟
promisify(设置超时)
在没有模拟的情况下,无法使用计时器模拟测试使用
promisify(setTimeout)
的代码
promisify(setTimeout)

  • jsdom
    环境中
    promisify(setTimeout)
    将挂起
  • 节点
    环境中
    promisify(setTimeout)
    将是一个不调用
    setTimeout
    的函数,因此当它没有任何效果时
promisify(setTimeout)
通过创建以下
\uuuuumock\uuuuu/util.js

const util=require.requireActual('util');//得到真实的信息
const realPromisify=util.promisify;//抓住真正的承诺
util.promisify=(…参数)=>{
if(args[0]==setTimeout){//返回模拟if promisify(setTimeout)
返回时间=>
新承诺(解决=>{
setTimeout(解析,时间);
});
}
返回realPromisify(…args);/…否则调用realPromisify
}
module.exports=util;
注意调用
jest.mock('util')在测试中

按一定间隔调用jest.runAllTimers() 事实证明,
request.get
supertest
中启动了一个完整的过程,该过程使用,并且在当前正在运行的消息(测试)完成之前不会运行任何内容

这是有问题的,因为
request.get
最终将运行
app.get
,然后调用
wait timeOut(10000)在调用
jest.runAllTimers
之前不会完成

同步测试中的任何内容都将在
请求之前运行。如果测试期间运行了
jest.runAllTimers
,则get
会执行任何操作,这不会对以后调用
等待超时(10000)产生任何影响

此问题的解决方法是设置一个间隔,定期将调用
jest.runAllTimers
的JavaScript事件循环中的消息排队。调用
的消息等待超时时(10000)运行它将在该行暂停,然后将运行一条调用
jest.runAllTimers
的消息,等待
的消息将等待超时(10000)将能够继续并且
请求。get
将完成

捕获设置间隔和清除间隔 最后一点需要注意的是
jest.useFakeTimers
包括
setInterval
clearInterval
,因此为了设置间隔并清除它,我们需要在调用
jest.useFakeTimers
之前捕获真正的函数


考虑到所有这些,下面是对上面列出的app.js代码的工作测试:

jest.mock('util');//必须显式模拟core Node.js模块
const supertest=require(‘supertest’);
从“./app”导入应用程序;
骗局