How to setup Mocha tests with Node.js and Passport

2019-03-31 05:38发布

In an application built with Node.js (CompoundJS + PassportJS), how can the Mocha tests be run on the controllers that are locked down and require login? I have tried using Superagent but am not having much luck and to use it the server needs to be running to run the tests. I've gotten very close with this method, but don't want to have to have the server running to run unit tests.

I also tried including passport and using request.login method, I ended up at a point where I kept getting the error passport.initialize() middleware not in use.

I am trying to stick with the generated CompoundJS tests which were working great until the authentication got involved. The default CompoundJS tests run an init.js file which would be nice to handle the authentication in and somehow make available to each controller test:

require('should');
global.getApp = function(done) {
    var app = require('compound').createServer()
    app.renderedViews = [];
    app.flashedMessages = {};

    // Monkeypatch app#render so that it exposes the rendered view files
    app._render = app.render;
    app.render = function(viewName, opts, fn) {
        app.renderedViews.push(viewName);

        // Deep-copy flash messages
        var flashes = opts.request.session.flash;
        for (var type in flashes) {
            app.flashedMessages[type] = [];
            for (var i in flashes[type]) {
                app.flashedMessages[type].push(flashes[type][i]);
            }
        }

        return app._render.apply(this, arguments);
    }

    // Check whether a view has been rendered
    app.didRender = function(viewRegex) {
        var didRender = false;
        app.renderedViews.forEach(function(renderedView) {
            if (renderedView.match(viewRegex)) {
                didRender = true;
            }
        });
        return didRender;
    }

    // Check whether a flash has been called
    app.didFlash = function(type) {
        return !!(app.flashedMessages[type]);
    }

    return app;
};

controllers/users_controller_test.js

var app,
    compound,
    request = require('supertest'),
    sinon = require('sinon');

/** 
 * TODO: User CREATION and EDITs should be tested, with PASSPORT
 * functionality.
 */

function UserStub() {
    return {
        displayName: '',
        email: ''
    };
}

describe('UserController', function() {
    beforeEach(function(done) {
        app = getApp();
        compound = app.compound;
        compound.on('ready', function() {
            done();
        });
    });

    /**
     * GET /users
     * Should render users/index.ejs
     */
    it('should render "index" template on GET /users', function(done) {
        request(app)
            .get('/users')
            .end(function(err, res) {
                res.statusCode.should.equal(200);
                app.didRender(/users\/index\.ejs$/i).should.be.true;
                done();
            });
    });

    /*
     * GET /users/:id
     * Should render users/index.ejs
     */
    it('should access User#find and render "show" template on GET /users/:id',
        function(done) {
            var User = app.models.User;

            // Mock User#find
            User.find = sinon.spy(function(id, callback) {
                callback(null, new User);
            });

            request(app)
                .get('/users/42')
                .end(function(err, res) {
                    res.statusCode.should.equal(200);
                    User.find.calledWith('42').should.be.true;
                    app.didRender(/users\/show\.ejs$/i).should.be.true;

                    done();
                });
        });
});

These all fail with either AssertionError: expected 403 to equal 200 or AssertionError: expected false to be true

1条回答
可以哭但决不认输i
2楼-- · 2019-03-31 06:20

I used a combination of mocking passport.initialize, a test helper, and a listener on the compound configure event.

This provided two things:

  1. DRY - reuse of beforeEach code across controller tests.
  2. Unobtrusive testing - mock passport.initialize so I didn't have to modify configuration based on testing.

In test/init.js I added the method to mock passport.initialize**:

** Found it at:

http://hackerpreneurialism.com/post/48344246498/node-js-testing-mocking-authenticated-passport-js

// Fake user login with passport.
app.mockPassportInitialize = function () {
    var passport = require('passport');
    passport.initialize = function () {
        return function (req, res, next) {
            passport = this;
            passport._key = 'passport';
            passport._userProperty = 'user';
            passport.serializeUser = function(user, done) {
                return done(null, user.id);
            };
            passport.deserializeUser = function(user, done) {
                return done(null, user);
            };
            req._passport = {
                instance: passport
            };
            req._passport.session = {
                user: new app.models.User({ id: 1, name: 'Joe Rogan' })
            };

            return next();
        };
    };
};

Then I added a helpers.js file to be called in each controller:

module.exports = {
    prepApp: function (done) {
        var app = getApp();
        compound = app.compound;
        compound.on('configure', function () { app.mockPassportInitialize(); });
        compound.on('ready', function () { done(); });
        return app;
    }
};

This will be called in the beforeEach of each controller:

describe('UserController', function () {
    beforeEach(function (done) {
        app = require('../helpers.js').prepApp(done);
    });
    [...]
});
查看更多
登录 后发表回答