Unable to mock typeorm datasource - jestjs

Below is the file where I am trying to mock datasource. but Getting error as
TypeError: Cannot set property DataSource of [object Object] which has only a getter
import { AppService } from '../services';
import typeorm = require('typeorm');
describe("Tests for AppService", () => {
beforeAll(() => {
typeorm.DataSource = jest.fn().mockReturnValue({
manager: {
find: jest.fn(),
query: jest.fn(),
}
})
});
it("should call getServices", () => {
const AS = new AppService();
AS.getServices(1);
expect(typeorm.DataSource).toHaveBeenCalled();
});
})
datasource file
import { DataSource } from 'typeorm';
import { services } from '../entity/services';
import { versions } from '../entity/versions';
export const connectDB = new DataSource({
type: 'postgres',
host: 'localhost',
port: 5431,
username: 'postgres',
password: 'password',
database: 'test',
synchronize: true,
logging: true,
entities: [services, versions],
subscribers: [],
migrations: [],
});
connectDB
.initialize()
.then(() => {
console.log(`Data Source has been initialized`);
})
.catch((err) => {
console.error(`Data Source initialization error`, err);
});
export default connectDB;
And I am using it like,
import { Injectable } from '#nestjs/common';
import { Like } from 'typeorm';
import connectDB from '../config/db_connection';
import { services } from '../entity/services';
import { versions } from '../entity/versions';
// handle errors //
// write function description
#Injectable()
export class AppService {
public getServices(id: number): Promise<versions[]> {
return connectDB.manager.find(versions, {
relations: {
service: true,
Is there any better way to mock typeorm datasource in jest.
Thanks

This is the way I implement it in a repo of mine:
import { DataSource } from 'typeorm'
const makeDataSourceMock = (): DataSource => ({
name: 'any_data_source',
options: { database: 'any_database' },
destroy: jest.fn(),
manager: { save: jest.fn() },
getRepository: jest.fn(() => ({
clear: jest.fn()
})),
entityMetadatas: [
{ name: 'any_entity_name' }
]
}) as any
Here is the complete test file if you want to take a look: https://github.com/leal32b/webapi-nodejs/blob/main/test/core/3.infra/persistence/postgres/client/postgres-client.unit.test.ts

Related

Sequelize ModelNotInitializedError: Model not initialized

I'm trying to instantiate a new object of my Product model in my service but I got error:
`
ModelNotInitializedError: Model not initialized: Product cannot be instantiated. "Product" needs to be added to a Sequelize instance.
`
There is my sequelize config
I'm ussing repository mode and build the path that contain my models.
import { join } from "path";
import { Config } from "sequelize";
import { Sequelize } from "sequelize-typescript";
const path = join(__dirname, '..', '/entities/models/*.model.ts')
const sequelize = new Sequelize({
repositoryMode: true,
dialect: 'postgres',
host: 'localhost',
database: 'postgres',
port: 5432,
schema: 'store',
username: 'postgres',
password: '123.',
models: [path],
modelMatch: (filename, member) => {
return filename.substring(0, filename.indexOf('.model')).toLowerCase() === member.toLowerCase();
},
hooks: {
afterConnect: async (connection: Sequelize, _config: Config) => {
try {
await connection.query('set search_path = store')
} catch (error) {
console.log('Error at set search path: ', error)
}
}
}
})
export { sequelize }
There are my classes.
Product model, this model I attemp to instantiate.
import { Table, Column, Model, BelongsToMany } from 'sequelize-typescript'
import { Category, CategoryProduct } from './index'
#Table({
timestamps: false,
modelName: 'tal_product',
freezeTableName: true,
underscored: true
})
export class Product extends Model {
#Column({ primaryKey: true })
id!: number
#Column
name!: string
#Column
stock!: number
#Column({ field: 'is_active' })
isActive?: boolean
#BelongsToMany(() => Category, () => CategoryProduct)
categories?: Category[]
}
Product repository, here I have all my querys.
import { Repository } from 'sequelize-typescript'
import { Category, Product } from '../../../entities/models'
import { sequelize } from '../adminModule'
import { ProductRepository } from './product.repository'
export class ProductRepositoryImp implements ProductRepository {
productRepository: Repository<Product>
categoryRepository: Repository<Category>
constructor() {
this.productRepository = sequelize.getRepository(Product)
this.categoryRepository = sequelize.getRepository(Category)
}
create = async (product: Product): Promise<Product> => {
return await this.productRepository.create({ ...product })
}
}
product service, here I call the producRepository and instantiate my model
import { ICreateProduct, IProduct } from '../../../dto/product.dto'
import { Product } from '../../../entities/models'
import { ProductRepository } from '../repositories'
import { ProductServices } from './product.services'
export class ProductServicesImp implements ProductServices {
private productRepository: ProductRepository
public constructor(productRepository: ProductRepository) {
this.productRepository = productRepository
}
create = async (productDto: ICreateProduct): Promise<IProduct> => {
const product = new Product()
product.name = productDto.name
product.stock = productDto.stock
return await this.productRepository.create(product)
}
}
In ProductServicesImp when I instantiate a 'new Product()' I got the error.

NestJS - TypeORM - Unable to track the Entity Not Found in TypeOrm & Nest.JS

No repository for "UserEntity" was found. Looks like
this entity is not registered in current "default" connection? +114ms
RepositoryNotFoundError: No repository for "UserEntity" was found. Looks like this entity is not registered in current "default" connection?
at RepositoryNotFoundError.TypeORMError [as constructor] (E:\Projects\...\src\error\TypeORMError.ts:7:9)
This is a Seed Method. It runs fine and add the data in the database, after adding data, I just get the error.
import { MediaEntity } from '../entities/media.entity';
import { Connection, Equal } from 'typeorm';
import { UserEntity } from '../entities/user.entity';
import { Helper } from '../services/helper';
export default async function UsersSeed(connection: Connection) {
const repository = connection.getRepository(UserEntity);
const data: Partial<UserEntity> = {
firstName: 'tesFName',
lastName: 'testLNsmr',
password: 'fafafa',
email: 'testmail#mail.com',
createdAt: Helper.dateToUTC(new Date())
};
let user = await repository.findOne({ email: data.email });
console.log("19");
console.log(user);
if (!user) {
const entity = Helper.createEntity(UserEntity, data);
console.log("23");
user = await repository.save(entity);
console.log("25");
} else {
console.log("27");
await repository.update(user.id, {
firstName: data.firstName,
lastName: data.lastName
});
console.log("33");
}
console.log("36");
const mediaRepository = connection.getRepository(MediaEntity);
await mediaRepository.update({ user: Equal(null) }, { user: user });
return user;
}
A bit more context:
It is being imported in app.module.ts as this.
#Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, 'uploads')
}),
TypeOrmModule.forRootAsync({
useFactory: async (optionsService: OptionsService) =>
optionsService.typeOrmOptions(),
imports: [OptionsModule],
inject: [OptionsService]
}),
OptionsModule,
MediaModule,
AuthModule,
PlaceModule,
RepositoryModule,
ServicesModule
],
controllers: [],
providers: []
})
And then there is src/entities/entities.module.ts as:
const entities = [
UserEntity,
MediaEntity,
PlaceEntity,
DownloadRestrictionEntity,
MediaInfoEntity
];
#Module({
imports: [TypeOrmModule.forFeature(entities)],
exports: [TypeOrmModule.forFeature(entities)]
})
export class EntitiesModule {
}
Then src/options/options.service.ts as
#Injectable()
export class OptionsService {
constructor(
private service: ConfigService<IEnvironmentVariables>
) {
}
public typeOrmOptions(): TypeOrmModuleOptions {
const environment = process.env.NODE_ENV ? process.env.NODE_ENV : '';
const directory = this.directory();
return {
type: 'postgres',
host: this.service.get('POSTGRES_HOST'),
port: this.service.get('POSTGRES_PORT'),
username: this.service.get('POSTGRES_USER'),
password: this.service.get('POSTGRES_PASSWORD'),
database: this.service.get('POSTGRES_DATABASE'),
synchronize: false,
migrationsRun: this.service.get('RUN_MIGRATIONS'),
keepConnectionAlive: this.isTest(),
entities: [`${directory}/**/*.entity${environment ? '{.ts,.js}' : '{.js, .ts}'}`],
migrationsTableName: 'migrations',
migrations: [`${directory}/migrations/*.ts`],
cli: {
migrationsDir: 'src/migrations'
}
}
}
}
please create UserRepository typeorm-repository-pattern
Method 1:
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User)
private usersRepository: Repository<User>,
) {}
}
Method 2: use BaseRepository from typeorm-transactional-cls-hooked
#EntityRepository(UserAdminEntity)
export class UserAdminRepository extends BaseRepository<UserAdminEntity> {}

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

TypeORM and NestJS: Creating database tables at the beginning of an e2e test

I'd like to have database tables created at the beginning of each test or test run, so that either 1) all migrations are run against the test database, or 2) a single migration sets up all the tables (faster). This would be on a similar fashion as Django does it.
Is there a way to automate this easily with TypeORM, so that I do not need manually maintain a copy of a test database? Naturally, at the end of a test the opposite, tear down and table purge needs to happen.
Currently, my test fails:
● User › GET /users › should return an array of users
QueryFailedError: relation "user" does not exist
at new QueryFailedError (../src/error/QueryFailedError.ts:9:9)
at Query.callback (../src/driver/postgres/PostgresQueryRunner.ts:178:30)
at Query.Object.<anonymous>.Query.handleError (../node_modules/pg/lib/query.js:145:17)
at Connection.connectedErrorMessageHandler (../node_modules/pg/lib/client.js:214:17)
at Socket.<anonymous> (../node_modules/pg/lib/connection.js:134:12)
Apparently... does not fail if I manually run migrations against e2e_test database.
My test code:
import { INestApplication } from '#nestjs/common';
import { Test } from '#nestjs/testing';
import { TypeOrmModule } from '#nestjs/typeorm';
import * as supertest from 'supertest';
import { Repository } from 'typeorm';
import { User } from '../src/user/user.entity';
import { UserModule } from '../src/user/user.module';
describe('User', () => {
let app: INestApplication;
let repository: Repository<User>;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [
UserModule,
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 54320,
username: 'local_dev',
password: 'local_dev',
database: 'e2e_test',
entities: ['./**/*.entity.ts'],
synchronize: false,
}),
],
}).compile();
app = module.createNestApplication();
repository = module.get('UserRepository');
await app.init();
});
afterEach(async () => {
await repository.query(`DELETE FROM users;`);
});
afterAll(async () => {
await app.close();
});
describe('GET /users', () => {
it('should return an array of users', async () => {
await repository.save([{ displayName: 'test-name-0' }, { displayName: 'test-name-1' }]);
const { body } = await supertest
.agent(app.getHttpServer())
.get('/users')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
expect(body).toEqual([
{ id: expect.any(Number), name: 'test-name-0' },
{ id: expect.any(Number), name: 'test-name-1' },
]);
});
});
});
Example loosely based on this tutorial by Paul Salmon.
Looks like one can fish out connection of TypeORM and then call its synchronise method.
If someone 1) knows a better way to obtain the connection 2) knows if it is a right time to call synchronise please drop a comment.
This issue was discussed on Github
describe('User', () => {
let app: INestApplication;
let repository: Repository<User>;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [
UserModule,
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 54320,
username: 'local_dev',
password: 'local_dev',
database: 'e2e_test',
entities: ['./**/*.entity.ts'],
synchronize: false,
}),
],
}).compile();
app = module.createNestApplication();
repository = module.get('UserRepository');
const connection = repository.manager.connection;
// dropBeforeSync: If set to true then it drops the database with all its tables and data
await connection.synchronize(true);
await app.init();
});

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