【发布时间】:2015-04-09 22:28:40
【问题描述】:
快速总结/tldr:
- 似乎Ember的容器查找过程+ Ember-CLI的模块解析器不允许手动取消注册服务然后注册替换如果原始服务可以使用
resolver解决(我想做的方法described here,但它不起作用) - 如何在验收测试中模拟 Ember-CLI 服务而不使用 hacky 的自定义解析器? (example project/acceptance test here)
详解+示例
创建一个注入控制器的新服务:
ember generate service logger
services/logger.js
export default Ember.Object.extend({
log: function(message){
console.log(message);
}
});
initializers/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>
controllers/application.hs
export default Ember.Controller.extend({
actions: {
doSomething: function(){
// access the injected service
this.loggerService.log('log something');
}
}
});
尝试测试此行为是否正确发生
我创建了一个验收测试来检查按钮单击是否触发了服务。目的是模拟服务并确定它是否在没有实际触发服务实现的情况下被调用——这避免了真实服务的副作用。
ember generate acceptance-test application
tests/acceptance/application-test.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 容器中的 this line:
function resolve(container, normalizedName) {
// ...
var resolved = container.resolver(normalizedName) || container.registry[normalizedName];
// ...
}
这是容器查找链的一部分。问题是容器的resolve 方法在检查其内部registry 之前检查resolver。 application.register 命令使用容器的registry 注册我们的模拟服务,但是当调用resolve 时,容器在查询registry 之前会先检查resolver。 Ember-CLI 使用自定义的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项目见this example project on github.
【问题讨论】:
-
您有解决方法吗?如果是,请分享。谢谢。
-
这显然是一个已知问题。 Stefan Penner 在他的一个项目 (github.com/stefanpenner/ember-jobs/commit/…) 中创建了一些辅助方法,并且有一些初步工作可以将它们直接集成到 Ember-Cli 中,但这似乎还没有完成:github.com/ember-cli/ember-cli/pull/3306
-
知道这项工作的状态吗?这将非常有帮助。