想象一下,我有一个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存储对象函数中使用的类函数。如果我不这样做,我不想再提出额外的论点。
答案 0 :(得分:1)
首先:您的测试代码没有任何问题。它有效!
我看着它,并认为它看起来是正确的,所以我重新创建它(删除拼写和语法错误)here。测试通过了飞行的颜色。这意味着你所做的事与你所假设的不同。
在该回购中,我还添加了支持代码,以演示下面提到的各种方法。
Controller
的依赖关系的好方法是什么?你会发现关于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的土地上你需要"需要"像proxyquire
或rewire
这样的支持库使用链接接缝,因此您使用setter进行手动依赖注入是很自然的。虽然使用链接接缝让您避免更改生产代码,但它还有一个缺点,即假设您对模块依赖性有深入的了解。 DI至少让你保持在界面层面。
"肮脏的感觉"你在自己的答案中提到的可能是两件事:
Service
Service
未被使用)。依赖注入主要有两种形式:构造函数注入(我在此定义中包含工厂方法)或setter注入。您可以看到here。
构造函数注入在这里不能正常工作,因为您正在使用在模块定义时创建的Singleton对象。使用setter(Service.js
)替换export __setService(MyStubClass){ Service = MyStubClass; }
模块本身并不会改变您正在更改模块的事实,如果您不这样做,可能会影响在此之后运行的测试# 39;正确清理(例如添加__reset
方法)。这基本上就是你在自己提供的答案中所做的。
您可以在link-seams
和dependency-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'
});
});
这感觉很脏!非常感谢更好的解决方案。