问题
I'm trying to write an integration test for my express router using typescript, mocha, sinon and chai-http. This router uses a custom middleware that I wrote which checks for JWT in the header.
Ideally, I want to stub my authMiddleware
so that I can control its behaviour without actually providing valid/invalid JWT for every test case.
When I try to stub authMiddleware
in my tests, I realised that express app
uses the actual implementation of authMiddleware
rather than mocked one.
I've tried to import app
after mocking authMiddleware
using dynamic imports of typescript but it didn't work also.
authMiddleware.ts
import { Request, Response, NextFunction } from 'express';
export default class AuthMiddleware {
verifyToken(req: Request, res: Response, next: NextFunction) :void {
console.log('Actual implemetation of verifyToken is called!');
// verify token
next();
}
}
subjectRouter.ts
import express from'express';
import AuthMiddleware from '../middleware/authMiddleware';
import * as subjectController from '../controller/subjectController';
const router = express.Router();
const authMiddleware = new AuthMiddleware();
router.post('/', authMiddleware.verifyToken, subjectController.createSubject);
export default router;
app.ts
import express from 'express';
import subjectRoute from './route/subjectRoute';
// Initilize express app
const app = express();
app.set("port", 3000);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// Routers
app.use('/user', userRoute);
app.use('/subject', subjectRoute);
export default app;
subjectTests.ts
import app from '../../src/app';
import AuthMiddleware from '../../../src/middleware/AuthMiddleware';
describe('Subject', () => {
let app;
beforeEach(async () => {
sinon.stub(AuthMiddleware.prototype, 'verifyToken').callsFake((req: Request, res: Response, next: NextFunction): void => {
console.log('Fake verifyToken is called!');
// THIS IS NEVER CALLED IN TESTS...
});
app = (await import('../../../src/app')).default;
});
it('should throw 403 when jwt is missing in header', (done) => {
request(app)
.post(/subject)
.end((err, res) => {
expect(res).has.status(403);
done();
});
});
});
When I run the above test I see that mock authMiddleware
is not called. app
in tests uses the real implementation of authMiddleware
object.
Is there a way to stub express middleware and pass it to app explicitly?
回答1:
I've just explained what is happening in this response, however giving only workaround.
After few thoughts, i believe the best way to overcome this is to remove global state from your modules and capture whole initialization code into explicitly called functions (or classes, if you like), so you can create your server for each tests. Namely, change your architecture to:
// router.ts
export function createRouter() {
/// ...
router.post('/', authMiddleware.verifyToken, subjectController.createSubject);
return router;
}
// app.ts
import { createRouter} from "./router.js"
export function createApp() {
///same code as currently, but in function
app.use('/subject', createRouter());
}
Now, you can create new "app" in each setup callback:
// test.ts
beforeEach(async () => {
sinon.stub(AuthMiddleware.prototype, 'verifyToken').callsFake((req: Request, res: Response, next: NextFunction): void => {
...
});
app = (await import('../../../src/app')).createApp(); // note that we create new app for each test, so you're not polutting global state of app
});
This approach has many advantages, among others:
- you can mock different functions for different tests;
- you're effectively removing "implicit singletons" from code whose existence was root cause of your issue.
来源:https://stackoverflow.com/questions/62604398/how-to-stub-express-middleware-using-sinon-in-typescript