'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.



Solution 1:[1]

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.

Solution 2:[2]

If you're looking for a simple unit test, then you need to understand how RxJS works for asynchronous testing. Something like the following could work:

describe('ResponseInterceptor', () => {
  let interceptor: ResponseInterceptor;

  beforeEach(() => {
    interceptor = new ResponseInterceptor();
  });

  it('should map the data', (done) => {
    // this sets up a mock execution context (which you don't use so it's blank)
    // and a mock CallHandler that returns a known piece of data 'test data'
    const obs$ = interceptor.intercept({} as any, { handle: () => of('test data') });
    // this tests the observable, and calls done when it is complete
    obs$.subscribe({
      next: (val) => {
        expect(val).toEqual({ success: true, data: 'test data' })
      }),
      complete: () => done()
    })
  });

});

Solution 3:[3]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Steven Scott
Solution 2 Jay McDoniel
Solution 3