如何在打字稿中使用 sinon 存根表达中间件?

How to stub express middleware using sinon in typescript?

我正在尝试使用 typescript、mocha、sinon 和 chai-http 为我的 express 路由器编写集成测试。该路由器使用我编写的自定义中间件,它在 header.

中检查 JWT

理想情况下,我想存根 authMiddleware 这样我就可以控制它的行为,而无需为每个测试用例实际提供 valid/invalid JWT。

当我尝试在测试中存根 authMiddleware 时,我意识到 express app 使用 authMiddleware 的实际实现而不是模拟的实现。

我尝试在使用打字稿的动态导入模拟 authMiddleware 之后导入 app,但它也不起作用。

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();
            });
    });
});

当我 运行 上面的测试时,我发现 mock authMiddleware 没有被调用。 app 在测试中使用 authMiddleware object.

的真实实现

有没有办法存根 express 中间件并将其显式传递给应用程序?

我刚刚解释了 this response 中发生的事情,但只提供了解决方法。

经过深思熟虑,我认为解决这个问题的最佳方法是从模块中删除全局状态并将整个初始化代码捕获到显式调用的函数中(或 类,如果您愿意),这样您就可以为每个测试创建你的服务器。即,将您的体系结构更改为:

// 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());
}

现在,您可以在每个设置回调中创建新的“应用程序”:

// 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
});

这种方法有很多优点,其中包括:

  • 您可以为不同的测试模拟不同的函数;
  • 你有效地从代码中删除了“隐式单例” 存在是您问题的根本原因。