1

考虑这个角度分量:

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();
    });
  }
}

我正在尝试编写一个茉莉花测试来确认它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 方法。

在断言该方法已被调用之前,有什么方法可以等待可观察的冷测试完成发射?

我试过使用fakeAsynctick但似乎也没有帮助。我意识到它可以of()代替 a 使用TestColdObservable,但我希望对可观察行为进行更精细的控制。我真的不打算完成getStatus()observable。

4

3 回答 3

1

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

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

查看您的代码,似乎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,并且测试只能处理服务而不是组件。

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

于 2021-05-25T06:40:46.813 回答
1

阅读有关组件大理石测试的文档,我发现我需要像这样刷新测试调度程序:

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
});
于 2021-05-25T06:51:48.087 回答
0

你的主要问题是:

我想要对可观察行为进行更精细的控制。我真的不打算完成 getStatus() 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 指南

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

于 2021-05-24T22:45:30.417 回答