Tester le cas d'erreur avec les observables dans les services

disons que j'ai un composant qui s'abonne à une fonction de service:

export class Component {

   ...

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

l'appel d'abonnement prend deux fonctions anonymes comme paramètres, j'ai réussi à mettre en place un test de l'Unité de travail pour la fonction de données, mais Karma n'acceptera pas la couverture pour l'erreur.

enter image description here

j'ai essayé d'espionner sur la console.fonction d'erreur, lancer une erreur et s'attendre à ce que l'Espion ait été appelé mais cela ne fait pas tout à fait il.

mon test d'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.
    }
);

Quelle est la meilleure pratique pour tester des fonctions anonymes comme celles-ci? Quel est le strict minimum pour obtenir une couverture?

12
demandé sur isherwood 2016-10-10 17:04:04

2 réponses

Je ne suis pas sûr du but exact du code que vous montrez, qui essaye de tester un service simulé. Le problème de couverture est que le composant et le rappel d'erreur n'ont pas été appelés (ce qui n'est appelé qu'en cas d'erreur).

ce que je fais habituellement pour la plupart de mes services observables, c'est de créer une maquette dont les méthodes se retournent d'elles-mêmes. Le service simulé a un subscribe méthode qui accepte l' next,error et complete rappels. L'utilisateur de la simulation devient le configurer pour ajouter une erreur si l' error la fonction est appelée, ou ajoute des données, donc le next méthode est appelée. Ce qui me plaît le plus, c'est que tout est synchrone.

ci-Dessous est quelque chose comme ce que j'utilise normalement. C'est juste une classe abstraite pour que d'autres moqueurs l'étendent. Il fournit la fonctionnalité de base qu'un observable fournit. Le service extension mock devrait juste ajouter les méthodes dont il a besoin, en se retournant dans la méthode.

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

Maintenant, dans votre les tests que vous venez de faire quelque chose comme

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();
})
13
répondu Paul Samsotha 2016-10-11 01:26:54

vous pouvez simplement simuler un objet D'erreur de lancer Observable comme Observable.throw({status: 404})et tester le bloc d'erreur de l'observable.

const xService = fixture.debugElement.injector.get(SomeService);
const mockCall = spyOn(xService, 'method')
                       .and.returnValue(Observable.throw({status: 404}));
13
répondu Aniruddha Das 2017-06-23 21:06:33