简体   繁体   中英

How to stub a function in a module?

I build express app, there is a route A use many middlewares:

// fblogin.js
const saveUser = require('./middlewares').saveUser;
const issueJWT = require('./middlewares').issueJWT;

const exchangeLongTimeToken = (a) => { //return promise to call API };
const retrieveUserInfo = (b) => { //return promise to call API };

const service = {
    exchangeLongTimeToken,
    retrieveUserInfo,
};

const asyncAll = (req, res) => { 
    // use Promise.all() to get service.exchangeLongTimeToken 
    // and service.retrieveUserInfo
};

router.post('/', [asyncAll, saveUser, issueJWT], (req, res) => {
    //some logic;
});

module.exports = { router, service };

And this is my middlewares.js :

const saveUser = (req, res, next) => { //save user };
const issueJWT = (req, res, next) => { //issue jwt };

module.exports = { saveUser, issueJWT };

It works well. But got problem when I tried to write the test. This is my test, I use mocha, chai, supertest and sinon:

const sinon = require('sinon');
const middlewares = require('../../../../src/routes/api/auth/shared/middlewares');
const testData = require('../../testdata/data.json');
let app = require('../../../../src/config/expressapp').setupApp();
const request = require('supertest');
let service = require('../../../../src/routes/api/auth/facebook/fblogin').service;


describe('I want to test', () => {
    context('Let me test', function () {
        const testReq = {name: 'verySad'};

        beforeEach(() => {
            sinon.stub(middlewares, 'saveUser').callsFake((req, res, next)=>{
                console.log(req);
            });


            sinon.stub(service, 'exchangeLongTimeToken').callsFake((url) => {
                return Promise.resolve(testData.fbLongTimeToken);
            });

            sinon.stub(service, 'retrieveUserInfo').callsFake((url) => {
                return Promise.resolve(testData.fbUserInfo);
            });

        });


        it('Should return 400 when bad signedRequest', () => {

            return request(app).post(facebookAPI).send(testReq).then((response) => {
                response.status.should.be.equal(400);
            });
        });
});

What is the problem
You could see that there are 3 stubs, 1 for middlewares.saveUser and 2 for services.XXXX which is in the same file of the route.

The problem is that, the 2 stubs works while the 1 for middlewares.saveUser not work, always trigger the original one.

I think it maybe that when I call the setupApp() , the express will load all the routers it needs, so mock it afterwards won't have a effect, but it is strange that route.service could be mocked...

How to get the stub work?
The only way to get it work, is to put the stub at the top of test file, just after that middleware require.

I tried:
1. Use 3rd party modules like proxyquire , rewire
2. Use node's own delete require.cache[middlewares] and 'app' and re-require them.
3. Many other tricks.
4. Use jest's mock, but still only works if I put it at the top of the file.

What is the way of solving this problem without putting the stub at the top of the test file? Thanks!

The solution in the question is a bit restricted, since the mock has polluted the whole test suites.

I end up by doing this, The logic is simple, we still need to mock the saveUser first, but then we require all the other variables into the test function rather than require them at the top of the file, more flexible this time. And I add a checkIfTheStubWorks method to check if the stub works, to make sure the whole test works.

const middlewares = require('../../../../src/routes/api/auth/shared/middlewares');
const removeEmptyProperty = require('../../../../src/utils/utils').removeEmptyProperty;

let app;
let service;
let request;

/*
 * The reason we need this is:
 * If the mock not works,
 * the whole test is meaningless
 */
const checkIfTheStubWorks = () => {
    expect(spy1).toHaveBeenCalled();
    expect(spy2).toHaveBeenCalled();
    expect(spy3).toHaveBeenCalled();
};

const loadAllModules = () => {
    service = require('../../../../src/routes/api/auth/facebook/fblogin').service;
    app = require('../../../../src/config/expressapp').setupApp();
    request = require('supertest')(app);
};

describe('Mock response from facebook', () => {
    let spy1 = {};
    let spy2 = {};
    let spy3 = {};
    const testReq = testData.fbShortToken;

beforeAll(() => {
    spy1 = jest.spyOn(middlewares, 'saveUser').mockImplementation((req, res, next) => {
        userToSaveOrUpdate = removeEmptyProperty(res.locals.user);
        next();
    });

    // It must be load here, in this order,
    // otherwise, the above mock won't work!
    loadAllModules();

    spy2 = jest.spyOn(service, 'exchangeLongTimeToken').mockImplementation((url) => {
        // mock it
    });

    spy3 = jest.spyOn(service, 'retrieveUserInfo').mockImplementation((url) => {
        // mock it
    });
});

afterAll(() => {
    spy1.mockRestore();
    spy2.mockRestore();
    spy3.mockRestore();
});

test('Return a JWT should have same length as facebook one', async () => {
    const response = await request.post(facebookAPI).send(testReq);
    // assert this, assert that 
    checkIfTheStubWorks();
});
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM