'Testing Observables with jest

How can I test Observables with Jest?

I have an Observable that fires ~every second, and I want to test that the 1st event is correctly fired, before jest times out.

const myObservable = timer(0, 1000); // Example here

it('should fire', () => {
  const event = myObservable.subscribe(data => {
    expect(data).toBe(0);
  });
});

This test passes, but it also passes if I replace with toBe('anything'), so I guess I am doing something wrong.

I tried using expect.assertions(1), but it seems to be only working with Promises.



Solution 1:[1]

There are some good examples in the Jest documentation about passing in an argument for the test. This argument can be called to signal a passing test or you can call fail on it to fail the test, or it can timeout and fail.

https://jestjs.io/docs/en/asynchronous.html

https://alligator.io/testing/asynchronous-testing-jest/

Examples

Notice I set the timeout to 1500ms

const myObservable = timer(0, 1000); // Example here

it('should fire', done => {
  myObservable.subscribe(data => {
    done();
  });
}, 1500); // Give 1500ms until it fails

Another way to see if it fails using setTimeout

const myObservable = timer(0, 1000); // Example here

it('should fire', done => {
  myObservable.subscribe(data => {
    done();
  });

  // Fail after 1500ms
  setTimeout(() => { done.fail(); }, 1500);
}, timeToFail);

Solution 2:[2]

My preferred way to test observables, without fake timers and timeouts, is to async, await and use resolves or rejects on an expected converted promise.

it('should do the job', async () => {
    await expect(myObservable
      .pipe(first())
      .toPromise())
      .resolves.toEqual(yourExpectation);
});

Update:

In Rxjs 7 and onwards, you can use lastValueFrom or firstValueFrom for the promise convertion

it('should do the job', async () => {
    await expect(lastValueFrom(myObservable))
      .resolves.toEqual(yourExpectation);
});

Solution 3:[3]

test('Test name', (done) => {
  service.getAsyncData().subscribe((asyncData)=>{
    expect(asyncData).toBeDefined();
       done();
    })
  });
})

Solution 4:[4]

Since version 6.2.1, RxJS supports Jest's fake time, so one way you could write this test is:

const myObservable = timer(0, 1000);

jest.useFakeTimers('modern');

it('should fire', () => {
  myObservable.subscribe(data => {
    expect(data).toBe(0);
  });
  jest.runAllTimers();
});

I've published a library that helps with this type of tests (in the below example, log adds logging to the observable, and getMessages retrieves messages that have been logged):

import { getMessages, log } from '1log';
import { timer } from 'rxjs';

const myObservable = timer(0, 1000);

test('timer', () => {
  myObservable.pipe(log).subscribe();
  jest.runAllTimers();
  expect(getMessages()).toMatchInlineSnapshot(`
    [create 1] +0ms [Observable]
    [create 1] [subscribe 1] +0ms [Subscriber]
    [create 1] [subscribe 1] [next] +1.000s 0
    [create 1] [subscribe 1] [complete] +0ms
    ยท [create 1] [subscribe 1] [unsubscribe] +0ms
  `);
});

Solution 5:[5]

the correct way to test any RXJS observable (Jest or no) is to the use the TestScheduler in rxjs/testing:

e.g.:

import { TestScheduler } from 'rxjs/testing';
import { throttleTime } from 'rxjs/operators';
 
const testScheduler = new TestScheduler((actual, expected) => {
  // asserting the two objects are equal - required
  // for TestScheduler assertions to work via your test framework
  // e.g. using chai.
  expect(actual).deep.equal(expected);
});
 
// This test runs synchronously.
it('generates the stream correctly', () => {
  testScheduler.run((helpers) => {
    const { cold, time, expectObservable, expectSubscriptions } = helpers;
    const e1 = cold(' -a--b--c---|');
    const e1subs = '  ^----------!';
    const t = time('   ---|       '); // t = 3
    const expected = '-a-----c---|';
 
    expectObservable(e1.pipe(throttleTime(t))).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });
});

From the RXJS marble testing testing docs.

Trying to convert observables, etc. into promises works fine if you have a simple observable. As soon as things become more complicated you are going to struggle without using marble diagrams and the correct testing library.

Solution 6:[6]

There are 2 approaches mentioned above

  1. Taking argument done in our test and call it when we have tested.
  2. Convert our observable to promise using firstValueFrom(myObs) or lastValueFrom(myObs). and use async await with them...

If we have multiple observables to test then we have to nest the observables in our test as we can call done() only once. In that case async await approach can come handy. In this example when we call filter Customer all three observables emits values so we have to test all of them.

it('Filter Customers based on Producers- Valid Case Promise way ',async()=>{
    
    service.filterCustomers('Producer-1');

    await expect(firstValueFrom(service.customers$)).resolves.toEqual(['Customer-1']);

    await firstValueFrom(service.customers$).then((customers:string[])=>{
      expect(customers).toEqual(['Customer-1']);
      expect(customers.length).toBe(1);
    })

    await expect(firstValueFrom(service.products$)).resolves.toEqual([]);
    await expect(firstValueFrom(service.types$)).resolves.toEqual([]);

  }).

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
Solution 2
Solution 3 Yoav Schniederman
Solution 4 Ivan
Solution 5 CTS_AE
Solution 6 Sanjay Garg