【问题标题】:HOW DO TESTING ANGULAR - catchError operator 'rxjs'如何测试 ANGULAR - catchError 运算符 'rxjs'
【发布时间】:2020-12-12 10:48:57
【问题描述】:

你能帮我测试一下代码吗?我无法测试可观察区间 (1000) 管道内的 catchError 运算符...

我对 inteval (1000) 的 observables 的角度单元测试知之甚少。我需要模拟一个区间的执行,并模拟错误。我没有编写代码,代码已经完成,我是一名新员工,我被分配了进行单元测试的任务。不知道有没有必要在间隔上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 给我的建议,结果如下:

【问题讨论】:

  • 我在提供的component.ts 代码中看不到catchError 代码。它出现在随附的屏幕截图中
  • 抱歉,我删除了,忘记放回去了。我已经编辑过了。
  • 捕获错误,这意味着之前在某处抛出了错误:使用您的代码,它不可能发生。
  • @Gérome Grignon 你是对的,不可能发生错误。你有什么建议?从管道中删除 catchError?所以我可以得到 100% 的覆盖率。
  • 是的,并且来自 start() 函数内的订阅

标签: angular typescript unit-testing rxjs karma-jasmine


【解决方案1】:

这就是我的建议,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();
  }));

试一试,如果可行,请告诉我。

报道截图:

【讨论】:

  • 谢谢!但是 spyOn(counterSessionProvider,'error').and.callThrough() 中的方法 'error';不存在。我应该放哪一个?
  • 它没有用。抛出这样的错误:错误:预期未定义要定义。和另一个类似这样的错误:错误:预期的间谍错误已被调用。
  • @NelsonCastiblanco :是的,当我在本地运行代码时,我意识到了错误。我设置了条件 if this.inactivityTime &lt; 1 ,那将永远不会触发 interval 一起。这次我改变了条件并在本地进行了覆盖。这条线现在似乎被覆盖了。试试看!
  • 效果很好。非常感谢你,你太棒了!解决方案是显式抛出错误,因为类型为interval的可观察对象无法知道它们何时抛出错误。
猜你喜欢
  • 2022-01-14
  • 1970-01-01
  • 2020-10-07
  • 2021-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-16
相关资源
最近更新 更多