typescript 使用Jasmine测试fakeAsync、延迟和滴答的可观察性

nhhxz33t  于 2023-01-06  发布在  TypeScript
关注(0)|答案(1)|浏览(135)

我有一个管道可以帮助返回一个可观察对象的状态。

import {Pipe, PipeTransform} from '@angular/core';
import {Observable, of} from 'rxjs';
import {catchError, map, startWith} from 'rxjs/operators';

/** Specifies the status of an Observable. */
export interface ObservableStatus<T> {
  loading?: boolean;
  value?: T;
  error?: boolean;
}

/** Returns the status {@code ObservableStatus} of a given Observable. */
@Pipe({name: 'getObserverStatus'})
export class ObservableStatusPipe implements PipeTransform {
  transform<T = Item>(observer: Observable<T>):
      Observable<ObservableStatus<T>> {
    return observer.pipe(
        map((value: T) => {
          return {
            loading: false,
            error: false,
            value,
          };
        }),
        startWith({loading: true}),
        catchError(error => of({loading: false, error: true})));
  }
}

我想用Jasmine为这个功能编写单元测试。我试着用fakeAsync, delay, tick, flush, discardPeriodicTasks,但是好像不起作用。

我尝试过不同的方法:
  • 方式一
describe('loading test', () => {
  const loadingPipe = new ObservableStatusPipe();

  it('returns state of an observable', fakeAsync(() => {
    const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));

    const result: Observable<ObservableStatus<Item>> = loadingPipe.transform(input);

    result.subscribe(val => {
      expect(val.loading).toEqual(true);
      expect(val.value).toBeUndefined();
    });
    tick(2000);
    result.subscribe(val => {
      expect(val.loading).toEqual(false);
      expect(val.value!.name).toEqual('Item');
    });
  }));
});

上述测试用例未通过,故障如下:

Error: Expected true to equal false. (at expect(val.loading).toEqual(false))
Error: 1 periodic timer(s) still in the queue.
  • 方法2:从网上,我看到我们可以使用flush来刷新任何挂起的任务。
describe('loading test', () => {
  const loadingPipe = new ObservableStatusPipe();

  it('returns state of an observable', fakeAsync(() => {
    const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));

    const result: Observable<ObservableStatus<Item>> = loadingPipe.transform(input);

    result.subscribe(val => {
      expect(val.loading).toEqual(true);
      expect(val.value).toBeUndefined();
    });
    tick(2000);
    result.subscribe(val => {
      expect(val.loading).toEqual(false);
      expect(val.value!.name).toEqual('Item');
    });
    flush();     // <----- here.
  }));
});

这有助于解决Error: 1 periodic timer(s) still in the queue.问题。但是,测试用例仍然失败,原因如下:

Error: Expected true to equal false.
TypeError: Cannot read properties of undefined (reading 'name')

所有这一切是否意味着tick在某种程度上没有模拟input上的时间?

  • 我在input上直接测试了相同的模拟:
describe('loading test', () => {
  const loadingPipe = new ObservableStatusPipe();

  it('returns state of an observable', fakeAsync(() => {
    const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));

    input.subscribe(val => {
      expect(val.name).toBeUndefined();
    });
    tick(2000);
    input.subscribe(val => {
      expect(val.name).toEqual('Item');
    });
    discardPeriodicTasks();    <--- Using flush() here is causing 'Error: 2 periodic timer(s) still in the queue' error.
  }));
});

上面的测试用例通过了。但是我仍然不明白为什么flush()在这里不起作用。

describe('loading test', () => {
  const loadingPipe = new ObservableStatusPipe();

  it('returns state of an observable', fakeAsync(() => {
    const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));

    const result: Observable<ObservableStatus<Item>> = loadingPipe.transform(input);

    result.subscribe(val => {
      expect(val.loading).toEqual(true);
      expect(val.value).toBeUndefined();
    });
    tick(2000);
    result.subscribe(val => {
      expect(val.loading).toEqual(false);
      expect(val.value!.name).toEqual('Item');
    });
    discardPeriodicTasks();
  }));
});

这仍然会失败,并显示相同的错误:

Error: Expected true to equal false. (at expect(val.loading).toEqual(false))
Error: 1 periodic timer(s) still in the queue.

有人能解释一下这里发生了什么吗,以及如何解决这个问题?

    • 顺便说一句,我不想使用debounceTimesetTimeOut来解决这个问题。因为它们看起来不像是模拟时间,而是实际上等待和延迟时间,即使用debounceTime(1000)实际上会等待1秒。我不想在我的情况下(我想模拟时间)。**
  • 订阅可观察对象时使用delay运算符有效(不使用tick)。
describe('loading test', () => {
  const loadingPipe = new ObservableStatusPipe();

  it('returns state of an observable', fakeAsync(() => {
       const input: Observable<Item> = of({name: 'Item'}).pipe(delay(1000));

       const result: Observable<ObservableStatus<Item>> = loadingPipe.transform(input);

       result.subscribe(val => {
         expect(val.loading).toEqual(true);
         expect(val.value).toBeUndefined();
       });
       result.pipe(delay(2000)).subscribe(val => {
         expect(val.loading).toEqual(false);
         expect(val.value!.name).toEqual('Item');
       });
       discardPeriodicTasks();
     }));
});

这实际上是延迟/等待1000或2000ms,还是使用fakeAsync以某种方式让延迟来模拟时间?
谢谢!

l7wslrjt

l7wslrjt1#

我认为你的方式1是好的,但你可能会面临迟订阅和多订阅的情况。试试这个:

// Take 2 emissions
    result.pipe(take(2)).subscribe(val => {
      if (val.loading) {
        console.log('False case');
        expect(val.loading).toEqual(true);
        expect(val.value).toBeUndefined();
      } else {
        console.log('True case');
        expect(val.loading).toEqual(false);
        expect(val.value!.name).toEqual('Item');
      }
    });
    
    // Move time in a fake way. The above observable stream should hopefully emit 
    // twice and therefore there is a take(2).
    // Make sure you see both console.logs to ensure both paths were asserted.
    tick(2000);

相关问题