Nestjs mocking service constructor with Jest - javascript

I have created following service to use twilio send login code sms to users:
sms.service.ts
import { Injectable, Logger } from '#nestjs/common';
import * as twilio from 'twilio';
Injectable()
export class SmsService {
private twilio: twilio.Twilio;
constructor() {
this.twilio = this.getTwilio();
}
async sendLoginCode(phoneNumber: string, code: string): Promise<any> {
const smsClient = this.twilio;
const params = {
body: 'Login code: ' + code,
from: process.env.TWILIO_SENDER_NUMBER,
to: phoneNumber
};
smsClient.messages.create(params).then(message => {
return message;
});
}
getTwilio() {
return twilio(process.env.TWILIO_SID, process.env.TWILIO_SECRET);
}
}
sms.service.spec.js that contains my test
import { Test, TestingModule } from '#nestjs/testing';
import { SmsService } from './sms.service';
import { Logger } from '#nestjs/common';
describe('SmsService', () => {
let service: SmsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SmsService]
}).compile();
service = module.get<SmsService>(SmsService);
});
describe('sendLoginCode', () => {
it('sends login code', async () => {
const mockMessage = {
test: "test"
}
jest.mock('twilio')
const twilio = require('twilio');
twilio.messages = {
create: jest.fn().mockImplementation(() => Promise.resolve(mockMessage))
}
expect(await service.sendLoginCode("4389253", "123456")).toBe(mockMessage);
});
});
});
How can I use jest create mock of the SmsService constructor so that it's twilio variable gets set to mocked version of it I create in service.spec.js?

You should inject your dependency instead of using it directly, then you can mock it in your test:
Create a custom provider
#Module({
providers: [
{
provide: 'Twillio',
useFactory: async (configService: ConfigService) =>
twilio(configService.TWILIO_SID, configService.TWILIO_SECRET),
inject: [ConfigService],
},
]
Inject it in your service
constructor(#Inject('Twillio') twillio: twilio.Twilio) {}
Mock it in your test
const module: TestingModule = await Test.createTestingModule({
providers: [
SmsService,
{ provide: 'Twillio', useFactory: twillioMockFactory },
],
}).compile();
See this thread on how to create mocks.

Related

Can't define entity in controller's unit test

I want to write a unit test for a controller in NestJS which uses the service. Service uses an entity and typeorm to getting data from postgres.
controller.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { EmissionsWeldingController } from '../EmissionsWelding.controller';
describe('EmissionsWeldingController', () => {
let controller: EmissionsWeldingController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [EmissionsWeldingController],
}).compile();
controller = module.get<EmissionsWeldingController>(EmissionsWeldingController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
It failed with an error: Nest can't resolve dependencies of the EmissionsWeldingController (?). Please make sure that the argument EmissionsWeldingService at index [0] is available in the RootTestModule context.
When I define my service and entity
import { Test, TestingModule } from '#nestjs/testing';
import { TypeOrmModule } from '#nestjs/typeorm';
import { EmissionsWeldingController } from '../EmissionsWelding.controller';
import { Mark, Substance, WorkType, WorkSpecification, InputField, SummaryValue } from '../EmissionsWelding.entity';
import { EmissionsWeldingService } from '../EmissionsWelding.service';
describe('EmissionsWeldingController', () => {
let controller: EmissionsWeldingController;
let service: EmissionsWeldingService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [TypeOrmModule.forFeature([Mark, Substance, WorkType, WorkSpecification, InputField, SummaryValue])],
controllers: [EmissionsWeldingController],
providers: [EmissionsWeldingService],
}).compile();
controller = module.get<EmissionsWeldingController>(EmissionsWeldingController);
service = module.get<EmissionsWeldingService>(EmissionsWeldingService);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
It's failing with an error: Nest can't resolve dependencies of the MarkRepository (?). Please make sure that the argument Connection at index [0] is available in the TypeOrmModule context.
How should I define providers and entities without getting above error?
service.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { GetResultDto } from './dto/GetResult.dto';
import { InputField, Mark, Substance, SummaryValue, WorkSpecification, WorkType } from './EmissionsWelding.entity';
#Injectable()
export class EmissionsWeldingService {
constructor(
#InjectRepository(Mark)
private markRepository: Repository<Mark>,
#InjectRepository(Substance)
private substanceRepository: Repository<Substance>,
#InjectRepository(WorkSpecification)
private workSpecificationRepository: Repository<WorkSpecification>,
#InjectRepository(WorkType)
private workTypeRepository: Repository<WorkType>,
#InjectRepository(InputField)
private inputFieldRepository: Repository<InputField>,
#InjectRepository(SummaryValue)
private summaryValueRepository: Repository<SummaryValue>,
) {}
async getMarks(work_type_id: number, work_specification_id: number): Promise<Mark[]> {
return await this.markRepository.find({ where: { work_type_id, work_specification_id } });
}
async getSubstances(): Promise<Substance[]> {
return await this.substanceRepository.find();
}
async getWorkSpecifications(): Promise<WorkSpecification[]> {
return await this.workSpecificationRepository.find();
}
async getWorkTypes(): Promise<WorkType[]> {
return await this.workTypeRepository.find();
}
async getInputFields(): Promise<WorkType[]> {
return await this.inputFieldRepository.find();
}
async getSummaryValues(mark_id: number, substance_id: number): Promise<SummaryValue[]> {
return await this.summaryValueRepository.find({ where: { mark_id, substance_id } });
}
async getResult(body: GetResultDto): Promise<GetResultDto[]> {
const result = [];
const { mark_id, input_fields_values } = body;
const substances = await this.getSubstances();
let currentSummaryValue;
for (let i = 0; i <= substances.length - 1; i++) {
currentSummaryValue = await this.getSummaryValues(mark_id, i + 1);
result.push({
code: substances[i].code,
name: substances[i].name,
year:
((input_fields_values.year * currentSummaryValue[0].value) / 10 ** 6) *
(1 - (input_fields_values.clean ? input_fields_values.clean : 0)),
second:
((currentSummaryValue[0].value * input_fields_values.hour) / 3600) *
(1 - (input_fields_values.clean ? input_fields_values.clean : 0)),
});
}
return result;
}
}
controller.ts
import { Body, Controller, Get, Post, Query } from '#nestjs/common';
import { ApiTags } from '#nestjs/swagger';
import { GetResultDto } from './dto/GetResult.dto';
import { EmissionsWeldingService } from './EmissionsWelding.service';
#ApiTags('EmissionsWelding')
#Controller('EmissionsWelding')
export class EmissionsWeldingController {
constructor(private emissionsWeldingService: EmissionsWeldingService) {}
#Get('getMarks')
getMarks(#Query('work_type_id') work_type_id: number, #Query('work_specification_id') work_specification_id: number) {
return this.emissionsWeldingService.getMarks(work_type_id, work_specification_id);
}
#Get('getSubstances')
getSubstances() {
return this.emissionsWeldingService.getSubstances();
}
#Get('getWorkSpecifications')
getWorkSpecifications() {
return this.emissionsWeldingService.getWorkSpecifications();
}
#Get('getWorkTypes')
getWorkTypes() {
return this.emissionsWeldingService.getWorkTypes();
}
#Get('getInputFields')
getInputFields() {
return this.emissionsWeldingService.getInputFields();
}
#Post('getResult')
getResult(#Body() body: GetResultDto) {
return this.emissionsWeldingService.getResult(body);
}
}
You need to provide a mock for every repository that is injected into your service.
This answer provides the necessary detail: https://stackoverflow.com/a/55366343/588734
describe('EmissionsWeldingController', () => {
let controller: EmissionsWeldingController;
let service: EmissionsWeldingService;
let markRepositoryMock: MockType<Repository<Mark>>;
// repeat for every repository let __RepositoryMock: MockType<Repository<__>>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [EmissionsWeldingController],
providers: [
EmissionsWeldingService,
{
provide: getRepositoryToken(Mark),
useValue: jest.fn(() => ({
findOne: jest.fn(entity => entity),
// provide a mock for each function you are calling
}))
},
// repeat for every mock
],
}).compile();
controller = module.get<EmissionsWeldingController>(EmissionsWeldingController);
service = module.get<EmissionsWeldingService>(EmissionsWeldingService);
markRepositoryMock = module.get(getRepositoryToken(Mark));
// repeat for every mock repository
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

How to read config value using ConfigModule inside of a provider?

I would like to add a config module to my nestjs application and use some config in my database module.
For example I would like to get the host value of my configuration:
In a class I can do const dbHost = this.configService.get<string>('database.host');, but in my code I need the value inside of a provider. How do I get config values inside of a provider?
app.module.ts
#Module({
imports: [
DatabaseModule,
ConfigModule.forRoot({
load: [configuration],
}),
],
})
export class AppModule {}
database.module.ts
import { Module, Inject } from '#nestjs/common'
import { MongoClient, MongoClientOptions, Db, Logger } from 'mongodb'
#Module({
providers: [
{
provide: 'DATABASE_CLIENT',
useFactory: () => ({ client: null })
},
{
provide: 'DATABASE_CONNECTION',
inject: ['DATABASE_CLIENT'],
useFactory: async (dbClient): Promise<Db> => {
Logger.setLevel('debug')
// How do I get the host value of my config?
const mongo: string = 'mongodb://127.0.0.1:27017'
const database: string = 'data'
const options: MongoClientOptions = {}
const client = new MongoClient(mongo, options)
await client.connect()
const db = client.db(database)
return db
}
}
],
exports: ['DATABASE_CONNECTION', 'DATABASE_CLIENT'],
import: [ConfigModule]
})
export class DatabaseModule {
constructor(#Inject('DATABASE_CLIENT') private dbClient) {}
onApplicationShutdown(signal: string) {
if (signal) console.log(signal + ' signal recieved')
}
}
In your inject add ConfigService, then it'll be the second value of useFactory's parameters.
#Module({
providers: [
{
provide: 'DATABASE_CLIENT',
useFactory: () => ({ client: null })
},
{
provide: 'DATABASE_CONNECTION',
inject: ['DATABASE_CLIENT', ConfigService],
useFactory: async (dbClient, config: ConfigService): Promise<Db> => {
Logger.setLevel('debug')
const dbHost = config.get('database.host')
const mongo: string = 'mongodb://127.0.0.1:27017'
const database: string = 'data'
const options: MongoClientOptions = {}
const client = new MongoClient(mongo, options)
await client.connect()
const db = client.db(database)
return db
}
}
],
exports: ['DATABASE_CONNECTION', 'DATABASE_CLIENT'],
import: [ConfigModule]
})
export class DatabaseModule {
constructor(#Inject('DATABASE_CLIENT') private dbClient) {}
onApplicationShutdown(signal: string) {
if (signal) console.log(signal + ' signal recieved')
}
}

Mocking HTTP interceptors in Nest JS

I have a core module that provides an authentication interceptor in Nest js.
Here is the core module.
#Module({
imports: [
providers: [{
provide: APP_INTERCEPTOR,
useClass: LoggerInterceptor
}
]
})
export class ConfModule {
}
This conf module is been imported by the app module.
#Module({
imports: [
ConfModule
]
})
export class AppModule {
}
Here is my LoggerInterceptor class
#Injectable()
export class LoggerInterceptor implements NestInterceptor {
constructor(private readonly reflector: Reflector, private readonly connection: Connection) {
}
async intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// tslint:disable-next-line:no-console
console.log(context.switchToHttp().getRequest().body);
await this.connection.createEntityManager().save(context.switchToHttp().getRequest().body);
return next.handle();
}
}
I am currently writing an E2E test and will want to override the logger interceptor. Here is my MockLoggerInterceptor
export class MockLoggerInterceptor extends LoggerInterceptor {
reflex: Reflector;
conn: Connection;
constructor(reflector: Reflector, connection: Connection) {
super(reflector, connection);
this.reflex = reflector;
this.conn = connection;
}
// #ts-ignore
async intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// tslint:disable-next-line:no-console
console.log(context.switchToHttp().getRequest().body);
await this.conn.createEntityManager().save(context.switchToHttp().getRequest().body);
return next.handle();
}
}
Here is my test Suite
describe('PermissionController', () => {
let applicationContext: INestApplication;
let connection: Connection;
let testUtils: TestUtils;
let appHeader: App;
beforeAll(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).overrideInterceptor(LoggerInterceptor).useClass(MockLoggerInterceptor).compile();
applicationContext = moduleRef.createNestApplication();
connection = getConnection();
await applicationContext.init();
testUtils = new TestUtils(connection);
appHeader = await testUtils.getAuthorisedApp();
});
it('Test of permission can be created', async () => {
await request(applicationContext.getHttpServer())
.post('/permissions')
.set({
'X-APP-CODE': appHeader.code,
'X-APP-TOKEN': appHeader.token,
'Authorisation': appHeader.token
})
.send(
{
permissionName: 'newPermission'
}
).expect(201);
});
afterAll(async () => {
await connection.close();
await applicationContext.close();
});
});
My test still uses the conf module logger instead of the test logger. Apparently there are other use cases but this is the best I can give.
You have to use overrideInterceptor instead of overrideProvider, as an interceptor is not provided in the providers array of a module:
.overrideInterceptor(LoggerInterceptor).useClass(MockLoggerInterceptor)

How to mock third party library using jest

I am developing a node.js application using nestjs
I have a class called LoggerService as below
export class LoggerService {
private logger: Rollbar;
constructor() {
this.logger = this.setupLogger();
}
critical(...args: Array<string | Error | object | Date | any[]>) {
this.logger.error(...args);
}
private setupLogger(): Rollbar {
if (this.logger == null) {
this.logger = new Rollbar({
accessToken: 'token',
environment: 'dev',
captureUncaught: true,
captureUnhandledRejections: true,
});
}
return this.logger;
}
Now I am writing unit test for this class using jest as below.
describe('LoggerService.log', () => {
let service: LoggerService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LoggerService],
}).compile();
service = module.get<LoggerService>(LoggerService);
});
it('critical', () => {
service.critical('error','message');
expect(???).toHaveBeenCalledWith('error', 'message')
})
);
My question is how to check(expect) if logger.error is called, or how to mock Rollbar in this class.
1) Provide your external dependency/library as an injectable token in your module
#Module({
providers: [
{
provide: 'Rollbar',
useFactory: async (configService: ConfigService) => new Rollbar({
accessToken: configService.accessToken,
environment: configService.environment,
captureUncaught: true,
captureUnhandledRejections: true,
}),
inject: [ConfigService],
},
]
2) Inject it in your LoggerService instead of creating it
export class LoggerService {
constructor(#Inject('Rollbar') private logger: Rollbar) {
}
3) Now you can mock your dependency in your test
const module: TestingModule = await Test.createTestingModule({
providers: [
LoggerService,
{ provide: 'Rollbar', useFactory: rollbarMockFactory },
],
}).compile();

Inject TypeORM repository into NestJS service for mock data testing

There's a longish discussion about how to do this in this issue.
I've experimented with a number of the proposed solutions but I'm not having much luck.
Could anyone provide a concrete example of how to test a service with an injected repository and mock data?
Let's assume we have a very simple service that finds a user entity by id:
export class UserService {
constructor(#InjectRepository(UserEntity) private userRepository: Repository<UserEntity>) {
}
async findUser(userId: string): Promise<UserEntity> {
return this.userRepository.findOne(userId);
}
}
Then you can mock the UserRepository with the following mock factory (add more methods as needed):
// #ts-ignore
export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
findOne: jest.fn(entity => entity),
// ...
}));
Using a factory ensures that a new mock is used for every test.
describe('UserService', () => {
let service: UserService;
let repositoryMock: MockType<Repository<UserEntity>>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
// Provide your mock instead of the actual repository
{ provide: getRepositoryToken(UserEntity), useFactory: repositoryMockFactory },
],
}).compile();
service = module.get<UserService>(UserService);
repositoryMock = module.get(getRepositoryToken(UserEntity));
});
it('should find a user', async () => {
const user = {name: 'Alni', id: '123'};
// Now you can control the return value of your mock's methods
repositoryMock.findOne.mockReturnValue(user);
expect(service.findUser(user.id)).toEqual(user);
// And make assertions on how often and with what params your mock's methods are called
expect(repositoryMock.findOne).toHaveBeenCalledWith(user.id);
});
});
For type safety and comfort you can use the following typing for your (partial) mocks (far from perfect, there might be a better solution when jest itself starts using typescript in the upcoming major releases):
export type MockType<T> = {
[P in keyof T]?: jest.Mock<{}>;
};
My solution uses sqlite memory database where I insert all the needed data and create schema before every test run. So each test counts with the same set of data and you do not have to mock any TypeORM methods:
import { Test, TestingModule } from "#nestjs/testing";
import { CompanyInfo } from '../../src/company-info/company-info.entity';
import { CompanyInfoService } from "../../src/company-info/company-info.service";
import { Repository, createConnection, getConnection, getRepository } from "typeorm";
import { getRepositoryToken } from "#nestjs/typeorm";
describe('CompanyInfoService', () => {
let service: CompanyInfoService;
let repository: Repository<CompanyInfo>;
let testingModule: TestingModule;
const testConnectionName = 'testConnection';
beforeEach(async () => {
testingModule = await Test.createTestingModule({
providers: [
CompanyInfoService,
{
provide: getRepositoryToken(CompanyInfo),
useClass: Repository,
},
],
}).compile();
let connection = await createConnection({
type: "sqlite",
database: ":memory:",
dropSchema: true,
entities: [CompanyInfo],
synchronize: true,
logging: false,
name: testConnectionName
});
repository = getRepository(CompanyInfo, testConnectionName);
service = new CompanyInfoService(repository);
return connection;
});
afterEach(async () => {
await getConnection(testConnectionName).close()
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return company info for findOne', async () => {
// prepare data, insert them to be tested
const companyInfoData: CompanyInfo = {
id: 1,
};
await repository.insert(companyInfoData);
// test data retrieval itself
expect(await service.findOne()).toEqual(companyInfoData);
});
});
I got inspired here: https://gist.github.com/Ciantic/be6a8b8ca27ee15e2223f642b5e01549
Similar to best practices in other frameworks you can use a test DB instead of a mock.
describe('EmployeesService', () => {
let employeesService: EmployeesService;
let moduleRef: TestingModule;
beforeEach(async () => {
moduleRef = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
url: 'postgres://postgres:#db:5432/test', // read this from env
autoLoadEntities: true,
synchronize: true,
dropSchema: true,
}),
],
providers: [EmployeesService],
}).compile();
employeesService = moduleRef.get<EmployeesService>(EmployeesService);
});
afterEach(async () => {
await moduleRef.close();
});
describe('findOne', () => {
it('returns empty array', async () => {
expect(await employeesService.findAll()).toStrictEqual([]);
});
});
});
Real-life example in resolver specs in: https://github.com/thisismydesign/nestjs-starter
Last tested with typeorm#0.3.7 and #nestjs/typeorm#9.0.0.
I also found that this worked for me:
export const mockRepository = jest.fn(() => ({
metadata: {
columns: [],
relations: [],
},
}));
and
const module: TestingModule = await Test.createTestingModule({
providers: [{ provide: getRepositoryToken(Entity), useClass: mockRepository }],
}).compile();
Starting with the above ideas and to help with mocking any class, we came out with this MockFactory:
export type MockType<T> = {
[P in keyof T]?: jest.Mock<unknown>;
};
export class MockFactory {
static getMock<T>(type: new (...args: any[]) => T, includes?: string[]): MockType<T> {
const mock: MockType<T> = {};
Object.getOwnPropertyNames(type.prototype)
.filter((key: string) => key !== 'constructor' && (!includes || includes.includes(key)))
.map((key: string) => {
mock[key] = jest.fn();
});
return mock;
}
}
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getRepositoryToken(MyCustomRepository),
useValue: MockFactory.getMock(MyCustomRepository)
}
]
}).compile();
First of all I'm new to Ts/Js/Node.
Here is my example code : it lets you use NEST's injection system with a custom Connection during tests.
In this manner service/controller objects are not created by hand but wired by the TestingModule:
import { Test } from '#nestjs/testing';
import { getRepositoryToken } from '#nestjs/typeorm';
import {
Repository,
createConnection,
getConnection,
getRepository,
Connection,
} from 'typeorm';
import { Order } from './order';
import { OrdersService } from './orders.service';
describe('Test Orders', () => {
let repository: Repository<Order>;
let service: OrdersService;
let connection: Connection;
beforeEach(async () => {
connection = await createConnection({
type: 'sqlite',
database: './test.db',
dropSchema: true,
entities: [Order],
synchronize: true,
logging: true,
});
repository = getRepository(Order);
const testingModule = await Test.createTestingModule({
providers: [
OrdersService,
{
provide: getRepositoryToken(Order, connection),
useFactory: () => {
return repository;
},
},
],
}).compile();
console.log('Getting Service from NEST');
service = testingModule.get<OrdersService>(OrdersService);
return connection;
});
afterEach(async () => {
await getConnection().close();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('CRUD Order Test', async () => {
const order = new Order();
order.currency = 'EURO';
order.unitPrice = 12.0;
order.issueDate = new Date();
const inserted = await service.create(order);
console.log('Inserted order ', inserted.id); // id is the #PrimaryGeneratedColumn() key
let allOrders = await service.findAll();
expect(allOrders.length).toBe(1);
await service.delete(inserted.id);
allOrders = await service.findAll();
expect(allOrders.length).toBe(0);
});
});
Something similar to the suggested MockTypes defined in the previous answer is the TypedMockType
type ArgsType<T> = T extends (...args: infer A) => unknown ? A : never;
export type TypedMockType<T> = {
// eslint-disable-next-line #typescript-eslint/no-explicit-any
[P in keyof T]: T[P] extends (...args: any) => unknown
? jest.Mock<ReturnType<T[P]>, ArgsType<T[P]>>
: never;
};
This is a utility type that can be used the same as MockType, but the difference is that your payloads of the original method signature will be the same.

Categories