Πώς να δημιουργήσετε stub για το middleware του Express χρησιμοποιώντας sinon σε TypeScript;


Προσπαθώ να γράψω ένα τεστ ενσωμάτωσης για τον δρομολογητή μου στο express χρησιμοποιώντας TypeScript, Mocha, Sinon και Chai-http. Αυτός ο δρομολογητής χρησιμοποιεί ένα προσαρμοσμένο middleware που έγραψα και ελέγχει για JWT στην κεφαλίδα.

Ιδανικά, θέλω να αντικαταστήσω προσωρινά το authMiddleware μου έτσι ώστε να μπορώ να ελέγχω τη συμπεριφορά του χωρίς να παρέχω πραγματικό/μη έγκυρο JWT για κάθε σενάριο δοκιμής.

Όταν προσπαθώ να κάνω stub το authMiddleware στις δοκιμές μου, συνειδητοποίησα ότι η express app χρησιμοποιεί την πραγματική υλοποίηση του authMiddleware αντί για μια ψεύτικη.

Προσπάθησα να εισάγω το app αφού έκανα mock το authMiddleware χρησιμοποιώντας δυναμικές εισαγωγές του TypeScript, αλλά δεν λειτούργησε επίσης.

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.

Υπάρχει τρόπος να δημιουργήσεις ένα stub για το middleware του Express και να το περάσεις ρητά στην εφαρμογή;

Απάντηση #1

Μόλις εξήγησα τι συμβαίνει σε αυτή την απάντηση, παρόλα αυτά παρέχοντας μόνο προσωρινή λύση.

Μετά από λίγες σκέψεις, πιστεύω ότι ο καλύτερος τρόπος να ξεπεράσετε αυτό είναι να αφαιρέσετε την παγκόσμια κατάσταση από τα modules σας και να καταγράψετε όλο τον κώδικα αρχικοποίησης σε ρητά καλούμενες συναρτήσεις (ή κλάσεις, αν θέλετε), ώστε να μπορείτε να δημιουργείτε τον διακομιστή σας για κάθε δοκιμή. Συγκεκριμένα, αλλάξτε την αρχιτεκτονική σας σε:

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

Τώρα, μπορείτε να δημιουργήσετε νέα "εφαρμογή" σε κάθε 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
});

Αυτή η προσέγγιση έχει πολλά πλεονεκτήματα, μεταξύ άλλων:

  • μπορείτε να προσομοιώσετε διαφορετικές συναρτήσεις για διαφορετικά τεστ;
  • αφαιρείτε ουσιαστικά τα «ρητά μοναδικά αντικείμενα» από τον κώδικα των οποίων η ύπαρξη ήταν η ριζική αιτία του προβλήματός σας.