Reputation: 1719
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
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.
@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 {
// ...
// ...
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");
});
});
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.
createMock
comes from @golevelup/ts-jest
Upvotes: 3