Last active
August 7, 2022 01:27
-
-
Save micalevisk/33d793202541f044d8f5bccb81049b94 to your computer and use it in GitHub Desktop.
An attempt to unit test a timeout NestJS (v8) interceptor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { createMock } from '@golevelup/ts-jest'; | |
| import { CallHandler, ExecutionContext, RequestTimeoutException } from '@nestjs/common'; | |
| import { throwError } from 'rxjs'; | |
| import { marbles } from 'rxjs-marbles/jest'; | |
| import { TimeoutInterceptor } from '@/api/interceptors/timeout.interceptor'; | |
| describe('TimeoutInterceptor', () => { | |
| const TIMEOUT = TimeoutInterceptor.TIMEOUT_RESPONSE_TIME_MS; | |
| // | |
| let timeoutInterceptor: TimeoutInterceptor; | |
| beforeEach(() => { | |
| timeoutInterceptor = new TimeoutInterceptor(); | |
| }); | |
| it(`should return the data, when the request handler take less than ${TIMEOUT}ms to execute`, | |
| marbles((m) => { | |
| // #region | |
| const ctx = createMock<ExecutionContext>(); | |
| const expectedData = {}; | |
| const next: CallHandler = { | |
| handle: () => m.cold('(e|)', { e: expectedData }), | |
| }; | |
| // #endregion | |
| const handlerData$ = timeoutInterceptor.intercept(ctx, next); | |
| /** Marble emiting the value `expectedData` and complete. */ | |
| const expected$ = m.cold('(e|)', { e: expectedData }); | |
| m.expect(handlerData$).toBeObservable(expected$); | |
| }), | |
| ); | |
| it(`should throw RequestTimeoutException (HTTP 408) error, when the request handler take ${TIMEOUT}ms to execute`, | |
| marbles((m) => { | |
| // #region | |
| const ctx = createMock<ExecutionContext>(); | |
| const next = { | |
| handle: () => m.cold(`${TIMEOUT}ms |`), | |
| }; | |
| // #endregion | |
| const handlerData$ = timeoutInterceptor.intercept(ctx, next); | |
| /** Marble emiting an error after `TIMEOUT`ms. */ | |
| const expected$ = m.cold(`${TIMEOUT}ms #`, undefined, new RequestTimeoutException()); | |
| m.expect(handlerData$).toBeObservable(expected$); | |
| }), | |
| ); | |
| it(`should forward the error thrown`, | |
| marbles((m) => { | |
| // #region | |
| const ctx = createMock<ExecutionContext>(); | |
| const error = new Error('something'); | |
| const next = { | |
| handle: () => throwError(error), | |
| }; | |
| // #endregion | |
| const handlerData$ = timeoutInterceptor.intercept(ctx, next); | |
| /** Marble emiting an error after `TIMEOUT`ms. */ | |
| const expected$ = m.cold(`#`, undefined, error); | |
| m.expect(handlerData$).toBeObservable(expected$); | |
| }), | |
| ); | |
| it(`should throw RequestTimeoutException (HTTP 408) error, when the request handler take ${2 * TIMEOUT}ms to run`, | |
| marbles((m) => { | |
| // #region | |
| const ctx = createMock<ExecutionContext>(); | |
| const next = { | |
| handle: () => m.cold(`${2 * TIMEOUT}ms |`), | |
| }; | |
| // #endregion | |
| const handlerData$ = timeoutInterceptor.intercept(ctx, next); | |
| /** Marble emiting the error after `TIMEOUT`ms. */ | |
| const expected$ = m.cold(`${TIMEOUT}ms #`, undefined, new RequestTimeoutException()); | |
| m.expect(handlerData$).toBeObservable(expected$); | |
| }), | |
| ); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| Injectable, | |
| NestInterceptor, | |
| ExecutionContext, | |
| CallHandler, | |
| RequestTimeoutException, | |
| } from '@nestjs/common'; | |
| import ms from 'ms'; | |
| import { Observable, throwError, TimeoutError } from 'rxjs'; | |
| import { catchError, timeout } from 'rxjs/operators'; | |
| @Injectable() | |
| export class TimeoutInterceptor implements NestInterceptor { | |
| static TIMEOUT_RESPONSE_TIME_MS = ms('5s'); | |
| intercept(_ctx: ExecutionContext, next: CallHandler): Observable<unknown> { | |
| return next.handle().pipe( | |
| timeout(TimeoutInterceptor.TIMEOUT_RESPONSE_TIME_MS), | |
| catchError((err) => { | |
| if (err instanceof TimeoutError) { | |
| return throwError(new RequestTimeoutException()); | |
| } | |
| return throwError(err); | |
| }), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment