Mocking HTTP interceptors in Nest JS - javascript

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)

Related

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')
}
}

Nestjs mocking service constructor with Jest

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.

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.

How to overrideProvider for NestJS/TypeORM

api.controller.ts
#Controller('api')
export class ApiController {
constructor() {}
#Post()
#Transaction()
async root(#Req() req: Request, #Res() res: Response, #TransactionManager() manager: EntityManager): Promise<void> {
res.send(/* anything */);
}
}
api.e2e-spec.ts
describe('API (e2e)', () => {
let app: INestApplication;
let connection: Connection;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [],
controllers: [ApiController],
providers: [],
})
.overrideProvider('Connection')
.useValue(/** How to ??? */)
.compile();
app = module.createNestApplication();
await app.init();
});
});
Result is
[Nest] 35181 - 10/30/2018, 5:42:06 PM [ExceptionHandler]
Connection "default" was not found.
ConnectionNotFoundError: Connection "default" was not found.
In this case, I just want to check request parameters and response values using mock by test.
I'd like to disable the DB connection by override it.
Well assuming you have some kind of a mocked Connection (e.g. use jest.createMockInstance see https://www.npmjs.com/package/jest-create-mock-instance ):
api.e2e-spec.ts
describe('API (e2e)', () => {
let app: INestApplication;
let connection: Mocked<Connection>;
beforeAll(async () => {
connection = createMockInstance(Connection);
const module = await Test.createTestingModule({
imports: [],
controllers: [ApiController],
providers: [],
})
.overrideProvider(Connection)
.useValue(connection)
.compile();
app = module.createNestApplication();
await app.init();
});
});

Categories