resolver
を使用して元のサービスを解決できる場合、サービスを手動で登録解除してから交換を登録できないようです(メソッドを実行したい ここで説明 ですが、機能しません)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');
});
});
これは、 Testing Ember Apps:Managing Dependency によるトークに基づいています。mixonicは、既存のサービスの登録を解除してから再登録することを推奨しています。モックバージョン:
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
メソッドが内部のresolver
をチェックする前にregistry
をチェックすることです。 application.register
コマンドは、モックされたサービスをコンテナーのregistry
に登録しますが、resolve
が呼び出されると、コンテナーはresolver
を照会してからregistry
を照会します。 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プロジェクトは、githubの このサンプルプロジェクトにあります。
ソリューションの短いバージョン:登録したモックサービスには、モックしようとしている「実際の」サービスとは異なるservice:nameが必要です。
受け入れテスト:
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'container-doubling/tests/helpers/start-app';
var application;
let speakerMock = Ember.Service.extend({
speak: function() {
console.log("Acceptance Mock!");
}
});
module('Acceptance | acceptance demo', {
beforeEach: function() {
application = startApp();
// the key here is that the registered service:name IS NOT the same as the real service you're trying to mock
// if you inject it as the same service:name, then the real one will take precedence and be loaded
application.register('service:mockSpeaker', speakerMock);
// this should look like your non-test injection, but with the service:name being that of the mock.
// this will make speakerService use your mock
application.inject('component', 'speakerService', 'service:mockSpeaker');
},
afterEach: function() {
Ember.run(application, 'destroy');
}
});
test('visit a route that will trigger usage of the mock service' , function(assert) {
visit('/');
andThen(function() {
assert.equal(currentURL(), '/');
});
});
統合テスト(これは私が最初に取り組んでいた問題でした)
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import Ember from 'ember';
let speakerMock = Ember.Service.extend({
speak: function() {
console.log("Mock one!");
}
});
moduleForComponent('component-one', 'Integration | Component | component one', {
integration: true,
beforeEach: function() {
// ember 1.13
this.container.register('service:mockspeaker', speakerMock);
this.container.injection('component', 'speakerService', 'service:mockspeaker');
// ember 2.1
//this.container.registry.register('service:mockspeaker', speakerMock);
//this.container.registry.injection('component', 'speakerService', 'service:mockspeaker');
}
});
test('it renders', function(assert) {
assert.expect(1);
this.render(hbs`{{component-one}}`);
assert.ok(true);
});
モックを登録して、元のサービスの代わりにインモックすることができます。
application.register('service:mockLogger', mockLogger, {
instantiate: false
});
application.inject('route', 'loggerService', 'service:mockLogger');
application.inject('controller', 'loggerService', 'service:mockLogger');
私はこのアプローチを使用して、サードパーティのログイン受け入れテストでtorii
ライブラリをモックしています。将来的にはもっと良い解決策があることを願っています。
既存の回答はうまく機能しますが、サービスの名前を変更せずにインジェクトをスキップする方法があります。
https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/helpers/module-for-acceptance.js#L14 および https: //github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/acceptance/keyboard-shortcuts-test.js#L1
これを、以前ここにあったテストヘルパーの更新としてここに提示します。これはドロップインの置き換えですが、代わりに上記のリンクをたどることができます。
// tests/helpers/override-service.js
// Override a service with a mock/stub service.
// Based on https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/helpers/module-for-acceptance.js#L14
// e.g. used at https://github.com/ember-weekend/ember-weekend/blob/fb4a02/tests/acceptance/keyboard-shortcuts-test.js#L13
//
// Parameters:
// - newService is the mock object / service stub that will be injected
// - serviceName is the object property being replaced,
// e.g. if you set 'redirector' on a controller you would access it with
// this.get('redirector')
function(app, newService, serviceName) {
const instance = app.__deprecatedInstance__;
const registry = instance.register ? instance : instance.registry;
return registry.register(`service:${serviceName}`, newService);
}
さらに https://guides.emberjs.com/v2.5.0/testing/acceptance/#toc_custom-test-helpers からjslintとヘルパーの登録手順を実行します
次に、この例のように、リダイレクト(window.location)サービスをスタブアウトするように呼び出します。リダイレクトすると、Testemが壊れるためです。
test("testing a redirect's path", function(assert) {
const assertRedirectPerformed = assert.async();
const redirectorMock = Ember.Service.extend({
redirectTo(href) {
assert.equal(href, '/neverwhere');
assertRedirectPerformed();
},
});
overrideService(redirectorMock, 'redirector');
visit('/foo');
click('#bar');
});