【问题标题】:How to stub a function in a module?如何存根模块中的函数?
【发布时间】:2017-06-04 11:31:43
【问题描述】:

我构建快递应用,有一个路由A使用了很多中间件:

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

这是我的middlewares.js

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

module.exports = { saveUser, issueJWT };

效果很好。但是当我尝试编写测试时遇到了问题。 这是我的测试,我用mocha、chai、supertest和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);
            });
        });
});

有什么问题
您可以看到有 3 个存根,1 个用于 middlewares.saveUser,2 个用于 services.XXXX,它们位于路由的同一文件中。

问题是,2 个存根有效,而 middlewares.saveUser 的 1 个无效,总是触发原始存根。

我想可能是当我调用setupApp()时,快递会加载它需要的所有路由器,所以之后模拟它不会有效果,但是它 很奇怪route.service 可以被嘲笑...

如何让存根工作?
让它工作的唯一方法是将存根放在测试文件的顶部,紧随其后的是middlewarerequire。

我试过了:
1. 使用第 3 方模块,例如 proxyquirerewire
2. 使用节点自己的delete require.cache[middlewares] 和 'app' 并重新请求它们。
3. 许多其他技巧。
4. 使用 jest 的 mock,但仅在我将其放在文件顶部时仍然有效。

如果不将存根放在测试文件的顶部,有什么方法可以解决这个问题?谢谢!

【问题讨论】:

    标签: javascript testing mocking mocha.js sinon


    【解决方案1】:

    问题中的解决方案有点受限,因为模拟已经污染了整个测试套件。

    我最终这样做了,逻辑很简单,我们仍然需要先模拟saveUser,但是我们需要所有其他变量到测试函数中,而不是在文件顶部需要它们,更多这次灵活了。我添加了一个checkIfTheStubWorks 方法来检查存根是否工作,以确保整个测试工作正常。

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

    【讨论】:

      猜你喜欢
      • 2017-03-06
      • 2011-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-27
      • 2017-01-15
      • 2016-05-11
      • 2021-06-10
      相关资源
      最近更新 更多