代码之家  ›  专栏  ›  技术社区  ›  jbernal

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

  •  5
  • jbernal  · 技术社区  · 10 年前

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

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

    它调用我的服务,并返回承诺。一切正常,但我在尝试单元测试时遇到了问题。

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

    因此,摩卡和西农看起来像:

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

    当我正在测试的函数返回自己的承诺时,我已经测试了类似的场景,因为我可以在then中挂接我的断言。

    3 回复  |  直到 10 年前
        1
  •  6
  •   Johannes Merz    10 年前

    您应该将断言部分移到 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);
         }
       },
    };
    
        2
  •  1
  •   Viktor Molokostov    10 年前

    这里的想法是承诺 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;    
    });
    
        3
  •  1
  •   dm03514    10 年前

    当代码很难测试时,它表明可能有不同的设计可能性需要探索,这有助于简化测试。突然出现的是 service 包含在模块中,并且完全不公开依赖项。我觉得目标不应该是找到按原样测试代码的方法,而是找到最佳设计。

    IMO目标是找到一种方法来暴露 服务 以便您的测试可以提供一个stubed实现,以便 findAll 可以同步隔离测试。

    一种方法是使用类似于 mockery 或 rewire 两者都很容易使用,(根据我的经验,随着测试套件和模块数量的增长,mockery开始降级并变得很难维护)它们将允许您修补 var service = require('./category.service'); 通过为自己的服务对象提供自己的 查找全部 定义

    服务 以某种方式对来电者说。这将允许您的调用者(单元测试)提供自己的 服务 树桩。

    一种简单的方法是导出函数构造函数而不是对象。

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

    现在测试可以为创建一个存根 服务 ServiceConstructor 查找全部 方法完全不需要异步测试。