NestJS Mock RabbitMQ in Jest - jestjs

I have an AppModule file as follows:
import { Module } from '#nestjs/common'
import { RabbitMQModule } from '#golevelup/nestjs-rabbitmq'
#Module({
imports: [
RabbitMQModule.forRoot(RabbitMQModule, {
exchanges: [
{
name: 'my_rabbit',
type: 'direct',
},
],
uri: process.env.RABBITMQ_URI,
connectionInitOptions: { wait: true },
}),
],
})
export class AppModule {}
I have tried to mock rabbitmq using #golevelup/nestjs-rabbitmq like this:
import { Module } from '#nestjs/common'
import { RabbitMQModule } from '#golevelup/nestjs-rabbitmq'
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
AppModule
],
})
.overrideProvider(AmqpConnection)
.useValue(createMock<AmqpConnection>())
.compile()
})
This is giving me error:
[Nest] 2745 - 24/07/2022, 17:02:54 ERROR [AmqpConnection] Disconnected from RabbitMQ broker (default)
Error: connect ECONNREFUSED 127.0.0.1:5672
If i mock the whole rabbitmq module like:
jest.mock('#golevelup/nestjs-rabbitmq')
I will get errors like:
Nest cannot create the AppModule instance.
The module at index [0] of the AppModule "imports" array is undefined.
Has anyone successfully mocked RabbitMQ? Please assist if possible.

I solve this problem mocking an AmqpConnection like this.
import { AmqpConnection } from "#nestjs-plus/rabbitmq";
import { TestingModule, Test } from "#nestjs/testing";
import { IntegrationQueueService } from "./integration-queue.service";
describe('IntegrationQueueService', () => {
type MockType<T> = {
[P in keyof T]?: jest.Mock<{}>;
};
const mockFactory: () => MockType<AmqpConnection> = jest.fn(() => ({
publish: jest.fn(() => AmqpConnection),
}))
let service: IntegrationQueueService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
IntegrationQueueService,
{
provide: AmqpConnection,
useFactory: mockFactory,
},
],
})
.compile();
service = module.get<IntegrationQueueService> (IntegrationQueueService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
})

Related

How to mock PrismaService for e2e in NestJs?

I'm usign nestjs-prisma in NestJs and I have in the following import for the prisma module in the app.module, so in every service I can use the PrismaService as the library allows it.
app.module.ts
import { Module } from '#nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
import { CategoryModule } from './category/category.module';
#Module({
imports: [PrismaModule.forRoot({ isGlobal: true }), CategoryModule],
})
export class AppModule {}
I want to do a e2e test just for the CategoryModule and need to mock the PrismaService, how can i do that?
You need to mook first of all the PrismaModule and then import in it the PrismaService, here is an example.
category.e2e-spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import * as request from 'supertest';
import { PrismaModule, PrismaService } from 'nestjs-prisma';
import { CategoryModule } from '../src/category/category.module';
describe('CategoryModule (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
// Here you have to mock the category funtions that you'll use
const mockPrismaService = {
provide: PrismaService,
useFactory: () => ({
category: {
findMany: jest.fn(() => [])
},
}),
};
// Here is the creation of the module and the definition for the service
const mockPrismaModule = {
module: PrismaModule,
providers: [mockPrismaService],
global: true,
};
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [CategoryModule, mockPrismaModule], // Here is the import for the mock module
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('(GET) /category', () => {
return request(app.getHttpServer()).get('/category').expect(200);
});
});

NestJS Test No Repository was found

I have a NestJS project configured with TypeORM. The e2e test is the one that is generated by the CLI:
import { Test, TestingModule } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
The error I get is the following:
RepositoryNotFoundError: No repository for "Question" was found. Looks like this entity is not registered in current "default" connection?
at RepositoryNotFoundError.TypeORMError [as constructor] (../src/error/TypeORMError.ts:7:9)
at new RepositoryNotFoundError (../src/error/RepositoryNotFoundError.ts:10:9)
at EntityManager.Object.<anonymous>.EntityManager.getRepository (../src/entity-manager/EntityManager.ts:975:19)
at Connection.Object.<anonymous>.Connection.getRepository (../src/connection/Connection.ts:354:29)
at InstanceWrapper.useFactory [as metatype] (../node_modules/#nestjs/typeorm/dist/typeorm.providers.js:17:30)
at TestingInjector.instantiateClass (../node_modules/#nestjs/core/injector/injector.js:304:55)
at callback (../node_modules/#nestjs/core/injector/injector.js:48:41)
at TestingInjector.resolveConstructorParams (../node_modules/#nestjs/core/injector/injector.js:124:24)
at TestingInjector.loadInstance (../node_modules/#nestjs/core/injector/injector.js:52:9)
AppModule is configured like this:
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '#nestjs/typeorm';
import { QuestionsModule } from './questions/questions.module';
import { Connection, getConnectionOptions } from 'typeorm';
#Module({
imports: [
// docker run --name postgres_questions_answers -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=dev_questions_answers -p 5432:5432 -d postgres
TypeOrmModule.forRootAsync({
useFactory: async () =>
Object.assign(await getConnectionOptions(), {
// FIXME: This is done so Jest e2e doesnt raise Cannot create a new connection named "default", because connection with such name already exist and it now has an active connection session.
keepConnectionAlive: process.env.NODE_ENV === 'test',
}),
}),
QuestionsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
constructor(private connection: Connection) {}
}
And the QuestionsModule like this:
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { QuestionsService } from './questions.service';
import { QuestionsController } from './questions.controller';
import { Question } from '../entities/question.entity';
#Module({
imports: [TypeOrmModule.forFeature([Question])],
controllers: [QuestionsController],
providers: [QuestionsService],
})
export class QuestionsModule {}
Project runs fine in development and production mode, so I am wondering what am I doing wrong here?

Mock Injected Twilio Service in Unit Testing Nest.js

I have a problem with overriding provider/setup module testing in nest.js application for testing.
Module file: smsModule.ts:
import { TwilioService } from './twilio/twilio.service';
import { DynamicModule, Module } from '#nestjs/common';
import { TwilioConfig, SMS_TWILIO_CONFIG } from './twilio.config';
import { TwilioClientCustom } from './twilio/twilio-client-custom';
#Module({
imports: [],
providers: [TwilioService],
})
export class SmsModule {
static register(options: TwilioConfig): DynamicModule {
return {
module: SmsModule,
imports: [HttpModule],
providers: [
{
provide: SMS_TWILIO_CONFIG,
useValue: options,
},
TwilioService,
TwilioClientCustom,
],
exports: [TwilioService],
};
}
}
Twilio client, config files:
//client
import { TwilioConfig, SMS_TWILIO_CONFIG } from '../twilio.config';
import { Twilio } from 'twilio';
import { Inject, Injectable } from '#nestjs/common';
#Injectable()
export class TwilioClientCustom extends Twilio {
constructor(#Inject(SMS_TWILIO_CONFIG) twilioConfig: TwilioConfig) {
super(twilioConfig.accountSid, twilioConfig.authToken);
}
}
//config
import { IsString, IsNotEmpty, NotContains, IsOptional, IsArray } from 'class-validator';
const INFO = 'Must be ....';
export class TwilioConfig {
#IsString()
#IsNotEmpty()
#NotContains('OVERRIDE_WITH_', { message: INFO })
accountSid: string;
#IsString()
#IsNotEmpty()
authToken: string;
#IsArray()
#IsOptional()
#IsNotEmpty('OVERRIDE_WITH_', { message: INFO })
serviceSid: string;
}
export const SMS_TWILIO_CONFIG = 'smsTwilioConfig';
Twilio service file: twilio.service.tst:
import { HttpService } from '#nestjs/axios';
import { TwilioConfig, SMS_TWILIO_CONFIG } from '../twilio.config';
import { SendSmsTwilioService } from './../sendsms.service';
import { Inject, Injectable } from '#nestjs/common';
import { TwilioClientCustom } from './twilio-client-custom';
#Injectable()
export class TwilioService implements SendSmsTwilioService {
constructor(
#Inject(SMS_TWILIO_CONFIG) private readonly config: TwilioConfig,
private readonly client: TwilioClientCustom,
private readonly httpService: HttpService
) {}
async sendSMS(to: string, from: string, body: string): Promise<string> {
......
return this.client.messages
.create({
to, //Recipinet's number
from, //Twilio number
body, //Messages to Recipient
})
.then((message) => message.sid)
.catch(() => {
throw new Error('TWILIO accountSid or authToken not valid');
});
}
I would like to test my service:
test file:
import { Test, TestingModule } from '#nestjs/testing';
//import { TWILIO_CONFIG_SPEC } from './test.config';
import { TwilioClientCustom } from '../src/twilio/twilio-client-custom';
import { HttpService } from '#nestjs/axios';
import { TwilioConfig } from './../src/twilio.config';
import { TwilioService } from './../src/twilio/twilio.service';
import nock from 'nock';
describe('TwilioService', () => {
let service: TwilioService;
let client: TwilioClientCustom;
let httpService: HttpService;
afterEach(() => {
nock.cleanAll();
});
//const smsServiceMock = {};
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [
TwilioService,
{
provide: HttpService,
useValue: {
method1: jest.fn(),
method2: jest.fn(),
method3: jest.fn(),
},
},
TwilioService,
],
imports: [
NestConfigModule.forRoot({
config: TwilioConfig,
} as Record<string, unknown>),
],
}).compile();
//getting service module from main module
httpService = moduleRef.get<HttpService>(HttpService);
client = moduleRef.get<TwilioClientCustom>(TwilioClientCustom);
service = moduleRef.get<TwilioService>(TwilioService);
});
//check service is avaible
it('Should be defined', () => {
expect(client).toBeDefined();
expect(service).toBeDefined();
expect(httpService).toBeDefined();
});
And after running test I get following errors:
Nest can't resolve dependencies of the TwilioService (?, TwilioClientCustom, HttpService). Please make sure that the argument smsTwilioConfig at index [0] is available in the RootTestModule context.
Potential solutions:
- If smsTwilioConfig is a provider, is it part of the current RootTestModule?
- If smsTwilioConfig is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing smsTwilioConfig */ ]
})
How can I solve this problem ?
smsTwilioConfig is registered with Nest's IOC via SmsModule.register(opts).
However, it seems you're attempting to test TwilioService directly with createTestingModule. Which is fine, but it does mean you need include the config with a provider or import in your test.
My guess is that you thought NestConfigModule... would do that, but that's not setting the config at the right level.
I would think the following is the right direction
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [
TwilioService,
{
provide: HttpService,
useValue: {
method1: jest.fn(),
method2: jest.fn(),
method3: jest.fn(),
},
},
{
// added this
provide: SMS_TWILIO_CONFIG,
useValue: testConfig
},
],
imports: [
// removed NestConfigModule
],
}).compile();

How to test Models (Mongoose) in a service (NestJS) with jest

I have a backend done with NestJS. In my service I inject two Mongoose Models. I use Jest to test the service.
Models are declared as is and injected into the module:
quizes.providers.ts
import { Connection } from 'mongoose';
import { QuizSchema } from './schemas/quiz.schema';
export const quizesProviders = [
{
provide: 'CLASS_MODEL',
useFactory: (connection: Connection) => connection.model('Quiz', QuizSchema),
inject: ['DATABASE_CONNECTION'],
},
];
users.providers.ts
import { Connection } from 'mongoose';
import { UserSchema } from './schemas/user.schema';
export const usersProviders = [
{
provide: 'USER_MODEL',
useFactory: (connection: Connection) => connection.model('User', UserSchema),
inject: ['DATABASE_CONNECTION'],
},
];
Example of module:
quizes.module.ts
import { Module } from '#nestjs/common';
import { QuizesController } from './quizes.controller';
import { QuizesService } from './quizes.service';
import { quizesProviders } from './quizes.providers';
import { usersProviders } from '../auth/users.providers';
import { DatabaseModule } from 'src/database.module';
import { AuthModule } from 'src/auth/auth.module';
#Module({
imports: [DatabaseModule, AuthModule],
controllers: [QuizesController],
providers: [QuizesService,
...quizesProviders, ...usersProviders]
})
export class QuizesModule {}
Then in my service, I inject models:
quizes.service.ts
#Injectable()
export class QuizesService {
constructor(
#Inject('CLASS_MODEL')
private classModel: Model<Quiz>,
#Inject('USER_MODEL')
private userModel: Model<User>
) {}
In my quizes.spec.ts (jest) I began to do things like that. It compiles but doesn't work:
import { Test } from '#nestjs/testing';
import * as mongoose from 'mongoose';
import { User } from 'src/auth/user.interface';
import { Quiz } from './quiz.interface';
import { databaseProviders } from '../database.providers';
const USER_MODEL:mongoose.Model<User> = mongoose.model('User', UserSchema);
const CLASS_MODEL:mongoose.Model<Quiz> = mongoose.model('Quiz', QuizSchema);
const mockingQuizModel = () => {
find: jest.fn()
}
const mockingUserModel = () => {
find: jest.fn()
}
const mockUser = {
username: 'Test user'
}
describe('QuizesService', () => {
let quizesService;
let userModel , classModel;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [QuizesService, ...usersProviders, ...quizesProviders,...databaseProviders,
{provide: USER_MODEL, useFactory: mockingUserModel},
{provide: CLASS_MODEL, useFactory: mockingQuizModel},
],
}).compile();
quizesService = await module.get<QuizesService>(QuizesService);
classModel = await module.get<mongoose.Model<Quiz>>(CLASS_MODEL)
userModel = await module.get<mongoose.Model<User>>(USER_MODEL)
})
describe('getAllQuizes', ()=> {
it('get all quizes', () => {
expect(userModel.find).not.toHaveBeenCalled();
})
})
})
userModel is undefined and the test does not exit.
Use the getModelToken function as defined in NestJS official: https://docs.nestjs.com/v6/
Techniques -> Mongo (Scroll down to Testing section)
Then your code should look a bit like this:
import { getModelToken } from '#nestjs/mongoose';
const mockRepository = {
find() {
return {};
}
};
const module = await Test.createTestingModule({
providers: [ ...,
{provide: getModelToken('CLASS_MODEL'), useValue: mockRepository,},
{provide: getModelToken('USER_MODEL'), useValue: mockRepository,},
],
...
Fixed
You should not use await for module.get
quizesService = module.get<QuizesService>(QuizesService);
clientClassModel = module.get(getModelToken('CLASS_MODEL'))
clientUserModel = module.get(getModelToken('USER_MODEL'))
The setup of the test suite was ok but not the test
I test the service getAllQuizes method
Here is the service
#Injectable()
export class QuizesService {
constructor(
#InjectModel('CLASS_MODEL')
private classModel: Model<Quiz>,
#InjectModel('USER_MODEL')
private userModel: Model<User>
) {}
async getAllQuizes(user: User) : Promise<Quiz[]> {
// console.log(user);
let userId;
try {
const userEntity = await this.userModel.find({username: user.username}).exec();
userId = userEntity[0]._id;
} catch (error) {
throw new NotFoundException('user not found');
}
return await this.classModel.find({user: userId}).exec();
}
Here is the test
it('get all quizes', async () => {
clientUserModel.find.mockResolvedValue('user1');
clientClassModel.find.mockResolvedValue([{title: 'test', description: 'test'}])
expect(clientUserModel.find).not.toHaveBeenCalled();
expect(clientClassModel.find).not.toHaveBeenCalled();
const result = quizesService.getAllQuizes(mockUser);
expect(clientUserModel.find).toHaveBeenCalled();
expect(clientClassModel.find).toHaveBeenCalled();
expect(result).toEqual([{title: 'test', description: 'test'}]);
})
My test is false because the assertion expect(clientClassModel.find).toHaveBeenCalled() is false
Whereas in my service I have a first call on find method of the user model, and a second call on the find method of the class model
Finally tests pass
describe("getAllQuizes", () => {
it("get all quizes, user not found", async () => {
clientUserModel.find.mockRejectedValue("user not found");
clientClassModel.find.mockResolvedValue([
{ title: "test", description: "test" },
]);
expect(clientUserModel.find).not.toHaveBeenCalled();
expect(clientClassModel.find).not.toHaveBeenCalled();
const result = quizesService.getAllQuizes(mockUser).catch((err) => {
expect(err.message).toEqual("user not found");
});
expect(clientUserModel.find).toHaveBeenCalled();
});
it("get all quizes, find quizzes", async () => {
clientUserModel.find.mockReturnValue({
_id: "1234",
username: "Test user",
});
clientClassModel.find.mockResolvedValue([
{ title: "test", description: "test" },
]);
expect(clientUserModel.find).not.toHaveBeenCalled();
expect(clientClassModel.find).not.toHaveBeenCalled();
const result = quizesService.getAllQuizes(mockUser).then((state) => {
expect(clientUserModel.find).toHaveBeenCalled();
expect(clientClassModel.find).toHaveBeenCalled();
expect(state).toEqual([{ title: "test", description: "test" }]);
});
//
});
});

How to unit test Controller and mock #InjectModel in the Service constructor

I am getting issues while unit testing my controller and getting an error "Nest can't resolve dependencies of my service".
For maximum coverage I wanted to unit test controller and respective services and would like to mock external dependencies like mongoose connection. For the same I already tried suggestions mentioned in the below link but didn't find any luck with that:
https://github.com/nestjs/nest/issues/194#issuecomment-342219043
Please find my code below:
export const deviceProviders = [
{
provide: 'devices',
useFactory: (connection: Connection) => connection.model('devices', DeviceSchema),
inject: ['DbConnectionToken'],
},
];
export class DeviceService extends BaseService {
constructor(#InjectModel('devices') private readonly _deviceModel: Model<Device>) {
super();
}
async getDevices(group): Promise<any> {
try {
return await this._deviceModel.find({ Group: group }).exec();
} catch (error) {
return Promise.reject(error);
}
}
}
#Controller()
export class DeviceController {
constructor(private readonly deviceService: DeviceService) {
}
#Get(':group')
async getDevices(#Res() response, #Param('group') group): Promise<any> {
try {
const result = await this.deviceService.getDevices(group);
return response.send(result);
}
catch (err) {
return response.status(422).send(err);
}
}
}
#Module({
imports: [MongooseModule.forFeature([{ name: 'devices', schema: DeviceSchema }])],
controllers: [DeviceController],
components: [DeviceService, ...deviceProviders],
})
export class DeviceModule { }
Unit test:
describe('DeviceController', () => {
let deviceController: DeviceController;
let deviceService: DeviceService;
const response = {
send: (body?: any) => { },
status: (code: number) => response,
};
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [DeviceController],
components: [DeviceService, ...deviceProviders],
}).compile();
deviceService = module.get<DeviceService>(DeviceService);
deviceController = module.get<DeviceController>(DeviceController);
});
describe('getDevices()', () => {
it('should return an array of devices', async () => {
const result = [{
Group: 'group_abc',
DeviceId: 'device_abc',
},
{
Group: 'group_xyz',
DeviceId: 'device_xyz',
}];
jest.spyOn(deviceService, 'getDevices').mockImplementation(() => result);
expect(await deviceController.getDevices(response, null)).toBe(result);
});
});
});
When I am running my test case above, I am getting two errors:
Nest can't resolve dependencies of the DeviceService (?). Please make sure that the argument at index [0] is available in the current context.
Cannot spyOn on a primitive value; undefined given
Example code:
import { Test } from '#nestjs/testing';
import { getModelToken } from '#nestjs/mongoose';
describe('auth', () => {
let deviceController: DeviceController;
let deviceService: DeviceService;
const mockRepository = {
find() {
return {};
}
};
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [DeviceModule]
})
.overrideProvider(getModelToken('Auth'))
.useValue(mockRepository)
.compile();
deviceService = module.get<DeviceService>(DeviceService);
});
// ...
});
You are not injecting the correct token here. Instead of a plain string you have to use the function getModelToken.
import { getModelToken } from '#nestjs/mongoose';
// ...
{ provide: getModelToken('devices'), useFactory: myFactory },
Here is the solution provided by this repo. See the mongo-sample. I am testing my API using the #injectModel and another service. Here's the snippet:
import { CategoriesService } from './../categories/categories.service';
import { getModelToken } from '#nestjs/mongoose';
import { Test, TestingModule } from '#nestjs/testing';
import { ProductsService } from './products.service';
describe('ProductsService', () => {
let service: ProductsService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
// getModelToken to mock the MongoDB connection
providers: [
ProductsService,
CategoriesService,
{
provide: getModelToken('Product'),
useValue: {
find: jest.fn(),
findOne: jest.fn(),
findByIdAndUpdate: jest.fn(),
findByIdAndRemove: jest.fn(),
save: jest.fn(),
},
},
{
provide: getModelToken('Category'),
useValue: {
find: jest.fn(),
findOne: jest.fn(),
findByIdAndUpdate: jest.fn(),
findByIdAndRemove: jest.fn(),
save: jest.fn(),
},
},
],
}).compile();
service = module.get<ProductsService>(ProductsService);
});
// your test case
});

Resources