I'm testing a synchronous middleware stack that I derived from expressjs. For some reason my tests are passing even though the assertions should fail. I know from this question that the .subscribe calls are asynchronous in JEST, but I'm not using any subscribe
calls, only expect
. I believe that the issue has to do with the test finishing before all of the expect()
calls complete. But given the implementation, I'm not sure how that's possible.
test( "Stack proceeds to next error-handling layer if next(error) is explicitly called", () => {
const S = new Stack();
S.use( () => {
throw new Error( "FAIL" );
});
S.use( ( err, data, next ) => {
console.log( err.message) // Prints "FAIL"
expect( err.message ).toBe( "PASS" ); // Always passes no matter what
next( err )
});
let SKIP_TEST = "PASS";
S.use( ( data, next ) => {
SKIP_TEST = "FAIL";
});
let CALL_TEST = "FAIL";
S.use( ( err, data, next ) => {
CALL_TEST = "PASS";
// adding a console.log( err.message ) here causes this test to fail, which is the expected behavior. Without a console.log on this line, the test will always pass
expect( err.message ).toBe( "PASS" );
});
S.dispatch(); // Causes the above functions to be called synchronously, in the order they were defined
expect( SKIP_TEST ).toBe( "PASS" ); // Passes
expect( CALL_TEST ).toBe( "PASS" ); // Passes
});
I know that the functions passed to .use are being called because I can console.log from them and the output shows up in the console. To make things even more strange, adding a console.log call right before the expect in the final S.use function causes the assertion to (correctly) fail.
Here is the middleware stack that I derived from express.js
"use strict"
export default class Stack {
constructor(){
this.stack = [];
}
dispatch( data, done = ()=>{} ){
let idx = 0;
let stack = this.stack;
if( stack.length === 0 )
done();
next();
function next( err ){
let layer = stack[ idx++ ];
if( !layer )
return done( err );
if( err )
layer.handleError( err, data, next );
else
layer.handleNext( data, next );
}
}
use( fn ){
this.stack.push( new Layer( fn ) );
}
}
class Layer {
constructor( fn ){
// if( !this instanceof Layer )
// return new Layer( fn );
this.handle = fn;
this.name = fn.name || "<anonymous>";
}
handleError( err, data, next ){
let fn = this.handle;
// If this layer is not an error-handler, pass error to next layer
if( fn.length !== 3 )
return next( err ); // Stop the chain unless next() is explicitly called by the next layer in the stack
try{
fn( err, data, next );
} catch( err ){
next( err );
}
}
handleNext( data, next ){
let fn = this.handle;
// If this layer is not a standard handler, skip this layer
if( fn.length > 2 )
return next(); // Stop the chain unless next() is explicitly called by the next layer in the stack
try{
fn( data, next );
} catch( err ) {
next( err );
}
}
}
export{ Stack }