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