Javascript 如何在模块中存根函数?

Javascript 如何在模块中存根函数?,javascript,testing,mocking,mocha.js,sinon,Javascript,Testing,Mocking,Mocha.js,Sinon,我创建express应用程序时,有一条路线可以使用许多中间件: // fblogin.js const saveUser = require('./middlewares').saveUser; const issueJWT = require('./middlewares').issueJWT; const exchangeLongTimeToken = (a) => { //return promise to call API }; const retrieveUserInfo =

我创建express应用程序时,有一条路线可以使用许多中间件:

// 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 };
这是我的
middleware.js

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

module.exports = { saveUser, issueJWT };
它工作得很好。但是当我试着写测试时遇到了问题。 这是我的测试,我使用摩卡、柴、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个用于
middleware.saveUser
,2个用于
services.XXXX
,它们位于路由的同一文件中

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

我想,当我调用
setupApp()
时,express可能会加载它所需的所有路由器,因此以后模拟它不会有效果,但它 奇怪的是,
route.service
可以被模仿

如何获得存根工作?
让它工作的唯一方法是将存根放在测试文件的顶部,就在
中间件
要求之后

我试过:
1.使用第三方模块,如
proxyquire
rewire

2.使用节点自己的
delete require.cache[middleware]
和“app”并重新请求它们。
3.还有许多其他技巧。
4.使用jest的mock,但仅当我将其放在文件顶部时,它仍然有效


在不将存根放在测试文件顶部的情况下,解决此问题的方法是什么?谢谢

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

最后,我这样做了,逻辑很简单,我们仍然需要先模拟
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();
});
});