快速总结/tldr:
- 看起来Ember的容器查找过程+Ember-CLI的模块解析器不允许手动取消注册一个服务,然后注册一个替换服务,如果可以使用
resolver
解析原始服务(我想要执行这里描述的方法,但不起作用) - 如何在接受测试中模拟一个Ember-CLI服务,而不使用hacky、自定义的解析器?(此处有示例项目/接受测试)
详细说明+示例
创建一个新的服务,并将其注入到控制器中:
ember generate service logger
服务/logger.js
export default Ember.Object.extend({
log: function(message){
console.log(message);
}
});
初始化程序/logger-service.js
export function initialize(container, application) {
application.inject('route', 'loggerService', 'service:logger');
application.inject('controller', 'loggerService', 'service:logger');
}
通过在应用程序控制器中的操作处理程序中使用其注入名称loggerService
访问该服务:
在控制器中使用该服务
templates/application.hbs
<button id='do-something-button' {{action 'doSomething'}}>Do Something</button>
控制器/application.hs
export default Ember.Controller.extend({
actions: {
doSomething: function(){
// access the injected service
this.loggerService.log('log something');
}
}
});
尝试测试该行为是否正确发生
我创建了一个验收测试,检查按钮单击是否触发了服务。目的是模拟服务并确定是否调用了服务而不实际触发服务的实现--这避免了真实服务的副作用。
ember generate acceptance-test application
测试/接受/应用测试.js
import Ember from 'ember';
import startApp from '../helpers/start-app';
var application;
var mockLoggerLogCalled;
module('Acceptance: Application', {
setup: function() {
application = startApp();
mockLoggerLogCalled = 0;
var mockLogger = Ember.Object.create({
log: function(m){
mockLoggerLogCalled = mockLoggerLogCalled + 1;
}
});
application.__container__.unregister('service:logger');
application.register('service:logger', mockLogger, {instantiate: false});
},
teardown: function() {
Ember.run(application, 'destroy');
}
});
test('application', function() {
visit('/');
click('#do-something-button');
andThen(function() {
equal(mockLoggerLogCalled, 1, 'log called once');
});
});
这是基于mixonic所提出的演讲Testing Ember Apps: Managing Dependency,其中推荐取消现有服务的注册,然后重新注册一个模拟版本:
application.__container__.unregister('service:logger');
application.register('service:logger', mockLogger, {instantiate: false});
不幸的是,这在Ember-CLI中不起作用。罪魁祸首是Ember容器中的这行代码:
function resolve(container, normalizedName) {
// ...
var resolved = container.resolver(normalizedName) || container.registry[normalizedName];
// ...
}
这是容器查找链的一部分。问题在于容器的resolve
方法在检查其内部的registry
之前会先检查resolver
。当使用resolve
调用容器时,容器首先查询resolver
,而不是内部的registry
。Ember-CLI使用自定义的resolver
来匹配模块,这意味着它始终会解析原始模块,而不使用新注册的模拟服务。解决此问题的方法看起来很糟糕,涉及修改resolver
以永远找不到原始服务的模块,这允许容器使用手动注册的模拟服务。
修改Resolver以避免解析到原始服务
在测试中使用自定义的resolver
可以成功地对服务进行模拟。这通过允许解析器执行正常的查找来实现,但当查找我们的服务名称时,修改后的解析器会像没有匹配该名称的模块一样工作。这会导致resolve
方法在容器中找到手动注册的模拟服务。
var MockResolver = Resolver.extend({
resolveOther: function(parsedName) {
if (parsedName.fullName === "service:logger") {
return undefined;
} else {
return this._super(parsedName);
}
}
});
application = startApp({
Resolver: MockResolver
});
这似乎并不必要,也不符合上面幻灯片中建议的服务模拟。 有没有更好的方法来模拟这个服务?
在这个问题中使用的ember-cli项目可以在GitHub上的这个示例项目中找到。