开玩笑等待 express 的默认错误处理程序

Jest await default error handler of express

给定以下设置:

const express = require("express");
const app = express();

app.get("/", function(req, res, next) {
  // explicitly return an error
  return next("my error");
});

// made this middleware for the example,
// solution should typically also work for express default error handling
app.use(function(error, req, res, next) {
  if (error) {
    res.status(500).send({ error });
    throw new Error(error); // <- how to test this?
  }
  next();
});

app.listen(8080, function() {
  console.log("server running on 8080");
}); //the server object listens on port 8080

测试:

const request = require("supertest");
const app = require("../../app.js");

const spy = jest.spyOn(global.console, "error").mockImplementation();

it("throws an error", async done => {
  const res = await request(app).get("/");

  expect(res.status).toBe(500);
  expect(res.error.text).toContain("my error");

  expect(spy).toHaveBeenCalled(); // nothing...

  done();
});

用这个 example code 制作了一个 Codesandbox。不过不确定如何 运行 进行节点测试。

async 不应与 done 一起使用,如果无法达到 done(),这会导致测试超时。

首先,错误处理程序不应该重新抛出错误,除非它是应该用另一个处理程序扩充的可重用路由器实例。如果它是连续的最后一个,它应该捕获其中可能发生的同步和异步错误。

问题是默认的错误处理程序是 triggered asynchronously 所以应该特别等待:

it("throws an error", async () => {
  const spy = jest.spyOn(global.console, "error");
  const res = await request(app).get("/");

  expect(res.status).toBe(500);
  expect(res.error.text).toContain("my error");
  await new Promise(resolve = > setTimeout(resolve));
  expect(spy).not.toHaveBeenCalled(); // it really shouldn't
});

更正确的处理方法是确保错误得到处理:

it("throws an error", async () => {
  const defaultErrorHandler = jest.fn((err, req, res, next) => {});

  app.use(defaultErrorHandler);
  const res = await request(app).get("/");

  expect(res.status).toBe(500);
  expect(res.error.text).toContain("my error");
  expect(defaultErrorHandler).not.toHaveBeenCalled();
});