Given the following setup:
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
And for the test:
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();
});
Made a Codesandbox with this example code. Not sure how to run a node test in that though.
async
shouldn't be used with done
, this results in test timeout in case done()
cannot be reached.
First of all, error handler shouldn't re-throw an error, unless it's reusable router instance that is supposed to be augmented with another handler. If it's the last one in a row, it should catch both synchronous and asynchronous errors that can happen inside of it.
The problem is that default error handler is triggered asynchronously so it should be specifically awaited:
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
});
A more correct way to approach this is to make sure the error is handled:
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();
});