Подтвердить что ты не робот

Как насмехаться над сервисом Ember-CLI в приемочном тесте?

Краткая сводка /TL;DR:

  • Кажется, что процесс поиска контейнера Ember + модуль-преобразователь Ember-CLI не позволяет вручную отменить регистрацию службы, а затем зарегистрировать замену, если исходная услуга может быть разрешена с помощью resolver (я хочу сделать метод описанный здесь, но он не работает)
  • Как я могу издеваться над сервисом Ember-CLI в приемочном тесте без использования хакерского, настраиваемого разрешения? (пример проекта/приемочный тест здесь)

Подробное объяснение + пример

Создайте новую службу, которая вводится в контроллер:

ember generate service logger

услуги/logger.js

export default Ember.Object.extend({
  log: function(message){
    console.log(message);
  }
});

инициализаторы/Регистратор-service.js

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

Доступ к службе осуществляется через его введенное имя loggerService в обработчике действий на контроллере приложения:

Использовать службу в контроллере

Шаблоны/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

тесты/принятие/приложение-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');
  });
});

Это основано на разговоре Тестирование Ember Apps: Управление зависимостью от 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, чтобы никогда не находить оригинальный сервисный модуль, который позволяет контейнеру использовать зарегистрированную вручную mock-службу.

Измените 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.

4b9b3361

Ответ 1

Краткая версия решения: ваша зарегистрированная макетная служба должна иметь другую службу: имя, чем "реальный" сервис, который вы пытаетесь высмеять.

Приемочный тест:

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);
});

Ответ 2

Вы можете зарегистрировать свой макет и ввести его вместо первоначальной службы.

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

application.inject('route', 'loggerService', 'service:mockLogger');
application.inject('controller', 'loggerService', 'service:mockLogger');

Я использую этот подход для издевательства библиотеки torii в моих сторонних приёмных тестах входа в систему. Надеюсь, в будущем будет более приятное решение.

Ответ 3

Существующие ответы работают хорошо, но есть способ, который позволяет избежать переименования службы и пропускает инъекцию.

См. 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#L13

Я представлю его здесь как обновление для тестового помощника, который я ранее имел здесь, поэтому это замена, но вы можете просто захотеть следовать за ссылками выше.

// 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);
}

Плюс выполнение шагов регистрации jslint и помощников из https://guides.emberjs.com/v2.5.0/testing/acceptance/#toc_custom-test-helpers

Тогда я могу называть его так, в моем примере, который запустил службу перенаправления (window.location), которую мы хотим сделать, поскольку перенаправление перерывов Testem:

test("testing a redirect 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');
});