jasmine.createSpyObj con propiedades
Al burlarme de las dependencias en mis pruebas angulares, generalmente creo un objeto espía usando jasmine.createSpyObj:
const serviceSpy= jasmine.createSpyObj('MyService', ['method']);
luego proporcione a TestBed:
providers: [
{provide: MyService, useValue: serviceSpy}
]
Cuando lo uso en mi prueba, puedo especificar el valor de retorno deseado:
serviceSpy.method.and.returnValue(of([...]));
Ahora también necesito simular propiedades y no puedo averiguar cómo se debe hacer. createSpyObjpermite la definición de nombres de propiedad:
const serviceSpy= jasmine.createSpyObj('MyService', ['method'], ['property']);
pero he probado varias soluciones basadas en los numerosos artículos y respuestas que existen sin éxito, por ejemplo:
// Cannot read property 'and' of undefined
serviceSpy.property.and.returnValue(true);
// not declared configurable
spyOnProperty(serviceSpy, 'property').and.returnValue(true);
// no build errors, but value stays 'undefined'
serviceSpy.property = true;
La única forma en que podría hacer que funcione 'a medias' es:
let fakeValue = true;
const serviceSpy= jasmine.createSpyObj('MyService', ['method'], {'property': fakeValue});
El problema aquí es que se trata de un conjunto único en la creación. Si quiero cambiar el valor esperado en la prueba, no funciona.
fakeValue = false;
serviceSpy.property ==> stays to the initial value 'true';
¿Existe una solución tanto para los métodos simulados como para las propiedades mediante la creación de un objeto espía, o debería crear mi propia clase falsa en la que luego pueda usar spyOny spyOnProperty?
También me gustaría saber cuál es el uso de la matriz de propiedades en la createSpyObjdefinición. Hasta ahora no he visto ningún ejemplo en la web que lo explique.
Respuestas
Según la documentación (el énfasis es mío):
Puede crear un objeto espía con varias propiedades rápidamente pasando una matriz o hash de propiedades como tercer argumento a
createSpyObj. En este caso, no tendrá una referencia a los espías creados, por lo que si necesita cambiar sus estrategias de espionaje más adelante, tendrá que usar elObject.getOwnPropertyDescriptorenfoque .it("creates a spy object with properties", function() { let obj = createSpyObj("myObject", {}, { x: 3, y: 4 }); expect(obj.x).toEqual(3); Object.getOwnPropertyDescriptor(obj, "x").get.and.returnValue(7); expect(obj.x).toEqual(7); });
Las propiedades espiadas son descriptores (ver, por ejemplo, Object.definePropertyen MDN ), por lo que para acceder a los objetos espías necesita obtener el objeto descriptor y luego interactuar con los métodos gety setdefinidos en él.
En TypeScript, el compilador necesita un poco de ayuda. createSpyObjdevuelve anyo SpyObj<T>, y a SpyObjsolo define los métodos como espiados:
type SpyObj<T> = T & {
[K in keyof T]: T[K] extends Func ? T[K] & Spy<T[K]> : T[K];
// | if it's a | spy on it | otherwise leave
// | callable | | it alone
};
Entonces, para acceder .andal getter del descriptor, necesitará un encadenamiento opcional (como Object.getOwnPropertyDescriptorpuede devolver undefined) y una aserción de tipo a Spy:
(Object.getOwnPropertyDescriptor(obj, "x")?.get as Spy<() => number>).and.returnValue(7);
Patio de recreo
¡Muchas gracias @jonrsharpe! Acabo de agregar una función para poder hacer:
spyPropertyGetter(spy, 'propName').and.returnValue(...)
Que se define como:
function spyPropertyGetter(
spyObj: jasmine.SpyObj<any>,
propName: string
) {
return Object.getOwnPropertyDescriptor(spyObj, propName).get as jasmine.Spy<
jasmine.Func
>;
}