Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest Unit Test for Nodejs/NestJS ExceptionFilter Catch Method

This is my BadRequestExceptionFilter written in Typescript for Nodejs/Nestjs


@Catch(BadRequestException)
export class BadRequestExceptionFilter implements ExceptionFilter {
  constructor(private logger: AppLoggerService) {}
  catch(exception: BadRequestException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof BadRequestException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message = {
      Title: exception.message.error,
      Type: 'Exception - BadRequestExceptionFilter',
      Detail: exception.message,
      Status: '',
    };

    this.logger.error(message, '');
    response.code(status).send({
      statusCode: status,
      ...(exception.getResponse() as object),
      timestamp: 'Exception - BadRequestException' + new Date().toISOString(),
    });
  }
}

This is my unit test andand 2 assert are done here. First assert is to check whether mockLogger.error called. It is working. Second assert to check whether response.code(status).send() is called. but getting this error. expect(jest.fn()).toBeCalled()

Expected number of calls: >= 1
Received number of calls:    0

const mockLogger = { error: jest.fn() };

const mockContext: any = {
  switchToHttp: () => ({
    getRequest: () => ({
      url: 'mock-url',
    }),
    getResponse: () => {
      const response = {
        code: jest.fn().mockReturnThis(),
        send: jest.fn().mockReturnThis(),
      };
      return response;
    },
  }),
};

describe('BadRequestExceptionFilter', () => {
  let filter: BadRequestExceptionFilter;

  beforeEach(() => {
    filter = new BadRequestExceptionFilter(mockLogger as any);
  });

  it('should catch and log the error', () => {
    const mockException: BadRequestException = new BadRequestException();

    mockException.name = 'BadRequestException';
    mockException.getResponse = jest.fn().mockReturnValue(of('getResponse'));
    mockException.getStatus = () => 404;

    jest.fn(mockContext.switchToHttp().getResponse().send);
    filter.catch(mockException, mockContext);
    expect(mockLogger.error).toBeCalled();

    expect(
      mockContext
        .switchToHttp()
        .getResponse()
        .code().send,
    ).toBeCalled();
  });

});

like image 462
user3497702 Avatar asked Jun 05 '26 12:06

user3497702


2 Answers

I think its straight forward to write the test case for exception filter using Nest Testing module. Check below how I achieved the same:

// all-exception.filter.spec.ts
import {
    Test,
    TestingModule
} from '@nestjs/testing';
import {
    HttpStatus,
    HttpException
} from '@nestjs/common';

import { AllExceptionsFilter } from './all-exceptions.filter';
import { AppLoggerService } from '../services/logger/app-logger.service';

const mockAppLoggerService = {
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
    debug: jest.fn()
};
const mockJson = jest.fn();
const mockStatus = jest.fn().mockImplementation(() => ({
    json: mockJson
}));
const mockGetResponse = jest.fn().mockImplementation(() => ({
    status: mockStatus
}));
const mockHttpArgumentsHost = jest.fn().mockImplementation(() => ({
    getResponse: mockGetResponse,
    getRequest: jest.fn()
}));

const mockArgumentsHost = {
    switchToHttp: mockHttpArgumentsHost,
    getArgByIndex: jest.fn(),
    getArgs: jest.fn(),
    getType: jest.fn(),
    switchToRpc: jest.fn(),
    switchToWs: jest.fn()
};

describe('System header validation service', () => {
    let service: AllExceptionsFilter;

    beforeEach(async () => {
        jest.clearAllMocks();
        const module: TestingModule = await Test.createTestingModule({
            providers: [
                AllExceptionsFilter,
                {
                    provide: AppLoggerService,
                    useValue: mockAppLoggerService
                },
            ]
        }).compile();
        service = module.get<AllExceptionsFilter>(AllExceptionsFilter);
    });

    describe('All exception filter tests', () => {

        it('should be defined', () => {
            expect(service).toBeDefined();
        });

        it('Http exception', () => {
            service.catch(
                new HttpException('Http exception', HttpStatus.BAD_REQUEST),
                mockArgumentsHost
            );
            expect(mockHttpArgumentsHost).toBeCalledTimes(1);
            expect(mockHttpArgumentsHost).toBeCalledWith();
            expect(mockGetResponse).toBeCalledTimes(1);
            expect(mockGetResponse).toBeCalledWith();
            expect(mockStatus).toBeCalledTimes(1);
            expect(mockStatus).toBeCalledWith(HttpStatus.BAD_REQUEST);
            expect(mockJson).toBeCalledTimes(1);
            expect(mockJson).toBeCalledWith({
                message: 'Http exception'
            });
        });
    });
});

like image 129
iAviator Avatar answered Jun 07 '26 02:06

iAviator


I did something like this and it worked!

export const contextMock = (roles?: string[]) => {
    const ctx: any = {}
    ctx.switchToHttp = jest.fn().mockReturnValue({
        getRequest: jest.fn().mockReturnValue(requestMock()),
        getResponse: jest.fn().mockReturnValue(responseMock()),
    })
    ctx.getHandler = jest.fn().mockReturnValue({ roles }) as Function

    return ctx
}

-----------------------------------------------

const ctxMock = contextMock() as any

expect(ctxMock.switchToHttp).toHaveBeenCalled()
expect(ctxMock.switchToHttp().getResponse).toHaveBeenCalled()
expect(ctxMock.switchToHttp().getResponse().status).toHaveBeenCalled()
expect(ctxMock.switchToHttp().getResponse().json).toHaveBeenCalled()

As I understood , we always have to pass a mock for the "expect".

like image 42
Carol Serrão Avatar answered Jun 07 '26 03:06

Carol Serrão