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