How to mock third party library using jest - javascript

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();

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

Error: Circular dependency - NestJS Testing

When I create a test of my controller I get the following error:
A circular dependency has been detected. Please make sure that each
side of a bidirectional relationship is decorated with "forwardRef()"
// category.service.ts
constructor(
#InjectModel(Category.name) private categoryModel: Model<CategoryDocument>,
private readonly subcategoriesService: SubcategoriesService,
) {}
// category.controller.spec.ts
describe('V1CategoriesController', () => {
let categoriesController: CategoriesController;
let categoriesService: CategoriesService;
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
controllers: [CategoriesController],
providers: [CategoriesService, SubcategoriesService],
}).compile();
categoriesController = moduleRef.get<CategoriesController>(
CategoriesController,
);
categoriesService = moduleRef.get<CategoriesService>(CategoriesService);
jest.clearAllMocks();
});
it('CategoriesController - should be defined', () => {
expect(categoriesController).toBeDefined();
});
});
I tried to create a SubcategoriesService Mock, but I'm not sure it's the right way to do it
const SubcategoriesService = jest.fn().mockReturnValue({
findByCategory: jest.fn().mockResolvedValue([]),
});
const moduleRef: TestingModule = await Test.createTestingModule({
controllers: [CategoriesController],
providers: [CategoriesService, { provide: 'SubcategoriesService', useFactory: SubcategoriesService },],
}).compile();
For your main service you are using a Mock?
There is a possibility that you are not using a mock or it has the wrong name.

Angular: testing login function

I'm moving my first steps in Angular testing. In particular, I am trying to test this function in LoginComponent
login() {
this.restApi.login(this.loginForm.value).subscribe((resp) => {
this.user.username = (resp as any).user;
this.user.token = (resp as any).tkn;
this.router.navigate(['/main']);
});
}
restApi is an instance of RestApiService, a service that makes http calls. To test this function, I wrote this test:
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let user = new UserService();
let httpMock: HttpTestingController;
let httpClient: HttpClient;
let testRouter = {
navigate: jasmine.createSpy('navigate'),
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, FormsModule, HttpClientTestingModule],
declarations: [LoginComponent],
providers: [
{ provide: Router, useValue: testRouter },
{ provide: UserService, useValue: user },
RestApiService,
],
}).compileComponents();
httpClient = TestBed.inject(HttpClient);
httpMock = TestBed.inject(HttpTestingController);
}));
function updateForm() {
component.loginForm.controls['username'].setValue('bob');
component.loginForm.controls['password'].setValue('bobspassword');
}
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
component.ngOnInit();
fixture.detectChanges();
});
it('should login', () => {
const loginRes = {
res: 1,
spec: 'ok',
tkn: 'aToken',
user: 'bob',
};
const loginReq = {
username: 'bob',
password: 'bobspassword',
};
updateForm();
component.login();
expect(component.user.token).toBeDefined();
expect(testRouter.navigate).toHaveBeenCalledWith(['/main']);
const req = httpMock.expectOne(
'http://myServerAddress:8080/restEndpoint/login'
);
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual(loginReq);
req.flush(loginRes);
});
});
The test fails because component.user.token is undefined and it doesn't navigate to /main
Can you help me?
Thanks
This should be handled using Mock rather than making actual http call by calling the service.
export class MockRestApiService{
login(){
return of({
user: 'someName',
tkn: 'someToken'
})
}
}
Now import this Mock class in spec as below:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, FormsModule, HttpClientTestingModule],
declarations: [LoginComponent],
providers: [
{ provide: Router, useValue: testRouter },
{ provide: UserService, useValue: user },
{provide: RestApiService, useClass: MockRestApiService},
],
}).compileComponents();
httpClient = TestBed.inject(HttpClient);
httpMock = TestBed.inject(HttpTestingController);
}));
This will return someToken as the value for the called function login()
Try to take a look at this article where I have explained a component code with proper mocking.. since you are new to testing, I would recommend this article to start with

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)

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.

Categories