Test di casi di errore con osservabili nei servizi

Diciamo che ho un componente che si abbona a una funzione di servizio:

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

La chiamata subscribe accetta due funzioni anonime come parametri, sono riuscito a impostare un test dell’unità di lavoro per la funzione dati, ma Karma non accetterà la copertura per l’errore.

inserisci la descrizione dell'immagine qui

Ho provato a spiare la funzione console.error, a lanciare un errore e poi mi aspettavo che la spia fosse stata chiamata, ma questo non lo fa.

Il mio test di unità:

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

Qual è la migliore pratica per testare funzioni anonime come queste? Qual è il minimo indispensabile per garantire la copertura del test?

Puoi semplicemente prendere in giro l’object di errore di tiro Observable come Observable.throw({status: 404}) e testare il blocco di errore osservabile.

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

Non sono sicuro esattamente lo scopo del codice che stai mostrando, che sta provando a testare un servizio simulato. Il problema di copertura è con il componente e il richiamo dell’errore non è stato chiamato (che viene chiamato solo quando c’è un errore).

Quello che faccio di solito per la maggior parte dei miei servizi osservabili, è quello di creare una simulazione i cui metodi si restituiscono da soli. Il servizio di simulazione ha un metodo di subscribe che accetta i callback next , di error e complete . L’utente della simulazione arriva a configurarlo per aggiungere un errore in modo da chiamare la funzione di error , o aggiungere dati, così viene chiamato il metodo next . La cosa che mi piace di più è che è tutto sincrono.

Di seguito è qualcosa di simile a quello che uso normalmente. È solo una class astratta per estendere altri mock. Fornisce le funzionalità di base fornite da un osservabile. Il servizio di simulazione estesa dovrebbe solo aggiungere i metodi di cui ha bisogno, ritornando nel metodo.

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

Ora nei tuoi test fai solo qualcosa

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