How to Test Timeout Interceptor in Nestjs Using Jest

1.8k views Asked by At

I can't find any explanation on how to test interceptors in NestJS.

Please help me to test the Interceptor using jest?

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from "@nestjs/common";
import { Observable, throwError, TimeoutError } from "rxjs";
import { catchError, timeout } from "rxjs/operators";

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
    constructor(private readonly interval: number) {}

    intercept(_context: ExecutionContext, next: CallHandler): Observable<any> {
        if (this.interval > 0) {
            return next.handle().pipe(
                timeout(this.interval),
                catchError((error) => {
                    if (error instanceof TimeoutError) {
                        return throwError(new RequestTimeoutException(`The operation timed out. `));
                    }
                    return throwError(error);
                }),
            );
        }
        return next.handle();
    }
}
2

There are 2 answers

0
Micael Levi On

I tried to write unit tests for this interceptor once but I didn't like it :/ Look: https://gist.github.com/micalevisk/33d793202541f044d8f5bccb81049b94

0
Farista Latuconsina On

I tried to find similar issues out there, hoping that anyone would be willing to answer but I still got nothing until today. Nevertheless, I try to figure it out all alone. For someone out there who's probably looking for a possible workaround.

Let's take a look at the simple timeout interceptor example from nestjs docs

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000),
      catchError(err => {
        if (err instanceof TimeoutError) {
          return throwError(() => new RequestTimeoutException());
        }
        return throwError(() => err);
      }),
    );
  };
};

The possible unit test to test the error inside the observable is to mock the return value after a specified time (delay). In this case, it would be more than 5000. This can be achieved by utilizing the delay rxjs operator.

So the appropriate unit test for this timeout interceptor would be something similar to this.

describe('TimeoutInterceptor', () => {
  const executionContext = mockedExecutionContext; // your mocked execution context
  const callHandler = mockedCallHandler; // your mocked call handler

  let timeoutInterceptor: TimeoutInterceptor;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [TimeoutInterceptor],
    }).compile();

    timeoutInterceptor = moduleRef.get<TimeoutInterceptor>(TimeoutInterceptor);
  });

  describe('when intercept is called', () => {
    const returnedValue: [] = [];

    describe('and the request time is not exceeded the max allowed timeout in ms', () => {
      it(`should return the to be returned value`, (done: any) => {
        callHandler.handle.mockReturnValue(
          of(returnedValue).pipe(
            delay(2500), // below 5000
          ),
        );

        timeoutInterceptor.intercept(executionContext, callHandler).subscribe({
          next(value) {
            expect(value).toStrictEqual(returnedValue);
          },
          complete() {
            done();
          },
        });
      });
    });

    describe('and the request time exceeded (+1000ms) the max allowed timeout in ms', () => {
      it(`should throw ${RequestTimeoutException.name}`, (done: any) => {
        callHandler.handle.mockReturnValue(
          of(returnedValue).pipe(delay(5000 + 1000)), // 5000 + 1000ms
        );

        timeoutInterceptor.intercept(executionContext, callHandler).subscribe({
          error(err) {
            expect(err).toBeInstanceOf(RequestTimeoutException);
            done(); // don't forget to invoke this done, or the test will be hang
          },
        });
      });
    });
  });
});