54

假设我有一个订阅服务功能的组件:

export class Component {

   ...

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

subscribe 调用将两个匿名函数作为参数,我设法为 data 函数设置了一个工作单元测试,但 Karma 不会接受错误的覆盖。

在此处输入图像描述

我试过监视 console.error 函数,抛出一个错误,然后期望间谍被调用,但这并没有完全做到。

我的单元测试:

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

测试此类匿名函数的最佳实践是什么?确保测试覆盖率的最低要求是什么?

4

3 回答 3

89

您可以简单地模拟 Observable 抛出错误对象,Observable.throw({status: 404})并测试 observable 的错误块。

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

2019 年更新:

由于有些人懒得阅读评论,所以我把它放在这里:这是一个最佳实践,对 Rxjs 使用错误

import { throwError } from 'rxjs'; // make sure to import the throwError from rxjs
const xService = fixture.debugElement.injector.get(SomeService);
const mockCall = spyOn(xService,'method').and.returnValue(throwError({status: 404}));
于 2017-06-23T20:02:50.657 回答
21

不确定您显示的代码的确切用途,它试图测试模拟服务。覆盖问题在于组件和错误回调尚未被调用(仅在出现错误时调用)。

对于我的大多数可观察服务,我通常做的是创建一个模拟,其方法只是返回自身。模拟服务有一个subscribe接受nexterrorcomplete回调的方法。模拟的用户可以将其配置为添加错误以error调用函数,或添加数据以next调用方法。我最喜欢的一点是它都是同步的。

下面是我通常使用的东西。它只是其他模拟扩展的抽象类。它提供了可观察对象提供的基本功能。扩展模拟服务应该只添加它需要的方法,并在方法中返回自身。

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

现在在您的测试中,您只需执行类似的操作

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();
})
于 2016-10-10T14:41:56.887 回答
0

我在 service.ts 中嘲笑了虚假的错误响应

通过评论之前的 http 调用和

让错误:任何=新错误(“失败”);返回新的 Observable(错误);

于 2021-11-23T06:53:51.973 回答