I have a controller that uses NestJS built-in Logger via dependency injection in constructor of the controller:
constructor(private readonly logger: Logger)
I want to be able to mock it in my Jest tests to see which methods and with what arguments are being called during logging. I tried this syntax:
providers[{
provide: Logger,
useValue: {
log: jest.fn(),
}
}]
In that case this line:
expect(Logger).toHaveBeenCalledTimes(1);
Returns:
Matcher error: received value must be a mock or spy function
Any help will be highly appreciated!
In your test, you should get the logger back out of the DI context using moduleFixture.get(Logger) (or something very similar) and then check expect(logger.log).toHaveBeenCalledTimes(1). Logger itself is a class, not a spy or mock, so Jest doesn't know what to do with that.
Full solution that worked:
import { Test } from '#nestjs/testing';
let logger: Logger;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: Logger,
useValue: {
log: jest.fn(),
},
},
],
}).compile();
logger = moduleRef.get<Logger>(Logger);
});
And then later in the test itself:
expect(logger.log).toHaveBeenCalledTimes(1);
expect(logger.log).toHaveBeenCalledWith('Your log message here')
Related
I have a simple Jest test for my Nest JS project.
The Jest looks like:
import { Test, TestingModule } from '#nestjs/testing';
import { IbmVpcController } from './ibm.vpc.controller';
import { IbmVpcServiceMock } from './ibm.vpc.service.mock';
import { ModuleMocker, MockFunctionMetadata } from 'jest-mock';
import { MOCKED_VPC } from '../../repository/ibm/mock.vpc.data';
const moduleMocker = new ModuleMocker(global);
describe('IbmVpcController', () => {
let controller: IbmVpcController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [IbmVpcController],
providers: [IbmVpcServiceMock]
})
.useMocker((token) => {
if (token === IbmVpcServiceMock) {
return {
list: jest.fn().mockResolvedValue(MOCKED_VPC.VPCs),
get: jest.fn().mockResolvedValue(MOCKED_VPC.VPCs[0]),
create: jest.fn().mockResolvedValue(MOCKED_VPC.VPCs[0]),
update: jest.fn().mockResolvedValue(MOCKED_VPC.VPCs[0]),
};
}
if (typeof token === 'function') {
const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
const Mock = moduleMocker.generateFromMetadata(mockMetadata);
return new Mock();
}
})
.compile();
controller = module.get<IbmVpcController>(IbmVpcController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
My jest.config.js looks like:
module.exports = {
verbose: true,
preset: "ts-jest",
testEnvironment: "node",
roots: ["./src"],
transform: { "\\.ts$": ["ts-jest"] },
testRegex: "(/__test__/.*|(\\.|/)(spec))\\.ts?$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
transformIgnorePatterns: [
'<rootDir>/node_modules/',
],
globals: {
"ts-jest": {
tsconfig: {
// allow js in typescript
allowJs: true,
},
},
},
};
However it is failing with the following error:
FAIL apps/protocols/src/ibm/vpc/ibm.vpc.controller.spec.ts
● Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
C:\Users\pradipm\clients\CloudManager\cm_6\occm\client-infra\nest-services\node_modules\axios\index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import axios from './lib/axios.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1350:14)
at Object.<anonymous> (../../node_modules/retry-axios/src/index.ts:124:1)
Now able to get it what I am missing in my typescript Nest's Jest configuration.
Basically I tried out some more options also:
I tried out specifying the transformIgnorePatterns as only '/node_modules/'.
Tried out excluding the lodash-es', 'axios'
Tried out transformIgnorePattens as '/lib/' (where axois is there)
Added allowJs: true in the tsconfig.app.json compileOptions.
Any help to get trough my first basic test would be helpful.
With axios version 1.1.2 there's a bug with jest. You can resolve it by adding moduleNameMapper: { '^axios$': require.resovle('axios') } to your jest configuration
I am trying to implement custom Telegraf middleware with nestjs-telegraf library and connection to DB using Prisma.
My AppModule is:
#Module({
imports: [
TelegrafModule.forRootAsync({
imports: [ConfigModule, LoggerModule],
useFactory: (configService: ConfigService, logger: LoggerMiddleware) => {
return {
token: configService.get<string>("TELEGRAM_TOKEN")!,
middlewares: [sessionMiddleware, logger.use]
};
},
inject: [ConfigService, LoggerMiddleware]
}),
PrismaModule
],
controllers: [],
providers: [...someProviders]
})
export class AppModule {}
LoggerMiddleware:
#Injectable()
export class LoggerMiddleware {
constructor(private readonly prisma: PrismaService) {}
async use(ctx: Context, next: NextFunction) {
const listUser = await this.prisma.user.findMany()
console.log('listUser = ', listUser)
next()
}
}
LoggerModule:
#Module({
imports: [PrismaModule],
providers: [LoggerMiddleware, PrismaService],
exports: [LoggerMiddleware]
})
export class LoggerModule {}
It starts without errors and code reaches my logger middleware but then I am getting an error:
TypeError: Cannot read properties of undefined (reading 'prisma')
I have access to Prisma service from another provider and a connection to DB works.
At the start, nest initializes all dependencies successfully but I don't understand why during execution it fell with this error. What did I do wrong?
The answer was given by Alexander Bukhalo on github
I am trying to add an auth guard to a GraphQL resolver but it doesn't seem to be working as expected.
I have a simple guard that should reject all requests:
#Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
return false;
}
}
and have applied it in a resolver:
#Resolver()
export class RecipeResolver {
#Query(() => [Recipe])
#UseGuards(AuthGuard)
public recipes(): Recipe[] {
return this.recipeData;
}
}
This does not work in the resolver and the canActivate is never fired. This does work in an HTTP Controller.
Changing #UseGuards(AuthGuard) to #UseGuards(new AuthGuard()) works but will not go through the dependency injection process which is not ideal.
What am I doing wrong?
Edit:
original app.module.ts:
#Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: 'shcema.gql',
playground: true,
}),
RecipeResolver,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
From the code, the resolver was in the wrong location (in an imports array instead of providers) which meant Nest didn't set up the request lifecycle properly.
I created an app using nest.js and bull.
I added bull-board package to monitor my queues, but in documentation, only one way to add it to the app is mount as middleware:
In main.ts:
app.use('/admin/queues', bullUI);
Is there any way to add bullUI in a normal nest controller, after jwt auth? Like:
#UseGuards(JwtAuthGuard)
#Get("queues")
activate() {
return UI
}
You can use any express middleware like this inside controllers, but maybe some cases cause errors like serving static files with Guard exception and etc.
#UseGuards(JwtAuthGuard)
#Get("queues/*")
activate(#Req() req, #Res() res) {
bullUI(req, res)
}
I've got this working via a middleware consumer, so something like this:
import { router } from 'bull-board';
#Module({
imports: [
NestBullModule.forRoot({ redis }),
],
providers: [],
})
export class BullModule {
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(router)
.forRoutes('/admin/queues');
}
}
I'd like to extend the original answer of #JCF, mainly, because it's working and much easier to understand.
I am using not default bull, with #nestjs/queue, but an improved version of BullMQ from anchan828 repo, with NestJS decorators, but I guess in both cases, the result will be the same.
The queue.module file:
#Module({
imports: [
BullModule.forRoot({
options: {
connection: {
host: redisConfig.host,
port: redisConfig.port,
},
},
}),
/** DI all your queues and Redis connection */
BullModule.registerQueue('yourQueueName'),
],
controllers: [],
providers: [],
})
export class QueueModule {
constructor (
#BullQueueInject('yourQueueName')
private readonly queueOne: Queue,
) {
/** Add queues with adapter, one-by-one */
setQueues([new BullMQAdapter(this.queueOne, { readOnlyMode: false })])
}
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(router)
.forRoutes('/admin/queues');
}
}
Then just add it, to parent AppModule, via import, like that:
I am not sure, that Redis connection is needed here, for parent AppModule
#Module({
imports: [
BullModule.forRoot({
options: {
connection: {
host: redisConfig.host,
port: redisConfig.port,
},
},
}),
QueueModule
],
controllers: [],
providers: [],
})
export class AppModule {}
run the main.js, and visit, localhost:8000/admin/queues
I am using nestjs and have just installed the cache-manager module and are trying to cache a response from a service call.
I register the cache module in a sample module (sample.module.ts):
import { CacheInterceptor, CacheModule, Module } from '#nestjs/common';
import { SampleService } from './sample.service';
import { APP_INTERCEPTOR } from '#nestjs/core';
import * as redisStore from 'cache-manager-redis-store';
#Module({
imports: [
CacheModule.register({
ttl: 10,
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
providers: [
SampleService,
{
provide: APP_INTERCEPTOR,
useClass: CacheInterceptor,
}
],
exports: [SampleService],
})
export class SampleModule {}
Then in my service (sample.service.ts):
#Injectable()
export class SampleService {
#UseInterceptors(CacheInterceptor)
#CacheKey('findAll')
async findAll() {
// Make external API call
}
}
Looking at redis I can see that nothing is cached for the service method call. If I use the same approach with a controller, then everything works fine and I can see the cached entry in my redis database. I am thinking that there is no way out of the box to cache individual service method calls in nestjs.
Reading the documentation it seems that I am only able to use this approach for controllers, microservices and websockets, but not ordinary services?
Correct, it is not possible to use the cache the same way for services as for controllers.
This is because the magic happens in the CacheInterceptor and Interceptors can only be used in Controllers.
However, you can inject the cacheManager into your service and use it directly:
export class SampleService {
constructor(#Inject(CACHE_MANAGER) protected readonly cacheManager) {}
findAll() {
const value = await this.cacheManager.get(key)
if (value) {
return value
}
const respone = // ...
this.cacheManager.set(key, response, ttl)
return response
}