Jasmine Cold Marble with toPromise - never ending await

406 Views Asked by At

I have a debtService which calls backend API with simple HttpClient request. It returns an observable. Now, in AppComponent, I'm calling debtService's method to fetch debt. I want to make sure, that when debt is fetched, also the method called logGetDebtAttempt is being called.

import { Component } from '@angular/core';
import { Observable, Subject, Subscriber, TeardownLogic } from 'rxjs';
import { DebtService } from './services/debt.service';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(private debtService: DebtService) {
  }

  async getDebt() {
    // getNationalDebt() returns Observable<Number>
    await this.debtService.getNationalDebt().toPromise();
    this.logGetDebtAttempt();
  }

  logGetDebtAttempt() {
  }
}

That's why I wrote such test:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { cold } from 'jasmine-marbles';
import { Observable, Subscriber, TeardownLogic } from 'rxjs';
import { AppComponent } from './app.component';
import { DebtService } from './services/debt.service';
import { HttpClientTestingModule } from '@angular/common/http/testing'


const chartServiceStub = {
  getNationalDebt(): Observable<Number> {
    return cold('--x|', { x: 1 });
  }
};

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        HttpClientTestingModule
      ],
      declarations: [
        AppComponent
      ],
      providers: [
        { provide: DebtService, useValue: chartServiceStub },
        
      ]
    }).compileComponents();

  });

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
  });

  it('should test', async () => {
    spyOn(component, 'getDebt').and.callThrough();
    spyOn(component, 'logGetDebtAttempt').and.callThrough();

    await component.getDebt();

    expect(component.logGetDebtAttempt).toHaveBeenCalled();
  });
});

When running tests, await component.getDebt(); never ends and it creates a timeout.

Could someone please explain why is that happening?

2

There are 2 best solutions below

0
AliF50 On

Disclaimer: This won't completely answer your question.

I am thinking it happens because of the -- syntax where each dash is one millisecond.

From my experience, I was never a fan of jasmine-marbles or any marble testing helping library because of those dashes.

For your scenario, you don't even need marbles. You can use of.

import { of } from 'rxjs';
....
const chartServiceStub = {
  getNationalDebt(): Observable<Number> {
    return of(1);
  }
};

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        HttpClientTestingModule
      ],
      declarations: [
        AppComponent
      ],
      providers: [
        { provide: DebtService, useValue: chartServiceStub },
        
      ]
    }).compileComponents();

  });

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
  });

  it('should test', async () => {
    spyOn(component, 'getDebt').and.callThrough();
    spyOn(component, 'logGetDebtAttempt').and.callThrough();

    await component.getDebt();

    expect(component.logGetDebtAttempt).toHaveBeenCalled();
  });
});

And also, since it is a .toPromise, I would do a take(1) on it too. I think having this take, it might help with the marbles scenario as well. I know this is an http call most likely and the take is not needed but you could argue that it is better design because if I am going to use await, the stream cannot emit forever.

import { Component } from '@angular/core';
import { Observable, Subject, Subscriber, TeardownLogic } from 'rxjs';
import { DebtService } from './services/debt.service';
import { take } from 'rxjs/operators';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(private debtService: DebtService) {
  }

  async getDebt() {
    // getNationalDebt() returns Observable<Number>
    await this.debtService.getNationalDebt().pipe(take(1)).toPromise();
    this.logGetDebtAttempt();
  }

  logGetDebtAttempt() {
  }
}
0
nephiw On

I have been searching for similar answers to your question and I just found a paragraph within the rxjs documentation here that reads:

At this time, the TestScheduler can only be used to test code that uses RxJS schedulers - AsyncScheduler, etc. If the code consumes a Promise, for example, it cannot be reliably tested with TestScheduler, but instead should be tested more traditionally. See the Known Issues section for more details.

The known issues section goes into slightly more detail about not testing promises. But this leads me to believe that if you use promises within your code, you can not use marbles for testing.