JEST How to mock getManager().query from NestJS - node.js

Hi I'm trying to test my service but got stuck
import { HttpException, Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { getManager, Repository } from 'typeorm';
import { PointsEntity } from './entities/point_store.entity';
#Injectable()
export class PointStoreService {
constructor(
#InjectRepository(PointsEntity)
private readonly pointsRepository: Repository<PointsEntity>,
) {}
async searchPoints(startDate: Date, endDate: Date, userId: string) {
try {
return await getManager().query(`
SELECT * FROM points WHERE expire_at
BETWEEN '${startDate.toISOString()}' AND '${endDate.toISOString()}'
AND "userId"='${userId}'
`);
} catch (err) {
throw new HttpException(err.message, err.state ? err.state : 500);
}
}
}
This is my Service code. As you know there is only one method.
import { Test, TestingModule } from '#nestjs/testing';
import { getRepositoryToken } from '#nestjs/typeorm';
import * as typeorm from 'typeorm';
import { PointsEntity } from './entities/point_store.entity';
import { PointStoreService } from './point_store.service';
const mockPointsRepository = {
save: jest.fn(),
};
describe('PointStoreService', () => {
let service: PointStoreService;
let repository: typeorm.Repository<PointsEntity>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PointStoreService,
{
provide: getRepositoryToken(PointsEntity),
useValue: mockPointsRepository,
},
],
}).compile();
jest.resetModules(); // Most important - it clears the cache
service = module.get<PointStoreService>(PointStoreService);
repository = module.get(getRepositoryToken(PointsEntity));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('searchPoints', () => {
it('should searchPoints', async () => {
// can not mock getManager!!!
jest.spyOn(getManager, 'query');
});
});
});
Now I code this test case. 'should be defined' works fine. But after I try to test 'should searchPoints', I have no idea to mock it.
Is there a way to mock getManager().query()?
gitHub repository

Related

Testing firestore with jest not working as expected

I have the following service which I just need to test that whenever an user is not validated, we throw an error
async payoutPendingRewards(userId: string): Promise<Reward[]> {
const db = admin.firestore();
const userRef = db.collection('UserProfiles').doc(userId);
const doc = await userRef.get();
if (!doc.data() || !doc.data().validated) {
throw Error('User profile has not been validated');
}
const userBankAccount = await this.getUserPayoutAccount(userId);
if (userBankAccount) {
const pendingRewards = await this.getPendingRewards(userId);
const amount = pendingRewards.reduce(
(amount, reward) => amount + reward.amount,
0,
);
if (amount > 0) {
try {
const payoutId = await this.createPayout(
userBankAccount.accountType,
userBankAccount.accountId,
amount,
);
if (payoutId) {
// TODO: add payout ID to Reward
return await this.updatePendingRewards(pendingRewards);
}
} catch (e) {
throw new HttpException(
{
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: 'There was an error: Payout Not Created',
},
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
When trying to test it using jest, I can't get to mock the firebase-admin functions. I get a firebase error when trying to do it
import { Test, TestingModule } from '#nestjs/testing';
import { HttpModule, HttpService } from '#nestjs/axios';
import { describe, expect, beforeEach, it, jest } from '#jest/globals';
import { INestApplication, forwardRef } from '#nestjs/common';
import { SecretManagerServiceClient } from '#google-cloud/secret-manager';
import * as request from 'supertest';
import { RewardsService } from '../src/rewards/rewards.service';
import { RewardsController } from '../src/rewards/rewards.controller';
import { UsersModule } from '../src/users/users.module';
import { FirebaseAuthGuard } from '../src/auth/firebase-auth.guard';
import { HttpExceptionFilter } from '../src/exceptions/http-exception.filter';
import { TransformInterceptor } from '../src/interceptors/transform.interceptor';
import { firestore } from './setupFirestoreTests';
describe('RewardsService', () => {
let service: RewardsService;
let httpService: HttpService;
let app: INestApplication;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RewardsService],
controllers: [RewardsController],
imports: [
FirebaseAuthGuard,
HttpExceptionFilter,
TransformInterceptor,
HttpModule,
forwardRef(() => UsersModule),
],
}).compile();
app = module.createNestApplication();
service = module.get<RewardsService>(RewardsService);
httpService = module.get<HttpService>(HttpService);
await app.init();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('makes a call to firestore', async () => {
service.payoutPendingRewards('123');
expect(firestore().doc().get).toHaveBeenCalledWith('123');
});
});
I need help just mocking the firebase functions. Already tried by creating mock functions on a different file but it didn't work

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" }]);
});
//
});
});

Nodejs Typescript Jest Unit Test Coverage shows some code to covered

This is my nodejs typescript class and written jest unit test for isHealthy() public method.
Test coverage shows that this.pingCheck() then block, catch and last return statement are not covered.
Please advise.
Can we do unit test for pingCheck private method ?
This my class
import { HttpService, Injectable } from '#nestjs/common';
import { DependencyUtlilizationService } from '../dependency-utlilization/dependency-utlilization.service';
import { ComponentType } from '../enums/component-type.enum';
import { HealthStatus } from '../enums/health-status.enum';
import { ComponentHealthCheckResult } from '../interfaces/component-health-check-result.interface';
import { ApiHealthCheckOptions } from './interfaces/api-health-check-options.interface';
#Injectable()
export class ApiHealthIndicator {
private healthIndicatorResponse: {
[key: string]: ComponentHealthCheckResult;
};
constructor(
private readonly httpService: HttpService,
private readonly dependencyUtilizationService: DependencyUtlilizationService,
) {
this.healthIndicatorResponse = {};
}
private async pingCheck(api: ApiHealthCheckOptions): Promise<boolean> {
let result = this.dependencyUtilizationService.isRecentlyUsed(api.key);
if (result) {
await this.httpService.request({ url: api.url }).subscribe(() => {
return true;
});
}
return false;
}
async isHealthy(
listOfAPIs: ApiHealthCheckOptions[],
): Promise<{ [key: string]: ComponentHealthCheckResult }> {
for (const api of listOfAPIs) {
const apiHealthStatus = {
status: HealthStatus.fail,
type: ComponentType.url,
componentId: api.key,
description: `Health Status of ${api.url} is: fail`,
time: Date.now(),
output: '',
links: {},
};
await this.pingCheck(api)
.then(response => {
apiHealthStatus.status = HealthStatus.pass;
apiHealthStatus.description = `Health Status of ${api.url} is: pass`;
this.healthIndicatorResponse[api.key] = apiHealthStatus;
})
.catch(rejected => {
this.healthIndicatorResponse[api.key] = apiHealthStatus;
});
}
return this.healthIndicatorResponse;
}
}
This is my unit test code.
I get the following error when I run npm run test
(node:7876) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'status' of undefined
(node:7876) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 6)
import { HttpService } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { DependencyUtlilizationService } from '../dependency-utlilization/dependency-utlilization.service';
import { ApiHealthIndicator } from './api-health-indicator';
import { ApiHealthCheckOptions } from './interfaces/api-health-check-options.interface';
import { HealthStatus } from '../enums/health-status.enum';
describe('ApiHealthIndicator', () => {
let apiHealthIndicator: ApiHealthIndicator;
let httpService: HttpService;
let dependencyUtlilizationService: DependencyUtlilizationService;
let dnsList: [{ key: 'domain_api'; url: 'http://localhost:3001' }];
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ApiHealthIndicator,
{
provide: HttpService,
useValue: new HttpService(),
},
{
provide: DependencyUtlilizationService,
useValue: new DependencyUtlilizationService(),
},
],
}).compile();
apiHealthIndicator = module.get<ApiHealthIndicator>(ApiHealthIndicator);
httpService = module.get<HttpService>(HttpService);
dependencyUtlilizationService = module.get<DependencyUtlilizationService>(
DependencyUtlilizationService,
);
});
it('should be defined', () => {
expect(apiHealthIndicator).toBeDefined();
});
it('isHealthy should return status as true when pingCheck return true', () => {
jest
.spyOn(dependencyUtlilizationService, 'isRecentlyUsed')
.mockReturnValue(true);
const result = apiHealthIndicator.isHealthy(dnsList);
result.then(response =>
expect(response['domain_api'].status).toBe(HealthStatus.pass),
);
});
it('isHealthy should return status as false when pingCheck return false', () => {
jest
.spyOn(dependencyUtlilizationService, 'isRecentlyUsed')
.mockReturnValue(false);
jest.spyOn(httpService, 'request').mockImplementation(config => {
throw new Error('could not call api');
});
const result = apiHealthIndicator.isHealthy(dnsList);
result
.then(response => {
expect(response['domain_api'].status).toBe(HealthStatus.fail);
})
.catch(reject => {
expect(reject['domain_api'].status).toBe(HealthStatus.fail);
});
});
});
Looks like you should define the status before initialize the unit test, try to grab some more logs using console.log and for the second test, added catch block to make sure you're grabing the failures

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

supertest e2e with nestjs: request is not a function

I try to introduce e2e tests for my simple NestJS backend services. I am providing a custom userService and a custom UserRepository mocked with sinon.
This is my user.e2e-spec.ts file:
import * as request from 'supertest';
import * as sinon from 'sinon';
import { Test } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import { UserService } from '../../src/user/user.service';
import { getRepositoryToken } from '#nestjs/typeorm';
import { User } from '../../src/user/user.entity';
import { TestUtil } from '../../src/utils/TestUtil';
import { createFakeUser } from '../../src/user/test/userTestUtil';
let sandbox: sinon.SinonSandbox;
let testUtil;
describe('User', () => {
let app: INestApplication;
const fakeUser = createFakeUser();
const userService = { findOne: () => fakeUser };
beforeAll(async () => {
sandbox = sinon.createSandbox();
testUtil = new TestUtil(sandbox);
const module = await Test.createTestingModule({
providers: [
{
provide: UserService,
useValue: userService,
},
{
provide: getRepositoryToken(User),
useValue: testUtil.getMockRepository().object,
},
],
}).compile();
app = module.createNestApplication();
await app.init();
});
it(`/GET user`, () => {
return request(app.getHttpServer())
.get('/user/:id')
.expect(200)
.expect({
data: userService.findOne(),
});
});
afterAll(async () => {
await app.close();
});
});
and this is my user.controller.ts:
import { ApiBearerAuth, ApiUseTags } from '#nestjs/swagger';
import { Controller, Get, Param } from '#nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';
#ApiUseTags('Users')
#ApiBearerAuth()
#Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
#Get('/:id')
findOne(#Param('id') id: number): Promise<User> {
return this.userService.find(id);
}
}
I wrote a bunch of Unit tests with the same pattern and it works. Have no clue what is wrong with this e2e supertest.
Thanks for your help!
UPDATE:
This is the error message I get:
TypeError: request is not a function
at Object.it (/Users/florian/Development/Houzy/nestjs-backend/e2e/user/user.e2e-spec.ts:40:16)
at Object.asyncFn (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/jasmine_async.js:124:345)
at resolve (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:46:12)
at new Promise (<anonymous>)
at mapper (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:34:499)
at promise.then (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:74:39)
at <anonymous>
Change import of request to:
import request from 'supertest';
In your test, replace :id with number:
it(`/GET user`, () => {
return request(app.getHttpServer())
.get('/user/1') // pass here id, not a string
.expect(200)
.expect({
data: userService.findOne(),
});
});
And in controller:
#Get('/:id')
findOne(#Param('id') id: number): Promise<User> {
return this.userService.find(id);
}
This should work now.

Resources