如何在方法中测试订阅

问题描述

考虑这个角度分量:

export class CheckoutComponent {
  constructor(private paymentService: PaymentService,private paymentModalService: PaymentModalService) {}

  public onCheckout(): void {
    const paymentStatus$: Observable<string> = this.paymentService.getStatus();

    const paymentrequired$ = paymentStatus$.pipe(map(paymentStatus => paymentStatus === 'INCOMPLETE'));

    paymentrequired$.subscribe(() => {
      this.paymentModalService.open();
    });
  }
}

我正在尝试编写一个 jasmine 测试来确认调用paymentModalService.open()

import { cold } from 'jasmine-marbles';

describe('CheckoutComponent',() => {
  let component: CheckoutComponent;
  let mockPaymentService: jasmine.SpyObj<PaymentService>;
  let mockPaymentModalService: jasmine.SpyObj<PaymentModalService>;

  beforeEach(() => {
    mockPaymentService = jasmine.createSpyObj(['getStatus']);
    mockPaymentModalService = jasmine.createSpyObj(['open']);
    component = new CheckoutComponent(mockPaymentService,mockPaymentModalService);
  });

  it('should open payment modal if required',() => {
    mockPaymentService.getStatus.and.returnValue(cold('a',{ a: 'INCOMPLETE' }));
    component.onCheckout();

    expect(mockPaymentModalService.open).toHaveBeenCalled(); // FAIL: was never called
  });
});

然而,显然从未调用过 open 方法

在断言方法已被调用之前,有没有办法等待冷测试 observable 完成发射?

我曾尝试使用 fakeAsynctick,但似乎都无济于事。我意识到它适用于 of() 而不是 TestColdobservable,但我希望对可观察行为进行更精细的控制。我真的不打算完成 getStatus() observable。

解决方法

您的主要问题是:

我想要对可观察行为进行更精细的控制。我真的不打算完成 getStatus() observable。

如果你不想完成它,那么它本质上不是一个冷的 observable,而是一个热的 observable,这意味着你可以创建一个主题并通过

   var sub = new Subject();
    mockPaymentService.getStatus.and.returnValue(sub.asObservable());
    // run method and let it subscribe 
    component.onCheckout();
    
// fire it
    sub.next('a',{ a: 'INCOMPLETE' });
 expect(mockPaymentModalService.open).toHaveBeenCalled();

我建议您阅读有关如何编写组件测试用例的文档: Angular guide to writing test cases

问题: 在您当前的测试方法中,您将无法运行组件生命周期,而是手动运行它们并跳过实际测试用例。 它适用于服务,但不适用于组件。

,

让我建议你一些东西,这不是你的问题的重点,但无论如何都可以提供帮助。

我的建议是将逻辑从组件转移到服务。

查看您的代码,似乎 CheckoutComponent 需要一个可观察的 paymentRequired$,它会在需要付款时发出通知。

让我们假设 this.paymentService.getStatus() 是对后端 API 的调用,它返回付款状态。在这种情况下,我会在服务中做的事情是这样的

export class PaymentService {
  constructor(http,...)

  // Private Subjects - using Subject to notify allows multicasting
  private _paymentRequired$ = new Subject<boolean>();

  // Public API Streams
  public paymentRequired$ = this._paymentRequired$.asObservable();

  // Public API methods
  public getStatus() {
    this.http.get(....).pipe(
      tap({
        next: resp => this._paymentRequired$.next(resp === 'INCOMPLETE'),error: err => {// manage the error case}
      })
    )
  }
}

如果你像这样将逻辑移到服务上,那么CheckoutComponent可以简单地订阅paymentService.paymentRequired$在其ngOnInit方法中的observable,并且测试只能处理服务而不是组件。

特别是测试必须以某种方式实例化服务、订阅 paymentRequired$ 可观察对象、调用 getStatus() 并检查 paymentRequired$ 是否已通知。

,

阅读关于 component marble tests 的文档,我发现我需要像这样刷新测试调度程序:

import { cold,getTestScheduler } from 'jasmine-marbles';

it('should open payment modal if required',() => {
  mockPaymentService.getStatus.and.returnValue(cold('a',{ a: 'INCOMPLETE' }));
  component.onCheckout();
  getTestScheduler().flush(); // <=== prompt the scheduler to execute all of its queued actions

  expect(mockPaymentModalService.open).toHaveBeenCalled(); // SUCCESS
});