Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test NestJs response interceptor

I tried to follow this thread but it I keep getting an error.

transform-response.interceptor.ts:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiResponseInterface } from '@walletxp/shared-interfaces';

@Injectable()
export class TransformResponseInterceptor<T>
  implements NestInterceptor<T, ApiResponseInterface<Record<string, unknown>>>
{
  intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponseInterface<Record<string, unknown>>> {
    return next.handle().pipe(map((data) => ({ success: true, data })));
  }
}

and for it's test, transform-response.interceptor.spec.ts:

import { TransformResponseInterceptor } from './transform-response.interceptor';
const interceptor = new TransformResponseInterceptor();

const executionContext: any = {
  switchToHttp: jest.fn().mockReturnThis(),
  getRequest: jest.fn().mockReturnThis(),
};

const callHandler = {
  handle: jest.fn(),
};

describe('ResponseInterceptor', () => {
  it('should be defined', () => {
    expect(interceptor).toBeDefined();
  });
  describe('#intercept', () => {
    it('t1', async () => {
      (executionContext.switchToHttp().getRequest as jest.Mock<any, any>).mockReturnValueOnce({
        body: { data: 'mocked data' },
      });
      callHandler.handle.mockResolvedValueOnce('next handle');
      const actualValue = await interceptor.intercept(executionContext, callHandler);
      expect(actualValue).toBe('next handle');
      expect(executionContext.switchToHttp().getRequest().body).toEqual({
        data: 'mocked data',
        addedAttribute: 'example',
      });
      expect(callHandler.handle).toBeCalledTimes(1);
    });
  });
});

My goal would be to mock the data returned from the controller and check if after it goes through the interceptor it equals the formatted data that I want.

like image 262
zedian Avatar asked Sep 18 '25 22:09

zedian


2 Answers

I'll show a simple and cleaner real world example from my project. The example is similar to the one shown in the question which is about using an interceptor to transform an object. I use this interceptor to exclude sensitive properties like hashedPassword from the user object sent as a response:

describe('SerializerInterceptor', () => {
  let interceptor: SerializerInterceptor

  beforeEach(() => {
    interceptor = new SerializerInterceptor(UserDto)
  })

  it('should return user object without the sensitive properties', async () => {

    const context = createMock<ExecutionContext>()
    const handler = createMock<CallHandler>({
      handle: () => of(testUser)
    })

    const userObservable = interceptor.intercept(context, handler)
    const user = await lastValueFrom(userObservable)

    expect(user.id).toEqual(testUser.id)
    expect(user.username).toEqual(testUser.username)

    expect(user).not.toHaveProperty('hashedPassword')
  })
})

For mocking the ExecutionContext and CallHandler, we use createMock() function from the @golevelup/ts-jest package.

NestJS Interceptor under the hood uses RxJS. So, when its intercept() method is called by the framework, it returns an Observable of our object. To cleanly extract our value from this Observable, we use the convenience function lastValueFrom() from RxJS.

The testUser here, is your object under test. You need to create it and provide it to the mock handler as shown above.

like image 149
Yogesh Umesh Vaity Avatar answered Sep 21 '25 15:09

Yogesh Umesh Vaity


I have tested my interceptors, using the calls to the application, more like an end to end test.

import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { INestApplication, HttpStatus } from '@nestjs/common';

import { EmulatorHeadersInterceptor } from '@LIBRARY/interceptors/emulator-headers.interceptor';

import { AppModule } from '@APP/app.module';

describe('Header Intercepter', () => {
    let app: INestApplication;

    afterAll(async () => {
        await app.close();
    });

    beforeAll(async () => {
        const moduleFixture: TestingModule = await Test.createTestingModule({
            imports: [AppModule],
        }).compile();

        app = moduleFixture.createNestApplication();
        app.useGlobalInterceptors(new EmulatorHeadersInterceptor());
        await app.init();
    });

    it('./test (PUT) should have the interceptor data', async () => {
        const ResponseData$ = await request(app.getHttpServer())
            .put('/test')
            .send();

        expect(ResponseData$.status).toBe(HttpStatus.OK);
        expect(ResponseData$.headers['myheader']).toBe('interceptor');
    });
});

My interceptor is adding a header field, but for your interceptor, you would replace the header interceptor I am using, with your interceptor. From there, you can test that the response contains what you want.

like image 23
Steven Scott Avatar answered Sep 21 '25 15:09

Steven Scott