In this lesson, you will learn how to verify that your Express middleware chains execute in the correct order and terminate appropriately when a request fails or meets specific conditions.
The Middleware Contract
Middleware in Express functions as a pipeline. Each function receives req, res, and next. The critical behavior you must test is twofold:
- Sequencing: Ensuring
next()is called to pass control to the subsequent middleware. - Short-circuiting: Ensuring the chain stops immediately if a response is sent (e.g.,
res.json()) or an error is passed (e.g.,next(err)).
Verifying the Chain
When testing these, you aren't just testing the logic inside one function; you are testing the transition between functions. Using a test double (like sinon.spy) for the next function is essential here.
Consider a simple authentication chain:
const authMiddleware = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).send('Unauthorized'); // Short-circuit
}
next(); // Sequencing
};
// Test implementation
it('should halt the chain if no authorization is provided', () => {
const req = { headers: {} };
const res = { status: sinon.stub().returnsThis(), send: sinon.spy() };
const next = sinon.spy();
authMiddleware(req, res, next);
// Assert short-circuiting
expect(res.status.calledWith(401)).to.be.true;
expect(next.called).to.be.false;
});
Why This Matters
If a middleware accidentally calls next() after sending a response, Express may attempt to write headers twice, leading to the dreaded ERR_HTTP_HEADERS_SENT crash. Furthermore, if you have a logging or auditing middleware placed after a security check, you must verify that a unauthorized request is blocked before it ever reaches the sensitive auditing layer.
When writing these tests, always spy on next and assert its status. If your logic dictates a successful flow, next must be called exactly once. If your logic dictates a rejection, next must not be called at all, and your response method (like send or json) must have been invoked.
Key Takeaway: To ensure middleware stability, always assert that next() is either invoked to progress the chain or strictly ignored when a response terminates the request cycle.