sclausen
sclausen

Reputation: 1719

How do I unit test a custom CacheInterceptor from NestJS?

I wrote an own CacheInterceptor to cache POST requests as well and take the Accept-Language header into account. Of course I want to unit test it, but I don't know how to properly do so, since the trackBy method needs an ExecutionContext and the method uses the httpAdapterHost and reflector fields. Has anybody done this before and knows how to achieve full test coverage?

EDIT: Here is the code of the CacheInterceptor

import {
    CACHE_KEY_METADATA,
    CacheInterceptor,
    ExecutionContext,
    Injectable,
} from '@nestjs/common';

import { createHash } from 'crypto';

@Injectable()
export class MyCacheInterceptor extends CacheInterceptor {
    trackBy(context: ExecutionContext): string | undefined {
        const httpAdapter = this.httpAdapterHost.httpAdapter;
        const cacheMetadata = this.reflector.get(CACHE_KEY_METADATA, context.getHandler());

        const request = context.switchToHttp().getRequest();

        return [
            cacheMetadata,
            httpAdapter.getRequestUrl(request),
            JSON.stringify(request.body),
            request.headers['accept-language'],
        ]
            .reduce(
                (hash, somethingToHash) => (
                    hash.update(
                        somethingToHash
                            ? Buffer.from(somethingToHash)
                            : Buffer.alloc(0)
                    )
                ),
                createHash('md5'),
            )
            .digest('hex');
    }
}

Upvotes: 2

Views: 1544

Answers (1)

doublethink13
doublethink13

Reputation: 1015

Please bear in mind that the following example is testing the interceptor in isolation. Some tweaks may be needed for your use case, but the overall approach should be valid.

  1. I would inject the cache and reflector dependencies using the constructor:
@Injectable()
export class MyCacheInterceptor extends CacheInterceptor {
  constructor(
    @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache,
    @Inject(Reflector) protected readonly reflector: Reflector
  ) {
    super(cacheManager, reflector);
  }

  trackBy(context: ExecutionContext): string | undefined {

// ...
// ...
  1. Your tests could look like:
describe("MyCacheInterceptor", () => {
  let interceptor: MyCacheInterceptor;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      imports: [CacheModule.register()],
      providers: [
        { provide: CACHE_MANAGER, useValue: {} },
        { provide: Reflector, useValue: { get: () => "hello" } },
        MyCacheInterceptor,
      ],
    }).compile();

    // see issue: https://github.com/nestjs/nest/issues/8076
    module.createNestApplication();

    interceptor = module.get(MyCacheInterceptor);
  });

  it("creates", () => {
    expect(interceptor).toBeTruthy();
  });

  it("tracks something", () => {
    const mockExecutionContext: ExecutionContext = createMock<ExecutionContext>(
      {
        getHandler: () => ({}),
        switchToHttp: () => ({
          getRequest: () => ({
            url: "/test-url",
            originalUrl: "/test-url",
            method: "GET",
            body: {
              someKey: "someValue",
            },
            headers: {
              "accept-language": "en",
            },
          }),
        }),
      }
    );

    const result = interceptor.trackBy(mockExecutionContext);

    expect(result).toBe("d4f8ad8ba612cda9a5fda09cc244120c");
  });
});
  1. There is a way of mocking httpAdapterHost (as well as cacheManager and reflector):
(interceptor["httpAdapterHost"] as any) = {
  httpAdapter: { getRequestUrl: () => "hello" },
};

I consider this an anti-pattern, because you shouldn't be mocking/spying on internal methods and properties. However, if you check this GitHub issue, you'll see that there isn't a good or proper way of mocking an HttpAdapterHost, so in this case it may be a good rule to break.

  1. createMock comes from @golevelup/ts-jest

Upvotes: 3

Related Questions