stubing es6类函数依赖

时间:2018-02-14 12:41:17

标签: class ecmascript-6 prototype sinon stub

想象一下,我有一个Controller来验证一些输入,并使用一个服务(es6类)来执行业务逻辑的某些部分。

Controller.js (对象功能)

const Service = require('./Service');

module.exports = {
  get: id => {
    //validates id
    return new Service().get(id);
  }
}

Service.js (ES6类功能)

class Service {
  constructor() {...}
  get(id) { return 'actual function'; }
}

module.exports = Service;

我希望在服务中存根get函数来测试Controller。

Controller_test.js

const Controller = require('../src/Controller.js);
const Service = require('../src/Service.js);

describe('Controller', () => {
  let sandbox;

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
  });
  afterEach(() => {
    sandbox.restore();
  });

  it('should use fake get function', done => {
    sandbox.stub(Service.protoype, 'get').callsFake(() => {
      return 'Fake has been called');
    });

    const result = Controller.get(id);
    expect(reuslt).to.equal('Fake has been called'); //returns 'actual function'
  });
});

所以,重申一下。我无法使用sinon存储对象函数中使用的类函数。如果我不这样做,我不想再提出额外的论点。

2 个答案:

答案 0 :(得分:1)

您的测试代码是否正确

首先:您的测试代码没有任何问题。它有效!

我看着它,并认为它看起来是正确的,所以我重新创建它(删除拼写和语法错误)here。测试通过了飞行的颜色。这意味着你所做的事与你所假设的不同。

在该回购中,我还添加了支持代码,以演示下面提到的各种方法。

提出了三个问题:

  1. 控制Controller的依赖关系的好方法是什么?
  2. 如何测试导出的单件?
  3. 你应该如何对测试进行建模?
  4. 你会发现关于Sinon问题跟踪器的一些关于#1的好讨论,其中this可能会引起关注。

    如果您了解Java,您可能会将两个阵营视为控制反转(依赖注入(传入依赖关系)和 IOC容器(给我)某种类型' foo') - 除了Angular之外,在JS中使用的并不多。如果您熟悉Michael Feathers,那么您也有link seams,它可以在较低级别工作,以替换模块加载器提供的代码(require调用)。

    后者可以像这样替换Service实现:

    // myServiceStub is at this point set up by you using Sinon
    // use `sandbox.resetHistory()` to reset the stub between tests
    const Controller = proxyquire('../src/Controller', { './Service' : myServiceStub });
    // test that the stub is invoked as expected
    

    在JS的土地上你需要"需要"像proxyquirerewire这样的支持库使用链接接缝,因此您使用setter进行手动依赖注入是很自然的。虽然使用链接接缝让您避免更改生产代码,但它还有一个缺点,即假设您对模块依赖性有深入的了解。 DI至少让你保持在界面层面。

    "肮脏的感觉"你在自己的答案中提到的可能是两件事:

    • 您正在更改模块(全局状态),而不是Service
    • 创建的任何实例
    • 您正在公开有关模块的直接依赖关系的知识,这些依赖关系不可能在测试中使用直接关系(linter可能会抱怨Service未被使用)。

    依赖注入主要有两种形式:构造函数注入(我在此定义中包含工厂方法)或setter注入。您可以看到here

    构造函数注入在这里不能正常工作,因为您正在使用在模块定义时创建的Singleton对象。使用setter(Service.js)替换export __setService(MyStubClass){ Service = MyStubClass; }模块本身并不会改变您正在更改模块的事实,如果您不这样做,可能会影响在此之后运行的测试# 39;正确清理(例如添加__reset方法)。这基本上就是你在自己提供的答案中所做的。

    您可以在link-seamsdependency-injection分支的mentioned repository中找到两种方式的正常运行示例。

    就我个人而言,我不认为使用单元测试本身测试Controller会带来很多价值,我会尝试避免使用单例模块(使用任何语言)。我测试Service实现并进行集成测试(在HTTP层上),并将正在使用的服务替换为假控制响应。实际的Service似乎更有趣。

答案 1 :(得分:0)

可能的解决方案我发现:从控制器导出服务并通过控制器存根。

<强>控制器

const Service = require('./Service');

const Controller = {
  get: id => {
    //validates id
    return new Service().get(id);
  }
}

module.exports.default = Controller;
module.exports.Service = Service;

<强>测试

const Controller = require('../src/Controller.js);

describe('Controller', () => {
  let sandbox;

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
  });
  afterEach(() => {
    sandbox.restore();
  });

  it('should use fake get function', done => {
    sandbox.stub(Controller.Service.protoype, 'get').callsFake(() => {
      return 'Fake has been called');
    });

    const result = Controller.get(id);
    expect(reuslt).to.equal('Fake has been called'); //returns 'actual function'
  });
});

这感觉很脏!非常感谢更好的解决方案。