如何在验收测试中模拟Ember-CLI服务?

Cra*_*den 31 ember.js ember-cli

快速摘要/ tldr:

  • 似乎Ember的容器查找过程+ Ember-CLI的模块解析器不允许手动取消注册服务然后注册替换,如果原始服务可以使用resolver(我想要执行此处描述的方法,但它没有不行
  • 如何在不使用hacky自定义解析器的情况下在验收测试中模拟Ember-CLI服务?(这里的示例项目/验收测试)

详细解释+例子

创建一个注入控制器的新服务:

ember generate service logger
Run Code Online (Sandbox Code Playgroud)

服务/ logger.js

export default Ember.Object.extend({
  log: function(message){
    console.log(message);
  }
});
Run Code Online (Sandbox Code Playgroud)

初始化/记录器-service.js

export function initialize(container, application) {
  application.inject('route', 'loggerService', 'service:logger');
  application.inject('controller', 'loggerService', 'service:logger');
}
Run Code Online (Sandbox Code Playgroud)

loggerService在应用程序控制器的动作处理程序中,通过其注入名称访问该服务:

在控制器中使用该服务

模板/ application.hbs

<button id='do-something-button' {{action 'doSomething'}}>Do Something</button>
Run Code Online (Sandbox Code Playgroud)

控制器/ application.hs

export default Ember.Controller.extend({
  actions: {
    doSomething: function(){
      // access the injected service
      this.loggerService.log('log something');
    }
  }
});
Run Code Online (Sandbox Code Playgroud)

尝试测试此行为是否正确发生

我创建了一个验收测试,检查按钮单击是否触发了服务.目的是模拟服务并确定是否在没有实际触发服务实现的情况下调用它 - 这避免了实际服务的副作用.

ember generate acceptance-test application
Run Code Online (Sandbox Code Playgroud)

测试/接受/应用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');
  });
});
Run Code Online (Sandbox Code Playgroud)

这是基于谈话测试Ember应用程序:通过mixonic 管理依赖关系,建议取消注册现有服务,然后重新注册模拟版本:

application.__container__.unregister('service:logger');
application.register('service:logger', mockLogger, {instantiate: false});
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不适用于Ember-CLI.罪魁祸首是Ember容器中的这条线:

function resolve(container, normalizedName) {
  // ...
  var resolved = container.resolver(normalizedName) || container.registry[normalizedName];
  // ...
}
Run Code Online (Sandbox Code Playgroud)

这是容器查找链的一部分.问题是容器的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
});
Run Code Online (Sandbox Code Playgroud)

这似乎不应该是必要的,并且与上述幻灯片中建议的服务模拟不匹配.有没有更好的方法来模拟这项服务

这个问题中使用的ember-cli项目可以在github上的这个示例项目中找到.

Art*_*are 17

解决方案的简短版本:您注册的模拟服务必须具有不同的服务:名称而不是您尝试模拟的"真实"服务.

验收测试:

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(), '/');
  });
});
Run Code Online (Sandbox Code Playgroud)

集成测试(这是我最初的工作,这导致我的问题)

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);
});
Run Code Online (Sandbox Code Playgroud)

  • 使用Ember 2.1我遇到了错误:"this.container.register"不是函数.它似乎现在通过"this.container.registry.register"和"this.container.registry.injection"访问(注意"注册表"属性). (2认同)

kun*_*erd 7

您可以注册您的模拟并注入它而不是原始服务.

application.register('service:mockLogger', mockLogger, {
  instantiate: false
});

application.inject('route', 'loggerService', 'service:mockLogger');
application.inject('controller', 'loggerService', 'service:mockLogger');
Run Code Online (Sandbox Code Playgroud)

torii在第三方登录验收测试中使用这种方法来模拟库.我希望将来有一个更好的解决方案.