在服务中使用 observables 测试错误案例
Testing error case with observables in services
假设我有一个订阅服务功能的组件:
export class Component {
...
ngOnInit() {
this.service.doStuff().subscribe(
(data: IData) => {
doThings(data);
},
(error: Error) => console.error(error)
);
};
};
订阅调用采用两个匿名函数作为参数,我已经设法为数据函数设置了一个工作单元测试,但 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.
}
);
测试此类匿名函数的最佳做法是什么?确保测试覆盖率的最低限度是多少?
不确定您显示的代码的确切用途,它正在尝试测试模拟服务。覆盖问题是组件和错误回调未被调用(仅在出现错误时调用)。
我通常为我的大部分可观察服务做的是创建一个模拟,其方法只是 returns 本身。模拟服务有一个 subscribe
方法,它接受 next
、error
和 complete
回调。 mock 的用户可以将其配置为添加错误,以便调用 error
函数,或者添加数据,从而调用 next
方法。我最喜欢的一点是它都是同步的。
下面是我平时使用的东西。它只是一个抽象 class 供其他模拟扩展。它提供了可观察对象提供的基本功能。扩展模拟服务应该只添加它需要的方法,在方法中返回它自己。
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();
})
你可以像Observable.throw({status: 404})
一样模拟Observable抛出错误对象并测试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}));
我在 service.ts
中嘲笑了虚假的错误响应
通过评论之前的 http 调用和
let error:any = new Error("failed");
return 新的 Observable(错误);
假设我有一个订阅服务功能的组件:
export class Component {
...
ngOnInit() {
this.service.doStuff().subscribe(
(data: IData) => {
doThings(data);
},
(error: Error) => console.error(error)
);
};
};
订阅调用采用两个匿名函数作为参数,我已经设法为数据函数设置了一个工作单元测试,但 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.
}
);
测试此类匿名函数的最佳做法是什么?确保测试覆盖率的最低限度是多少?
不确定您显示的代码的确切用途,它正在尝试测试模拟服务。覆盖问题是组件和错误回调未被调用(仅在出现错误时调用)。
我通常为我的大部分可观察服务做的是创建一个模拟,其方法只是 returns 本身。模拟服务有一个 subscribe
方法,它接受 next
、error
和 complete
回调。 mock 的用户可以将其配置为添加错误,以便调用 error
函数,或者添加数据,从而调用 next
方法。我最喜欢的一点是它都是同步的。
下面是我平时使用的东西。它只是一个抽象 class 供其他模拟扩展。它提供了可观察对象提供的基本功能。扩展模拟服务应该只添加它需要的方法,在方法中返回它自己。
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();
})
你可以像Observable.throw({status: 404})
一样模拟Observable抛出错误对象并测试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}));
我在 service.ts
中嘲笑了虚假的错误响应通过评论之前的 http 调用和
let error:any = new Error("failed"); return 新的 Observable(错误);