首页 文章

如何对一个调用另一个返回promise的函数进行单元测试?

提问于
浏览
5

我有一个使用express 4的node.js应用程序,这是我的控制器:

var service = require('./category.service');

module.exports = {
  findAll: (request, response) => {
    service.findAll().then((categories) => {
      response.status(200).send(categories);
    }, (error) => {
      response.status(error.statusCode || 500).json(error);
    });
  }
};

它调用我的服务,它返回一个promise . 一切正常但我在尝试进行单元测试时遇到了麻烦 .

基本上,我想确保根据我的服务返回的内容,我使用正确的状态代码和正文刷新响应 .

因此,对于mocha和sinon,它看起来像:

it('Should call service to find all the categories', (done) => {
    // Arrange
    var expectedCategories = ['foo', 'bar'];

    var findAllStub = sandbox.stub(service, 'findAll');
    findAllStub.resolves(expectedCategories);

    var response = {
       status: () => { return response; },
       send: () => {}
    };
    sandbox.spy(response, 'status');
    sandbox.spy(response, 'send');

    // Act
    controller.findAll({}, response);

    // Assert
    expect(findAllStub.called).to.be.ok;
    expect(findAllStub.callCount).to.equal(1);
    expect(response.status).to.be.calledWith(200); // not working
    expect(response.send).to.be.called; // not working
    done();
});

当我测试的函数返回一个promise时,我已经测试了我的类似场景,因为我可以在当时挂钩我的断言 .

我也试图用一个Promise包装controller.findAll并从response.send解析它,但它既不起作用 .

3 回答

  • 1

    您应该将断言部分移动到 res.send 方法中,以确保在断言之前完成所有异步任务:

    var response = {
       status: () => { return response; },
       send: () => {
         try {
           // Assert
           expect(findAllStub.called).to.be.ok;
           expect(findAllStub.callCount).to.equal(1);
           expect(response.status).to.be.calledWith(200); // not working
           // expect(response.send).to.be.called; // not needed anymore
           done();
         } catch (err) {
           done(err);
         }
       },
    };
    
  • 6

    这里的想法是让 service.findAll() 返回的承诺在测试代码中可以访问,而不需要调用 service . 据我所知 sinon-as-promised 你可能使用它不允许这样做 . 所以我只使用了原生的 Promise (希望你的节点版本不是太老了) .

    const aPromise = Promise.resolve(expectedCategories); 
    var findAllStub = sandbox.stub(service, 'findAll');
    findAllStub.returns(aPromise);
    
    // response = { .... }
    
    controller.findAll({}, response);
    
    aPromise.then(() => {
        expect(response.status).to.be.calledWith(200);
        expect(response.send).to.be.called;    
    });
    
  • 1

    当代码难以测试时,它可以表明可以有不同的设计可能性来探索,这促进了简单的测试 . 跳出来的是 service 包含在您的模块中,并且依赖性根本不会公开 . 我觉得目标不应该是找到一种方法来测试你的代码,而是找到一个优化的设计 .

    IMO的目标是找到一种方法来公开 service ,以便您的测试可以提供存根实现,以便 findAll 的逻辑可以同步隔离测试 .

    一种方法是使用像 mockeryrewire 这样的库 . 两者都相当容易使用(根据我的经验,随着您的测试套件和模块数量的增长,嘲讽开始降级并变得非常难以维护)它们将允许您通过提供自己的服务对象来修补 var service = require('./category.service'); ,其自己的 findAll 已定义 .

    另一种方法是重新构建代码,以某种方式将 service 暴露给调用者 . 这将允许您的调用者(单元测试)提供自己的 service 存根 .

    一种简单的方法是导出函数contstructor而不是对象 .

    module.exports = (userService) => {
    
      // default to the required service
      this.service = userService || service;
    
      this.findAll = (request, response) => {
        this.service.findAll().then((categories) => {
          response.status(200).send(categories);
        }, (error) => {
          response.status(error.statusCode || 500).json(error);
        });
      }
    };
    
    var ServiceConstructor = require('yourmodule');
    var service = new ServiceConstructor();
    

    现在,测试可以为 service 创建存根,并将其提供给 ServiceConstructor 以执行 findAll 方法 . 完全不需要异步测试 .

相关问题