【问题标题】:How to stub express middleware using sinon in typescript?如何在打字稿中使用 sinon 存根表达中间件?
【发布时间】:2020-10-17 15:00:38
【问题描述】:

我正在尝试使用 typescript、mocha、sinon 和 chai-http 为我的 express 路由器编写一个集成测试。该路由器使用我编写的自定义中间件,用于检查标头中的 JWT。

理想情况下,我想存根我的authMiddleware,这样我就可以控制它的行为,而无需为每个测试用例实际提供有效/无效的 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();
            });
    });
});

当我运行上述测试时,我看到模拟 authMiddleware 没有被调用。测试中的app 使用了authMiddleware 对象的真实实现。

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

【问题讨论】:

    标签: typescript express mocha.js sinon chai-http


    【解决方案1】:

    我刚刚解释了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
    });
    

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

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

    【讨论】:

    • 对于这个问题是否有任何不涉及对正在测试的代码进行重大更改的新解决方案?
    猜你喜欢
    • 2021-10-16
    • 2017-07-12
    • 1970-01-01
    • 1970-01-01
    • 2019-02-05
    • 2023-03-05
    • 2019-11-15
    • 2021-06-29
    • 1970-01-01
    相关资源
    最近更新 更多