Prueba error caso con observables en servicios

Digamos que tengo un componente que se suscribe a una función de servicio:

export class Component { ... ngOnInit() { this.service.doStuff().subscribe( (data: IData) => { doThings(data); }, (error: Error) => console.error(error) ); }; }; 

La llamada de suscripción toma dos funciones anónimas como parámetros, he logrado configurar una prueba de unidad de trabajo para la función de datos, pero Karma no aceptará la cobertura para el error.

enter image description here

Intenté espiar a la función console.error, lanzando un error y luego esperando que se llamara al espía, pero eso no acaba de hacer.

Mi prueba de unidad:

 spyOn(console,'error').and.callThrough(); serviceStub = { doStuff: jasmine.createSpy('doStuff').and.returnValue(Observable.of(data)), }; serviceStub.doStuff.and.returnValue(Observable.throw( 'error!' )); serviceStub.doStuff().subscribe( (res) => { *working test, can access res* }, (error) => { console.error(error); console.log(error); //Prints 'error!' so throw works. expect(console.error).toHaveBeenCalledWith('error!'); //Is true but won't be accepted for coverage. } ); 

¿Cuál es la mejor práctica para probar funciones anónimas como estas? ¿Cuál es el mínimo indispensable para asegurar la cobertura de la prueba?

Simplemente puede simular el objeto Observable throw error como Observable.throw({status: 404}) y probar el bloque de error de observable.

 const xService = fixture.debugElement.injector.get(SomeService); const mockCall = spyOn(xService, 'method') .and.returnValue(Observable.throw({status: 404})); 

No estoy seguro exactamente del propósito del código que está mostrando, que está tratando de probar un servicio simulado. El problema de cobertura es con el componente y no se ha llamado a la callback de error (que solo se invoca cuando hay un error).

Lo que suelo hacer para la mayoría de mis servicios observables, es crear un simulacro cuyos métodos simplemente se devuelvan. El servicio simulado tiene un método de subscribe que acepta el next , error y devoluciones de llamada complete . El usuario del simulacro lo configura para agregar un error de modo que se llame a la función de error o agregue datos, de modo que se llama al next método. Lo que más me gusta de esto es que todo es sincrónico.

A continuación se muestra algo parecido a lo que normalmente uso. Es solo una clase abstracta para que se extiendan otros simulacros. Proporciona la funcionalidad básica que proporciona un observable. El servicio simulado extensible debería simplemente agregar los métodos que necesita, regresando a sí mismo en el método.

 import { Subscription } from 'rxjs/Subscription'; export abstract class AbstractMockObservableService { protected _subscription: Subscription; protected _fakeContent: any; protected _fakeError: any; set error(err) { this._fakeError = err; } set content(data) { this._fakeContent = data; } get subscription(): Subscription { return this._subscription; } subscribe(next: Function, error?: Function, complete?: Function): Subscription { this._subscription = new Subscription(); spyOn(this._subscription, 'unsubscribe'); if (next && this._fakeContent && !this._fakeError) { next(this._fakeContent); } if (error && this._fakeError) { error(this._fakeError); } if (complete) { complete(); } return this._subscription; } } 

Ahora en tus pruebas simplemente haces algo como

 class MockService extends AbstractMockObservableService { doStuff() { return this; } } let mockService; beforeEach(() => { mockService = new MockService(); TestBed.configureTestingModule({ providers: [{provide: SomeService, useValue: mockService }], declarations: [ TestComponent ] }); }); it('should call service success', () => { mockService.content = 'some content'; let fixture = TestBed.createComponent(TestComponent); // test component for success case }); it('should call service error', () => { mockService.error = 'Some error'; let fixture = TestBed.createComponent(TestComponent); // test component for error case // this should handle your coverage problem }); // this assumes you have unsubscribed from the subscription in your // component, which you should always do in the ngOnDestroy of the component it('should unsubscribe when component destroyed', () => { let fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); fixture.destroy(); expect(mockService.subscription.unsubscribe).toHaveBeenCalled(); })