【问题标题】:Cannot Stub Function Returning Promise无法返回 Promise 的存根函数
【发布时间】:2018-09-02 11:13:36
【问题描述】:

我试图存根箭头函数removeUserEntry,但在测试中执行acctRmRouter 时,我的存根似乎被忽略了。我必须明确地存根UserModeldeleteOne方法才能成功进行测试,我想知道为什么会发生无知,谢谢

acctRoute.js

const removeUserEntry = (username) => {
    const condition = {username: username};

    return UserModel.deleteOne(condition)
       .then((res) => {
           if (res.n < 1) {
               throw new Error('User not exists');
           }
           return true;
       }, (err) => {throw err})
       .catch(err => err);
};

const acctRmRouter = function(httpReq, httpRes, next) {
    if (!argValidate(httpReq.body, 'string')) {
        httpRes.locals = {api: { success: false }};
        // return to avoid running downwards
        return next(new Error('bad argument'));
    }

    // perform DB rm user
    return removeUserEntry(httpReq.body).then((res) => {
        if (res === true) {
            httpRes.locals = {api: { success: true }};
            next();
        } else {
            httpRes.locals = {api: { success: false }}
            next(res);
        }
    });
};

acctRoute.spec.js

it('should remove user handler pass success request', async () => {
    shouldDbReset = false;
    const mockRequestURL = "/api/account/rm-user";
    const mockRequest = httpMocks.createRequest({
        method: "POST",
        url: mockRequestURL,
        headers: {
            "Content-Type": "text/plain"
        },
        body: 'validRmUser',
    });
    const mockResponse = httpMocks.createResponse();
    const spyNext = sinon.spy();
    const stubRemoveUserEntry = sinon.stub(accountRouterHelper, 'removeUserEntry'); 

    stubRemoveUserEntry.callsFake(function(){
        return Promise.resolve(true);
    }); // Expecting this function to be stubbed, and always return true

    await accountRouterHelper.acctRmRouter(mockRequest, mockResponse, spyNext); 
    /* But when running the function, it returns error object with "User not exists" 
    which is not what intended */

    const firstCallArgs = spyNext.getCall(0).args[0];

    expect(spyNext.called).to.be.true;
    console.log(`firstCallArgs: ${firstCallArgs}`)
    expect(firstCallArgs instanceof Error).to.be.false;
    expect(spyNext.args[0].length).to.equal(0);
    expect(mockResponse.statusCode).to.equal(200);
    expect(mockResponse.locals.api.success).to.be.true;

    stubRemoveUserEntry.resetHistory();
    stubRemoveUserEntry.restore();
});

以下确实成功地使用与removeUserEntry 类似的模式存根。

acctRoute.js

const createUserEntry = (userData) => {
    const updatedUserData = filterInput(userData);
    const userDoc = new UserModel(updatedUserData);
    return userDoc.save()
    .then((userObj) => userObj._doc
    ,(err) => { throw err;})
    .catch(err => err);
};

const acctCreateRouter = function (httpReq, httpRes, next) {
// do something in mongodb
return createUserEntry(userCondition)
   .then((response) => {
            if (!(response instanceof Error)) {
                httpRes.locals = {api: { success: true}};
                next();
            } else {
                httpRes.locals = {api: { success: false}};
                next(response);
            }
        }, (err) => {
            httpRes.locals = {api: { success: false}};
            next(err);
        })
        .catch((err) => {
            httpRes.locals = {api: { success: false}};
            next(err);
        });     
};

const acctOutputRouter = function(req, res, next) {
    if (res.locals) {
        res.send(res.locals.api);
    } else {next()}
};

acctRoute.spec.js

it("should return and save the success result to response locals for next route", () => {
        shouldDbReset = false;
        const mockResponse = httpMocks.createResponse();
        const stubCreateUserEntry = sinon.stub(accountRouterHelper, 'createUserEntry');
        const mockNext = sinon.spy();

        stubCreateUserEntry.callsFake(function(){
            return Promise.resolve();
        }); // Unlike removeUserEntry, stubbing neatly with desired output
        return accountRouterHelper.acctCreateRouter(mockRequest, mockResponse, mockNext)
        .then(() => {
            expect(mockNext.called).to.be.true;
            expect(mockResponse.locals.api.success).to.be.true;
        })
        .finally(() => {
            mockNext.resetHistory();
            stubCreateUserEntry.restore();
        });
    });

【问题讨论】:

    标签: javascript promise sinon stub


    【解决方案1】:

    问题

    sinon.stub(accountRouterHelper, 'removeUserEntry') 替换模块导出。

    acctRmRouter() 不是调用模块导出,而是直接调用removeUserEntry(),所以存根模块导出什么都不做。

    解决方案

    重构 acctRmRouter() 以调用 removeUserEntry() 的模块导出。

    ES6

    // import module into itself
    import * as self from './acctRoute';
    
    ...
    
    const acctRmRouter = function(httpReq, httpRes, next) {
    
        ...
    
        // call the function using the module
        return self.removeUserEntry(httpReq.body).then((res) => {
    
        ...
    

    Node.js 模块

    ...
    
    const acctRmRouter = function(httpReq, httpRes, next) {
    
      ...
    
      // call the function using module.exports
      return module.exports.removeUserEntry(httpReq.body).then((res) => {
    
      ...
    

    【讨论】:

    • 感谢您的回复,很抱歉没有表现得足够好,实际上还有另一个类似的模式createUserEntry函数可以巧妙地存根(参考更新的代码),我想知道为什么@ 987654329@ 不在同一模式下工作
    • @user2361494 你能贴出acctOutputRouter() 的代码吗?我需要先看看,然后才能回复
    • accountRouterHelper.acctOutputRouter() 似乎没有调用createUserEntry(),因此该测试中从未使用过存根。如果存根被完全删除,测试应该仍然通过
    • 我的道歉...愚蠢,我提供了另一个不相关的测试,我已经更新了createUserEntry的正确测试用例
    • 正如你所说的Brian,直接调用removeUserEntry是存根失败的原因,使用this.removeUserEntry解决了存根问题,因为现在this成为调用对象,然后可以被成功存根
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-03
    • 2021-01-26
    • 1970-01-01
    • 2017-01-02
    • 1970-01-01
    • 2020-05-12
    • 2019-12-23
    相关资源
    最近更新 更多