如何测试 ANGULAR - catchError 运算符 'rxjs'
HOW DO TESTING ANGULAR - catchError operator 'rxjs'
你能帮我测试一下代码吗?我无法测试可观察间隔 (1000) 管道内的 catchError 运算符 ...
我不太了解 angular 中针对间隔 (1000) 的可观察量的单元测试。我需要模拟一个区间的执行,模拟错误。我没有做代码,代码已经完成了,我是新员工,被分配了做单元测试的任务。不知道有没有必要在interval上catchError
我的文件 ts 是:
import { Injectable, EventEmitter } from '@angular/core';
import {Subscription, Observable, interval, BehaviorSubject, throwError} from 'rxjs';
import {take, map, catchError, finalize} from 'rxjs/operators';
import { environment } from 'environments/environment';
/**
* @Class CounterSessionProvider
* @description This class is a provider for control session time
* @author Andres Giraldo Londoño, Pragma S.A.
*/
@Injectable({
providedIn: 'root',
})
export class CounterSessionProvider {
// 4 minutes
public inactivityTime = 20; // environment.inactivityTime; // environment.inactivityTime -> 20 seconds
counterStateEmitter = new EventEmitter();
currentSubscription: Subscription;
public substracCounter$: BehaviorSubject<boolean>;
constructor() {
this.substracCounter$ = new BehaviorSubject(false);
}
public start() {
this.currentSubscription = this.initInterval().subscribe();
}
public initInterval(): Observable<number> {
return interval(1000).pipe(
take(this.inactivityTime),
map((index: number) => this.inactivityTime - (index + 1)),
catchError(err => {
this.counterStateEmitter.error(err);
return throwError(err);
})
);
}
public restarCounterTime(): void {
this.substracCounter$.next(true);
}
public stop() {
if (this.currentSubscription) {
this.currentSubscription.unsubscribe();
}
this.counterStateEmitter.emit('ABORTED');
}
}
我的文件 spec.ts 是:
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import { CounterSessionProvider } from './counter-session.provider';
import {interval, Observable, Subscription, throwError} from 'rxjs';
import {catchError, take} from 'rxjs/operators';
import { TestScheduler } from 'rxjs/testing';
describe('CounterSessionProvider', () => {
let counterSessionProvider: CounterSessionProvider;
let scheduler: TestScheduler;
beforeEach(() => {
counterSessionProvider = new CounterSessionProvider();
});
/*beforeEach(() => {
scheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
});*/
/*it('should throw error when initInterval interval', fakeAsync(() => {
scheduler.run(helpers => {
spyOn(counterSessionProvider, 'initInterval').and.returnValue(throwError('ERROR'));
const actions$ = helpers.hot('--a', { a: 1000 });
const completion$ = helpers.cold('--c', { c: 1000 });
expect(counterSessionProvider.initInterval).toBe(completion$);
});
}));*/
it('should initialize currentSubscription from interval', fakeAsync(() => {
let currentVal = null;
const numberObservable = counterSessionProvider.initInterval();
counterSessionProvider.currentSubscription = numberObservable.subscribe((value: number) => {
currentVal = value;
});
tick(1000);
expect(currentVal).toEqual(19);
counterSessionProvider.currentSubscription.unsubscribe();
}));
it('should emit a String COMPLETE when start() is called', () => {
spyOn(counterSessionProvider, 'start');
counterSessionProvider.start();
counterSessionProvider.counterStateEmitter.subscribe(value => {
expect(value).toEqual('COMPLETE');
});
expect(counterSessionProvider.start).toHaveBeenCalled();
});
it('should call stop()', () => {
spyOn(counterSessionProvider, 'stop');
counterSessionProvider.currentSubscription = new Subscription();
counterSessionProvider.stop();
counterSessionProvider.counterStateEmitter.subscribe((value: string) => {
expect(value).toEqual('ABORTED');
});
expect(counterSessionProvider.stop).toHaveBeenCalled();
});
it('should call method start and initilize currentSubscription', fakeAsync(() => {
counterSessionProvider.start();
tick(1000);
counterSessionProvider.currentSubscription.unsubscribe();
expect(counterSessionProvider.currentSubscription).not.toBeNull();
}));
it('should call restarCounterTime() and emit a TRUE', () => {
counterSessionProvider.restarCounterTime();
counterSessionProvider.substracCounter$.subscribe(value => expect(value).toBeTruthy());
});
it('should call method stop and emit ABORTED', fakeAsync(() => {
counterSessionProvider.start();
tick(1000);
counterSessionProvider.stop();
}));
/*beforeEach(() => {
counterSessionProvider = TestBed.get(CounterSessionProvider);
});
it('should be created', () => {
expect(counterSessionProvider).toBeTruthy();
});
it('should return ABORTED when stop function is called', () => {
counterSessionProvider.start();
counterSessionProvider.counterState.subscribe((msg: string) => {
expect(msg).toEqual('ABORTED');
});
counterSessionProvider.stop();
});*/
});
但是我需要覆盖你覆盖的所有代码。在下图中,我显示了我需要的覆盖范围。
尝试应用 Shashank Vivek 给我的建议,结果如下:
这就是我的建议,throw
一些 catchError
的东西,像这样 demo 否则 catchError
将不会被调用。我不确定catchError
是否应该被删除,你应该和团队核实一下(因为里面似乎有一些代码)
component.ts
public initInterval(): Observable<number> {
return interval(1000).pipe(
take(this.inactivityTime),
map((index: number) => {
// write some condition to "throw" otherwise "catchError" is of now use
if (this.inactivityTime > 30) {
throw new Error('inactivityTime greater than 30');
}
return this.inactivityTime - (index + 1);
}),
catchError((err) => {
this.counterStateEmitter.error(err);
return throwError(err);
}),
finalize(() => {
this.currentSubscription.unsubscribe();
this.counterStateEmitter.emit('COMPLETE');
})
);
}
然后 spec.ts 你设置条件抛出错误:
it('should handle error in initInterval', fakeAsync(() => {
let currentVal;
let someError;
spyOn(
counterSessionProvider.counterStateEmitter,
'error'
).and.callThrough();
counterSessionProvider.inactivityTime = 35; // just to simulate the error event as per the code I have added
const numberObservable = counterSessionProvider.initInterval();
counterSessionProvider.currentSubscription = numberObservable.subscribe(
(value: number) => {
currentVal = value;
},
(err) => {
someError = err;
}
);
tick(1000);
expect(currentVal).toBeUndefined();
expect(someError).toBeDefined(); // you can also use .toBe('inactivityTime greater than 30')
expect(counterSessionProvider.counterStateEmitter.error).toHaveBeenCalled();
counterSessionProvider.currentSubscription.unsubscribe();
}));
试一试,如果可行请告诉我。
覆盖率截图:
你能帮我测试一下代码吗?我无法测试可观察间隔 (1000) 管道内的 catchError 运算符 ...
我不太了解 angular 中针对间隔 (1000) 的可观察量的单元测试。我需要模拟一个区间的执行,模拟错误。我没有做代码,代码已经完成了,我是新员工,被分配了做单元测试的任务。不知道有没有必要在interval上catchError
我的文件 ts 是:
import { Injectable, EventEmitter } from '@angular/core';
import {Subscription, Observable, interval, BehaviorSubject, throwError} from 'rxjs';
import {take, map, catchError, finalize} from 'rxjs/operators';
import { environment } from 'environments/environment';
/**
* @Class CounterSessionProvider
* @description This class is a provider for control session time
* @author Andres Giraldo Londoño, Pragma S.A.
*/
@Injectable({
providedIn: 'root',
})
export class CounterSessionProvider {
// 4 minutes
public inactivityTime = 20; // environment.inactivityTime; // environment.inactivityTime -> 20 seconds
counterStateEmitter = new EventEmitter();
currentSubscription: Subscription;
public substracCounter$: BehaviorSubject<boolean>;
constructor() {
this.substracCounter$ = new BehaviorSubject(false);
}
public start() {
this.currentSubscription = this.initInterval().subscribe();
}
public initInterval(): Observable<number> {
return interval(1000).pipe(
take(this.inactivityTime),
map((index: number) => this.inactivityTime - (index + 1)),
catchError(err => {
this.counterStateEmitter.error(err);
return throwError(err);
})
);
}
public restarCounterTime(): void {
this.substracCounter$.next(true);
}
public stop() {
if (this.currentSubscription) {
this.currentSubscription.unsubscribe();
}
this.counterStateEmitter.emit('ABORTED');
}
}
我的文件 spec.ts 是:
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import { CounterSessionProvider } from './counter-session.provider';
import {interval, Observable, Subscription, throwError} from 'rxjs';
import {catchError, take} from 'rxjs/operators';
import { TestScheduler } from 'rxjs/testing';
describe('CounterSessionProvider', () => {
let counterSessionProvider: CounterSessionProvider;
let scheduler: TestScheduler;
beforeEach(() => {
counterSessionProvider = new CounterSessionProvider();
});
/*beforeEach(() => {
scheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
});*/
/*it('should throw error when initInterval interval', fakeAsync(() => {
scheduler.run(helpers => {
spyOn(counterSessionProvider, 'initInterval').and.returnValue(throwError('ERROR'));
const actions$ = helpers.hot('--a', { a: 1000 });
const completion$ = helpers.cold('--c', { c: 1000 });
expect(counterSessionProvider.initInterval).toBe(completion$);
});
}));*/
it('should initialize currentSubscription from interval', fakeAsync(() => {
let currentVal = null;
const numberObservable = counterSessionProvider.initInterval();
counterSessionProvider.currentSubscription = numberObservable.subscribe((value: number) => {
currentVal = value;
});
tick(1000);
expect(currentVal).toEqual(19);
counterSessionProvider.currentSubscription.unsubscribe();
}));
it('should emit a String COMPLETE when start() is called', () => {
spyOn(counterSessionProvider, 'start');
counterSessionProvider.start();
counterSessionProvider.counterStateEmitter.subscribe(value => {
expect(value).toEqual('COMPLETE');
});
expect(counterSessionProvider.start).toHaveBeenCalled();
});
it('should call stop()', () => {
spyOn(counterSessionProvider, 'stop');
counterSessionProvider.currentSubscription = new Subscription();
counterSessionProvider.stop();
counterSessionProvider.counterStateEmitter.subscribe((value: string) => {
expect(value).toEqual('ABORTED');
});
expect(counterSessionProvider.stop).toHaveBeenCalled();
});
it('should call method start and initilize currentSubscription', fakeAsync(() => {
counterSessionProvider.start();
tick(1000);
counterSessionProvider.currentSubscription.unsubscribe();
expect(counterSessionProvider.currentSubscription).not.toBeNull();
}));
it('should call restarCounterTime() and emit a TRUE', () => {
counterSessionProvider.restarCounterTime();
counterSessionProvider.substracCounter$.subscribe(value => expect(value).toBeTruthy());
});
it('should call method stop and emit ABORTED', fakeAsync(() => {
counterSessionProvider.start();
tick(1000);
counterSessionProvider.stop();
}));
/*beforeEach(() => {
counterSessionProvider = TestBed.get(CounterSessionProvider);
});
it('should be created', () => {
expect(counterSessionProvider).toBeTruthy();
});
it('should return ABORTED when stop function is called', () => {
counterSessionProvider.start();
counterSessionProvider.counterState.subscribe((msg: string) => {
expect(msg).toEqual('ABORTED');
});
counterSessionProvider.stop();
});*/
});
但是我需要覆盖你覆盖的所有代码。在下图中,我显示了我需要的覆盖范围。
尝试应用 Shashank Vivek 给我的建议,结果如下:
这就是我的建议,throw
一些 catchError
的东西,像这样 demo 否则 catchError
将不会被调用。我不确定catchError
是否应该被删除,你应该和团队核实一下(因为里面似乎有一些代码)
component.ts
public initInterval(): Observable<number> {
return interval(1000).pipe(
take(this.inactivityTime),
map((index: number) => {
// write some condition to "throw" otherwise "catchError" is of now use
if (this.inactivityTime > 30) {
throw new Error('inactivityTime greater than 30');
}
return this.inactivityTime - (index + 1);
}),
catchError((err) => {
this.counterStateEmitter.error(err);
return throwError(err);
}),
finalize(() => {
this.currentSubscription.unsubscribe();
this.counterStateEmitter.emit('COMPLETE');
})
);
}
然后 spec.ts 你设置条件抛出错误:
it('should handle error in initInterval', fakeAsync(() => {
let currentVal;
let someError;
spyOn(
counterSessionProvider.counterStateEmitter,
'error'
).and.callThrough();
counterSessionProvider.inactivityTime = 35; // just to simulate the error event as per the code I have added
const numberObservable = counterSessionProvider.initInterval();
counterSessionProvider.currentSubscription = numberObservable.subscribe(
(value: number) => {
currentVal = value;
},
(err) => {
someError = err;
}
);
tick(1000);
expect(currentVal).toBeUndefined();
expect(someError).toBeDefined(); // you can also use .toBe('inactivityTime greater than 30')
expect(counterSessionProvider.counterStateEmitter.error).toHaveBeenCalled();
counterSessionProvider.currentSubscription.unsubscribe();
}));
试一试,如果可行请告诉我。
覆盖率截图: