Skip to content

Instantly share code, notes, and snippets.

@micalevisk
Last active August 7, 2022 01:27
Show Gist options
  • Select an option

  • Save micalevisk/33d793202541f044d8f5bccb81049b94 to your computer and use it in GitHub Desktop.

Select an option

Save micalevisk/33d793202541f044d8f5bccb81049b94 to your computer and use it in GitHub Desktop.
An attempt to unit test a timeout NestJS (v8) interceptor.
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$);
}),
);
});
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