tutorial started start quick guide getting emberjs ember docs data ember.js ember-cli

ember.js - started - ¿Cómo burlarse de un servicio Ember-CLI en una prueba de aceptación?



ember startup (3)

Resumen rápido / tldr:

  • Parece que el proceso de búsqueda de contenedores de Ember + resolución de módulos de Ember-CLI no permite anular el registro manual de un servicio y luego registrar un reemplazo si el servicio original se puede resolver usando el resolver (quiero hacer el método descrito aquí , pero no funciona)
  • ¿Cómo puedo simular un servicio Ember-CLI en una prueba de aceptación sin usar un resolutor personalizado y hacky? ( ejemplo de proyecto / prueba de aceptación aquí )

Explicación detallada + ejemplo

Cree un nuevo servicio que se inyecta en un controlador:

ember generate service logger

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

Se accede al servicio a través de su nombre inyectado, loggerService , en un controlador de acción en el controlador de la aplicación:

Usa el servicio en un controlador

templates / application.hbs

<button id=''do-something-button'' {{action ''doSomething''}}>Do Something</button>

controladores / application.hs

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

Intente probar que este comportamiento se produce correctamente

Creé una prueba de aceptación que verifica que el clic del botón activó el servicio. La intención es burlarse del servicio y determinar si se llamó sin activar realmente la implementación del servicio, lo que evita los efectos colaterales del servicio real.

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

Esto se basa en la charla Testing Ember Apps: Managing Dependency by mixonic que recomienda anular el registro del servicio existente y luego volver a registrar una versión simulada:

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

Desafortunadamente, esto no funciona con Ember-CLI. El culpable es esta línea en el contenedor de Ember:

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

que es parte de la cadena de búsqueda del contenedor. El problema es que el método de resolve del contenedor comprueba el resolver antes de verificar su registry interno. El comando application.register registra nuestro servicio simulado con el registry del contenedor, pero cuando se llama a resolve el contenedor verifica con el resolver antes de consultar el registry . Ember-CLI utiliza una resolver personalizada para hacer coincidir las búsquedas con los módulos, lo que significa que siempre resolverá el módulo original y no usará el servicio simulado recientemente registrado. La solución para esto se ve horrible e implica modificar el resolver para que nunca encuentre el módulo del servicio original, lo que permite que el contenedor use el servicio falso simulado manualmente.

Modificar el resolutor para evitar resolver el servicio original

El uso de una resolver personalizada en la prueba permite burlar con éxito el servicio. Esto funciona al permitir que el resolver realice búsquedas normales, pero cuando se busca el nombre de nuestro servicio, el resolvedor modificado actúa como si no tuviera un módulo que coincida con ese nombre. Esto hace que el método de resolve encuentre el servicio falso simulado manualmente en el contenedor.

var MockResolver = Resolver.extend({ resolveOther: function(parsedName) { if (parsedName.fullName === "service:logger") { return undefined; } else { return this._super(parsedName); } } }); application = startApp({ Resolver: MockResolver });

Parece que no debería ser necesario y no coincide con el servicio sugerido de las diapositivas anteriores. ¿Hay una mejor manera de burlarse de este servicio ?

El proyecto ember-cli usado en esta pregunta se encuentra en este proyecto de ejemplo en github.


Las respuestas existentes funcionan bien, pero hay una manera de evitar el cambio de nombre del servicio y omite la inyección.

Consulte https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/helpers/module-for-acceptance.js#L14 y uso en https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/acceptance/keyboard-shortcuts-test.js#L13

Lo presentaré aquí como una actualización del asistente de prueba que tenía anteriormente, por lo que es un reemplazo directo, pero quizás prefiera seguir los enlaces anteriores.

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

Además de realizar los pasos de registro jslint y helper desde https://guides.emberjs.com/v2.5.0/testing/acceptance/#toc_custom-test-helpers

Entonces puedo llamarlo así, en mi ejemplo anulando un servicio de redirección (window.location), que queremos hacer porque el redireccionamiento rompe 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''); });


Puede registrar su simulacro e inyectarlo en lugar del servicio original.

application.register(''service:mockLogger'', mockLogger, { instantiate: false }); application.inject(''route'', ''loggerService'', ''service:mockLogger''); application.inject(''controller'', ''loggerService'', ''service:mockLogger'');

Utilizo este enfoque para burlarme de la biblioteca torii en mis pruebas de aceptación de inicio de sesión de terceros. Espero que haya una mejor solución en el futuro.


Versión corta de la solución: su servicio simulado registrado debe tener un servicio diferente: nombre que el servicio "real" que está intentando simular.

Examen de ingreso:

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

Prueba de integración (esto es en lo que originalmente estaba trabajando que me causaba problemas)

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