Search code examples
angularrxjsjasminejasmine-marbles

How to test subscription inside method


Consider this angular component:

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

I'm trying to write a jasmine test to confirm that paymentModalService.open() was called:

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

However the open method was apparently never called.

Is there any way to wait for the cold test observable to complete emit before asserting that the method has been called?

I've tried using fakeAsync and tick but it does not either seem to help. I realize it works with of() instead of a TestColdObservable, but I would like more granular control of observable behavior. I don't really intend to complete the getStatus() observable.


Solution

  • Reading documentation on component marble tests I found that I need to flush the test scheduler like so:

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