Tony Mathew
Tony Mathew

Reputation: 910

NestJs - test on response from interceptor

I have a simple interceptor that deletes a field from response:

import {
    CallHandler,
    ExecutionContext,
    Injectable,
    NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
    data: T;
}

@Injectable()
export class Transform<T> implements NestInterceptor<T, Response<T>> {
    intercept(
        context: ExecutionContext,
        next: CallHandler,
    ): Observable<Response<T>> {
        return next.handle().pipe(
            map((response) => {
                delete response?.value?.name;
                return response;
            }),
        );
    }
}

How can I write test case for this? Basically, I wanted to test if 'name' is deleted from response. I wrote the following test case but response is coming as undefined:

  it('should delete `name` from response if present', async () => {
    transformInterceptor = new TransformRepoResponse();
    const context = {
      switchToHttp: () => ({
        getRequest: () => jest.fn(),
      }),
    };
    const next = {
      handle: jest.fn().mockReturnValue({
        pipe: jest.fn(),
      }),
    };
    requestMock.method = 'GET';
    const response = await transformInterceptor.intercept(context, next);
    expect(response.value.name).toBe(undefined);
  });

Upvotes: 0

Views: 1221

Answers (2)

chickenchilli
chickenchilli

Reputation: 3548

This worked for me for your example:

import { CallHandler } from '@nestjs/common';
import { lastValueFrom, of } from 'rxjs';
import { createMocks } from 'node-mocks-http';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';

describe('Transform Interceptor test', () => {
  let transformInterceptor: Transform<any>;
  beforeEach(() => {
    transformInterceptor = new Transform();
  });

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

  it('should remove the name', async () => {
    const { req, res } = createMocks();
    const testContext = new ExecutionContextHost([req, res]);
    const nextSpy: CallHandler<any> = {
      handle: () =>
        of({ what: 'ever', value: { name: 'Mario', something: 'else' } }),
    };

    await expect(
      lastValueFrom(transformInterceptor.intercept(testContext, nextSpy)),
    ).resolves.toEqual({ what: 'ever', value: { something: 'else' } });
  });
});

I found good hint for this solution here: https://github.com/leosuncin/nest-api-example/blob/master/src/auth/interceptors/token.interceptor.spec.ts

Upvotes: 0

Jay McDoniel
Jay McDoniel

Reputation: 70570

Interceptors aren't async so you can't await their response without using lastValueFrom() as a wrapper. Also, your next.handle() should return an observable that will be acted on by the .pipe. Something like

it('should delete `name` from response if present', (done) => {
    transformInterceptor = new TransformRepoResponse();
    const context = {
      switchToHttp: () => ({
        getRequest: () => jest.fn(),
      }),
    };
    const next = {
      handle: jest.fn().mockReturnValue(of({
        value: {
          name: 'name-field',
          foo: 'foo-field',
        }
      }),
    };
    requestMock.method = 'GET';
    let errors = []
    transformInterceptor.intercept(context, next).subscribe({
      next: (data) => {
        try {
          expect(data).toEqual({
            value: { foo: 'foo-field' }
          })
        } catch (err) {
          errors.push(err);
        }
      complete: () => done(errors.length ? errors.join(' ') : undefined)
      }
    })
  });

Now you're testing the interceptor as an observable, you only expect one value so using the next of subscribe is fine, and you're telling Jest that the test is finished when the observable completes. You're also persisting any errors that happen by catching them in the next and using them as part of the done call if they exist.

Upvotes: 2

Related Questions