【问题标题】:Unit testing a BehaviorSubject in a service对服务中的 BehaviorSubject 进行单元测试
【发布时间】:2020-09-09 06:58:29
【问题描述】:

我正在做一个玩具项目来学习 Angular 中的单元测试(用 Jest 代替 Karma)。我见过类似的问题,但他们都以不同的方式处理订阅,所以我无法得到适合我的确切答案。

TL;DR:我正在尝试测试一个 BehaviorSubject,但它发送的不是我期望的值,而是它的初始值。我了解测试事项中订阅的顺序,但我还想不出正确的顺序。

我在服务中有一个 BehaviorSubject:

export class SomeService {
  // ...
  remainingSeconds: number;

  private _seconds: Subject<number> = new BehaviorSubject(RegularTimerSeconds.WORK_TIME);
  public readonly seconds$: Observable<number> = this._seconds.asObservable();

  setTimer(seconds: number) {
    this.remainingSeconds = seconds;
    this._seconds.next(this.remainingSeconds);
  }

  // ...
}

和一个组件上的监听器,它使用模板中的异步管道订阅 Observable

export class SomeComponent implements OnInit, OnDestroy {
  // ...
  remainingSeconds$ = this.timer.seconds$.pipe(tap(seconds => console.log(`remainingSeconds$: ${seconds}`)));

  constructor(public timer: TimerService) {}
}

订阅在模板中发生如下

<ng-container *ngIf="{
  remainingSeconds: remainingSeconds$ | async,
  currentState: currentState$ | async,
  informationText: informationText$ | async,
  doneCounter: doneCounter$ | async
} as data">
  <!-- ... -->
  <div class="regular-timer-container" *ngIf="data.remainingSeconds !== undefined">
    <p>Remaining seconds: {{ data.remainingSeconds }}</p>  
  </div>
</ng-container>

我想测试一下,当服务中的setTimer函数被调用时,组件中的remainingSeconds$ observable也得到了正确的值。特别是,如果服务中的“remainingSeconds”值未定义,我想确保剩余的Seconds$ observable 获得更新的未定义值,从而隐藏适当的UI 元素。

我在测试中遇到的问题(代码如下)是,当我从组件调用 service.setTimer(undefined) 时,我得到的是 BehaviorSubject 的初始值而不是 undefined 值 I想要(这使得测试毫无用处)。

it(`shouldn't render the timer component if the seconds value is unset`, async(() => {
  component.remainingSeconds$
    .subscribe(
      seconds => {
        expect(seconds).toEqual(undefined);

        fixture.detectChanges();

        const timerContainerDom: DebugElement = fixture.debugElement;
        const regularTimerComponent = timerContainerDom.query(By.directive(AppComponent));

        expect(regularTimerComponent).toBeFalsy();
      }
    );

  timerService.setTimer(undefined);
}));

我正在添加a stackblitz link,但遗憾的是我无法在那里运行 npm test。希望它能给我一些关于我要完成的工作的基本想法。

【问题讨论】:

  • 你试过component.remainingSeconds$.pipe(skip(1)).subscribe(...)吗?
  • @AndreiGătej 谢谢你的回复——我没用过。从理论上和实际上它都可以正常工作,因为它跳过了 BehaviorSubject 的初始值,这解决了我的问题。同时,我想知道测试 BehaviorSubjects 是否有关于它们的一般“最佳实践”。如果您可以将此作为答案发布,我会选择它作为好答案。 :)
  • 我不知道“一刀切”的解决方案,但是在您的情况下,我要做的是分开任务,问自己:我要测试什么?例如,如果您想测试 observable 在某些事件之后发出正确的值,您可能需要为此使用弹珠测试。如果您想测试某些元素是否正确出现在 DOM 中,我不会考虑事件的真实来源(例如 Subject),而是一个模拟的 observable,它只会发出一些值,让我专注于视图测试。
  • 我已经添加了答案。谢谢!

标签: angular unit-testing rxjs


【解决方案1】:

您可以通过以下方式快速解决此问题:

component.remainingSeconds$.pipe(skip(1)).subscribe(...)

【讨论】:

    【解决方案2】:

    试试

    TestBed.configureTestingModule({
            declarations: [
              AppComponent
            ],
            providers: [
              TimerService
            ]
          }).compileComponents();
    

    并删除 TestBed.inject

    【讨论】:

    • 感谢您的回答 :) 在测试中我使用的是 timerService 的实例。您建议我在哪里以及如何实例化它?
    • 无需实例化,只需将其添加到 providers 数组中即可。然后从 testbed.get 中获取服务实例。
    • 其实我使用 .inject() 的原因是因为get() is deprecated
    猜你喜欢
    • 2014-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-10
    • 2021-04-25
    • 1970-01-01
    相关资源
    最近更新 更多