'Angular testing - service call piping off from() promise

I've searched around and can't find the same case for this scenario. My service pipes off a promise and makes a post request:

updateUserLocation(): Observable<any> {
  return from(this.deviceService.getCoordinates()).pipe(
    switchMap(coords => {
      if (coords) {
        return this.http.post<any>('/user/location', coords)
      } else {
        return of(null);
      }
    })
  );
}

The test mocks the device service but the http testing controller is not seeing a request.

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [HttpClientTestingModule],
    providers: [
      {
        provide: DeviceService,
        useValue: {
          getCoordinates: () => Promise.resolve({ lat: 0, lng: 0 }),
        }
      }
    ]
  });
  service = TestBed.inject(AccountService);
  httpClient = TestBed.inject(HttpClient);
  httpTestingController = TestBed.inject(HttpTestingController);
})

describe('# updateUserLocation', () => {
  it('posts location', () => {
    service.updateUserLocation().subscribe();

    const url = 'user/location';
    const req = httpTestingController.expectOne(url);

    req.flush({});
    httpTestingController.verify();
  })
})

But the test always fails saying

"expected one matching request for criteria 'user/location', found none"

I have tried wrapping the it function with waitForAsync, and fakeAsync to no avail. I'm not sure what else to try.

Removing the from(...) part of the service method, so it's just the http call the test will pass.



Solution 1:[1]

A colleague of mine pointed out there is a gotcha. In testing, promises and observables don't mix. "when testing combinations of promises and observables, the key is to just return observables instead of promises when you mock them". So this fixed the issue:

 provide: DeviceService,
    useValue: {
      getCoordinates: () => of({ lat: 0, lng: 0 }),
    }
  }

Now the mock returns an observable instead of a promise and the test passes. My colleague explained:

It has to do with the fact that RxJs is in fact actually synchronous by nature, operates off the event loop, while promises are truly async and operate off the microtask queue. When in a test environment, with the TestScheduler, it does not flush the microtask queue...and...I don't even think there is a way it could, and do so in a manner that avoided any number of issues resulting from blending microtasks with event tasks and a manual scheduler

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 inorganik