Testing an rxjs observable using Jest: a beginner's guide

Consider the following code snippet which defines a function that returns an observable :

retrieveAll = (): Observable<Well[]> =>
  from(this.wellRepository.retrieveAssets()).pipe(
    map((assets: Asset[]) => this.mapper.mapping(assets)),
    catchError(() => {
      throw new Error("Something went wrong");
    })
  );

I attempted to test this function in the following way :

describe("Api Well Repository", () => {
  it("should retrieve wells", (done) => {
    const apiWellRepository = new ApiWellRepository(
      new InMemoryWellRepository()
    );

    const observable$: Observable<Well[]> = apiWellRepository.retrieveAll();
    observable$.subscribe((wells: Well[]) =>
      expect(wells).toStrictEqual([
        {
          id: "1",
          name: "Angola_0",
        },
      ])
    );
  });
});

The InMemoryWellRepository class provides an async array data source :

export class InMemoryWellRepository {
  private readonly assets: Asset[] = [
    {
      id: "1",
      name: "Angola",
    },
  ];

  retrieveAssets = async (): Promise<Asset[]> => this.assets;
}

This testing scenario fails due to a timeout issue.

If deliberately expecting an incorrect output, the test also results in a timeout error with a message indicating a failure in deep equality comparison. While the test appears to be functioning correctly, it seems to encounter difficulties completing and ends up timing out.

Answer №1

Ensure you remember to invoke the done function within the observable.subscribe() method post-assertions. To demonstrate, I will be utilizing jestjs as the test runner. Note that other test runners like Mocha and Jasmine operate similarly. Refer to asynchronous#callbacks

Jest will pause execution until the done callback is triggered, concluding the test.

If done() is not called, the test will fail due to a timeout error, which is the expected behavior.

InMemoryWellRepository.ts:

export type Asset = any;

export class InMemoryWellRepository {
  private readonly assets: Asset[] = [
    {
      id: '1',
      name: 'Angola',
    },
  ];

  retrieveAssets = async (): Promise<Asset[]> => this.assets;
}

ApiWellRepository.ts:

import { catchError, from, map, Observable } from 'rxjs';
import { Asset, InMemoryWellRepository } from './InMemoryWellRepository';

export type Well = any;

export class ApiWellRepository {
  mapper = {
    mapping: (arr) => arr.map((v, i) => ({ ...v, name: `${v.name}_${i}` })),
  };
  constructor(private wellRepository: InMemoryWellRepository) {}

  retrieveAll = (): Observable<Well[]> =>
    from(this.wellRepository.retrieveAssets()).pipe(
      map((assets: Asset[]) => this.mapper.mapping(assets)),
      catchError(() => {
        throw new Error('Something went wrong');
      }),
    );
}

ApiWellRepository.test.ts:

import { Observable } from 'rxjs';
import { ApiWellRepository, Well } from './ApiWellRepository';
import { InMemoryWellRepository } from './InMemoryWellRepository';

describe('Api Well Repository', () => {
  it('should retrieve wells', (done) => {
    const apiWellRepository = new ApiWellRepository(new InMemoryWellRepository());

    const observable$: Observable<Well[]> = apiWellRepository.retrieveAll();
    observable$.subscribe((wells: Well[]) => {
      expect(wells).toStrictEqual([
        {
          id: '1',
          name: 'Angola_0',
        },
      ]);
      done();
    });
  });
});

Test result:

 PASS   v7  packages/v7/stackoverflow/ApiWellRepository.test.ts
  Api Well Repository
    ✓ should retrieve wells (3 ms)

---------------------------|---------|----------|---------|---------|-------------------
File                       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------------------|---------|----------|---------|---------|-------------------
All files                  |      95 |      100 |   88.88 |   92.85 |                   
 ApiWellRepository.ts      |    92.3 |      100 |   85.71 |      90 | 16                
 InMemoryWellRepository.ts |     100 |      100 |     100 |     100 |                   
---------------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.149 s

package versions:

"rxjs": "^7.8.1",
"jest": "^29.5.0",

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Show the quantity of statements within python unit tests

When using phpUnit, the test results show both the number of tests that were run and the number of assertions made. However, in my current process of running Python's unit tests, only the number of tests run is shown. I'm curious if there is a me ...

Set a generic function type to a variable

Currently, I am facing an issue where I have a declared function type with generics that I want to assign to a named arrow function. Below is an example of the code: import { AutocompleteProps } from '@material-ui/lab/Autocomplete'; const itemTo ...

Simulating Express Requests using ts-mockito in Typescript

Is there a way to simulate the Request class from Express using ts-mockito in typescript? I attempted the following import { Request, Response } from "express"; const request = mock(Request); const req: Request = instance(request); but encou ...

Issue with selecting an individual value in NGRX entity using id

My goal is to retrieve an object by its Id from the entity state using a reducer. Here is the implementation: export interface MessageState extends EntityState<Message> { // additional entities state properties loaded: boolean; loading: boole ...

What exactly does bivarianceHack aim to achieve within TypeScript type systems?

As I went through the TypeScript types for React, I noticed a unique pattern involving a function declaration called bivarianceHack(): @types/react/index.d.ts type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void ...

While attempting to utilize npm install, I encounter an error on a discord bot stating "msvsVersion is not defined."

Struggling with self-hosting a TypeScript discord bot, the setup process has been a puzzle. It's supposed to generate a build directory with an index.js file, but it's unclear. Installed Visual Studio Build Tools 2017 as required, yet running npm ...

Determine the full location information with the help of Google Maps SDK without the need

My current challenge involves dealing with a list of unformatted and incorrectly written addresses. I am seeking a way to iterate through these flawed strings and generate more organized and accurate addresses using one of the many Google Maps SDKs availa ...

What sets a module apart from a script?

As I delve into the depths of TypeScript documentation to grasp the concept of modules, particularly ES6 modules, I stumbled upon some interesting insights. typescript-modules - this documentation talks about typescript modules and highlights an important ...

Validating Inputs with an Array of Values in my Angular 2 Application

I have been exploring ways to retrieve data from an array of values instead of a single value when using [(ngModel)] binding in my Angular 2 application. The current setup functions perfectly with a single value, as shown below. The data is pulled from the ...

Class-validator does not have any associated metadata

Struggling with implementing a ValidationPipe in my code, I consistently encounter the warning "No metadata found. There is more than one class-validator version installed probably. You need to flatten your dependencies" when sending a request. The struct ...

Try querying again if you receive no results from an http.get request in Angular using RXJS Operators

In my Angular service, I sometimes encounter an issue where I receive an empty array. In such cases, I would like to trigger a fresh query. let request = this.http.post(this.searchlUrl, payload).pipe( retryWhen(errors => errors.pipe(delay(100 ...

Using a module without a declaration file: tips for troubleshooting

I am working on a Node.js project using Typescript and would like to incorporate the npm package country-code-lookup. However, this package does not have type declarations available. Despite the lack of typings, I still want to use this package in my proj ...

Expo project encountering issues with nested navigation in React-Native

When configuring the header in my app using React Native Stack Navigation and nesting a Drawer Navigation inside of it, I encountered a strange issue. While everything worked fine in the android emulator, opening the app using Expo resulted in nothing but ...

Material-UI Slide component is encountering an issue where it is unable to access the style property of an undefined variable

Recently, I incorporated Material-UI Slide into my project and I'm curious about why the code functions correctly when written in this manner: {selectedItem && selectedItem.modal && selectedItem.modal.body ? ( selectedItem.modal.body.map((section ...

A Typescript interface designed for a higher-order function that returns another function

I am working with a StoryOptions object that includes a property called actionFn. This property, when invoked, will return a function utilizing function currying. The actionFn function must accept an object of type ActionBundle</code and should return ...

Tips for incorporating buttons into columns on ng2-table within Angular 2

I am in need of a table with an edit button in every column using ng2. However, I have encountered an issue placing the buttons at the end of each column. Here is my HTML code: <ng-table [config]="config.sorting" (tableChanged)="onChangeTable(co ...

Retrieve the original HTTP request in Angular before it is sent out

I am currently attempting to retrieve the HTTP requests before they are actually sent to the server. Initially, I tried creating a HttpIntercepter like this: @Injectable() export class HttpLoggingInterceptorProvider implements HttpInterceptor { interce ...

Response received through GET call in text format

Implementing a typed response in a simple GET request seems to be causing a strange behavior in the compiler. The application compiles successfully, but a red error is returned with the message displayed in the VS Code screenshot below: ERROR in src/app/s ...

Having trouble with implementing a custom hook in React using Typescript?

Having difficulty with typing in the following Hook: import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; type Callback<T> = (value?: T) => void; type DispatchWithCallback<T> = (value: T, callback?: Ca ...

Function useAppDispatch is missing a return type

.eslintrc.js module.exports = { root: true, extends: [ '@react-native-community', 'standard-with-typescript', 'plugin:@typescript-eslint/recommended', 'plugin:jest/recommended', 'plugin:p ...