I'm having difficulty testing a NestJS service which utilises a simple entity that only has one relationship. The related entity, however, is related to many other entities which in turn are also related to other entities. I don't want to import all of the entities and pass to TypeOrmModule.forRoot, and if I don't I get an error like the following:
Entity metadata for Wallet#customer was not found.
For every entity that has not been imported. Is it possible to "mock" the relationship in some way?
Test:
const module: TestingModule = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot(CONNECTION_OPTIONS),
TypeOrmModule.forFeature([WalletEntity, WalletTransactionEntity]),
],
providers: [WalletService],
}).compile();
Entity
#Entity('wallet')
export class WalletEntity extends BaseEntity {
#Column()
customerId: string;
#OneToOne(() => CustomerEntity, (customer) => customer.wallet)
customer: CustomerEntity;
#OneToMany(() => WalletTransactionEntity, (transaction) => transaction.wallet)
transactions: WalletTransactionEntity[];
}
For unit tests, I would suggest not connecting to a live database or using any imports, but instead just using custom providers to mock the dependencies of the service you're testing. This repository has a bunch of examples including some for TypeORM. A very basic setup, based on the imports you have, might be something like
beforeEach(async () => {
const modRef = await Test.createTestingModule({
providers: [
WalletService,
{
provide: getRepositoryToken(WalletEntity),
useValue: walletRepoMock,
},
{
provide: getRepositoryToken(WalletTransactionEntity),
useValue: walletTransactionRepoMock,
}
]
}).compile();
});
Now you can replace walletRepoMock and walletTransactionRepoMock with mock implementation of the methods you use for Repository and you'll be good to go
Related
Im useing nest js framework with mongoose, I need to implement trigger on one of my tables and send notification to users when a record insert ro table please tell me what is the best practice to do this in nest js
The NestJS mongoose module supports hooks. Check out the docs here.
#Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: Cat.name,
useFactory: () => {
const schema = CatsSchema;
schema.pre('save', function() { console.log('Hello from pre save') });
return schema;
},
},
]),
],
})
export class AppModule {}
Another avenue you may be interested in is something called Change Streams, which are native in MongoDB. Essentially, you "watch" for changes on a collection/document.
https://docs.mongodb.com/drivers/node/current/usage-examples/changeStream/
I am using mikro-orm in nestjs to create a sqlite database. For some reason, Mikro-Orm will create the database file(sandox.sqlite3) but will not create tables in the database. I have created a task module with entities.
App Module:
#Module({
imports: [
MikroOrmModule.forRoot({
entities: ['./dist/**/entities/*'],
entitiesTs: ['./src/**/entities/*'],
dbName: 'sandbox.sqlite3',
type: 'sqlite',
}),
TasksModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
TaskModule:
#Module({
imports: [MikroOrmModule.forFeature([Task])],
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}
Task Service:
When i make a post request to create, A 201 response is received back along with the data that i sent. But there is not table created in the database.
#Injectable()
export class TasksService {
constructor(
#InjectRepository(Task)
private readonly taskRepository: EntityRepository<Task>,
) {}
create(createTaskDto: CreateTaskDto) {
console.log(createTaskDto.name);
return this.taskRepository.create({ ...createTaskDto });
}
findAll() {
return this.taskRepository.findAll();
}
findOne(id: number) {
return this.taskRepository.findOne({ id });
}
async update(id: number, updateTaskDto: UpdateTaskDto) {
const task = await this.taskRepository.findOne({ id });
task.name = updateTaskDto.name ?? task.name;
task.description = updateTaskDto.description ?? task.description;
task.assign({ id });
// return this.taskRepository.(task);
}
remove(id: number) {
// return this.taskRepository.removeAndFlush();
}
}
Task Entity:
#Entity()
export class Task extends BaseEntity<Task, 'id'> {
#PrimaryKey()
id: number;
#Property()
name: string;
#Property()
description: string;
}
You need to sync the database manually, either via SchemaGenerator or via migrations. If you have CLI set up, you can do npx mikro-orm schema:update --run
https://mikro-orm.io/docs/faq#how-can-i-synchronize-my-database-schema-with-the-entities
I have NestJS application with couple microservices stored in single repository (monorepo approach).
AccessControl module stores in libs, it should be shared across multiple microservices. It has AccessControlModule.ts file
#Global()
#Module({
providers: [
{
provide: 'CONNECTION1',
useFactory: (configService: ConfigService) => {
return ClientProxyFactory.create(
configService.getRMQConnection(),
);
},
inject: [ConfigService],
},
ACGuard,
],
exports: [ACGuard],
imports: [ConfigModule],
})
export class AccessControlModule implements OnModuleDestroy {
constructor(
#Inject('CONNECTION1')
protected readonly orgConnection: ClientProxy,
) {}
onModuleDestroy(): any {
this.orgConnection.close();
}
}
This file responsible for module description, it creates connection for another microservice and provide it to ACGuard service. ACGuard.ts:
#Injectable()
export class ACGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private config: ConfigService,
#Inject('CONNECTION1')
private readonly userConnection: ClientProxy;
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
// do some stuff
}
}
This part by itself compiles well and logically works fine. Problem begins when I try to inject it into one of microservices. I do it as usual by adding AccessControlModule into import part of some module. For example KioskModule:
#Module({
imports: [
...
AccessControlModule
],
providers: [
...
KiosksResolver
]
})
export class KiosksModule {}
Since AccessControlModule marked as Global and exports ACGuard I expect it to be injectable into my providers.
#Resolver('Kiosk')
export class KiosksResolver {
...
#UseGuards(ACGuard)
#Query()
kiosks() {
// ...
}
...
}
But this code falls on the compilation step with error:
[Nest] 9964 - 05/07/2020, 9:33:02 PM [ExceptionHandler] Nest can't resolve dependencies of the ACGuard (Reflector, ConfigService, ?). Please make sure that the argument CONNECTION1 at index [2] is available in the KiosksModule context.
On the other hand, if i inject it in KiosksResolver's constructor, application builds successfully.
I will appreciate any help and ideas, thanks!
The way how i solved this issue was exporting CONNECTION1 provider in AccessControlModule.ts.
#Module({
providers: [
{
provide: 'CONNECTION1',
useFactory: (configService: ConfigService) => {
return ClientProxyFactory.create(
configService.getRMQConnection(),
);
},
inject: [ConfigService],
},
ACGuard,
],
exports: [ACGuard, 'CONNECTION1'],
imports: [ConfigModule],
})
export class AccessControlModule ...
With this export KioskModule creates it's own ACGuard but provides here connection exported from AccessControlModule.
It's not clear for me why KioskModule doesn't get built instance of ACGuard exported from AccessControlModule but try build it once more.
Connecting to two databases using TypeORM and NestJS throws a ConnectionNotFoundError when a custom repository is registered using the connection (UserConnection & PhotoConnection) one for each database. I have created a minimal repo here https://github.com/masonridge/connissuewithndb. The TypeOrm registration is done in the AppModule of NestJS
(app.module.ts)
#Module({
imports: [
TypeOrmModule.forRootAsync({
name: 'PhotoConnection',
useFactory: async () => {
return {
type: 'sqlite',
synchronize: true,
database: 'TestPhoto.sqlite',
entities: [Photo],
} as SqliteConnectionOptions;
},
}),
TypeOrmModule.forRootAsync({
name: 'UserConnection',
useFactory: async () => {
return {
type: 'sqlite',
synchronize: true,
database: 'TestUser.sqlite',
entities: [User],
} as SqliteConnectionOptions;
},
}),
// TypeOrmModule.forFeature([User, UserRepository], 'UserConnection'),
// TypeOrmModule.forFeature([Photo, PhotoRepository], 'PhotoConnection'),
PhotoModule, UserModule,
],
(photo.module.ts) DB connection1 - PhotoConnection is registered here
#Module({
imports: [
TypeOrmModule.forFeature([PhotoRepository], 'PhotoConnection'),
],
providers: [
PhotoService,
],
controllers: [PhotoController],
exports: [TypeOrmModule],
})
export class PhotoModule {}
(user.module.ts) DB connection2 - UserConnection is registered here
#Module({
imports: [
TypeOrmModule.forFeature([UserRepository], 'UserConnection'),
],
providers: [
UserService,
],
exports: [TypeOrmModule],
controllers: [UserController],
})
export class UserModule {}
(user.repository.ts) Custom repository
#EntityRepository(User)
export class UserRepository extends Repository<User> {
async createUser(name: string): Promise<string> {
const user = this.create();
user.username = name;
user.salt = 'salt';
user.password = 'xxkdkdk';
user.save();
return name;
}
}
(photo.repository.ts)
#EntityRepository(Photo)
export class PhotoRepository extends Repository<Photo> {
async createPhoto(name: string): Promise<string> {
const photo = this.create();
photo.name = name;
photo.save();
return name;
}
}
The repo is injected into the service using the connection (PhotoConnection)
(photo.service.ts)
export class PhotoService {
constructor(
#InjectRepository(Photo, 'PhotoConnection')
private readonly photoRepository: PhotoRepository,
) {}
and here using UserConnection (user.service.ts)
#Injectable()
export class UserService {
constructor(
#InjectRepository(User, 'UserConnection')
private readonly userRepository: UserRepository,
) {}
The application starts fine but on a POST request it throws a ConnectionNotFoundError error
(node:190812) UnhandledPromiseRejectionWarning: ConnectionNotFoundError: Connection "default" was not found.
at new ConnectionNotFoundError (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\error\ConnectionNotFoundError.js:10:28)
at ConnectionManager.get (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\connection\ConnectionManager.js:38:19)
at Object.getConnection (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\index.js:244:35)
at Function.BaseEntity.getRepository (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\repository\BaseEntity.js:67:57)
at Photo.BaseEntity.save (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\repository\BaseEntity.js:27:33)
at PhotoRepository.createPhoto (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.repository.js:15:15)
at PhotoService.createPhoto (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.service.js:24:43)
at PhotoController.addSetting (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.controller.js:22:27)
at C:\nestjs\type-orm-dbscratch\node_modules#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I would like to know if there is an issue with the registration. Any help would be appreciated.
It looks like the issue is between using the TypeORM approach of DataMapper and ActiveRecord. It seems using the ActiveRecord approach does not support named repositories with NestJS, and as such was looking for connection named 'default'. If you remove the extends BaseEntity and use the repository's save method instead this.save(user) vs user.save() you'll get a successful save. I didn't see an immediate way to set the connection of the ActiveRecord but it may be an option somewhere if that is still the approach you would like to follow.
I'm having trouble to mock multiple repositories from different modules in NestJS.
I'm using a UsersRepository from a UsersModule inside another module service (NotesService). The code is working fine, but I can't make the unit tests work.
I have the following error: Error: Nest can't resolve dependencies of the UserRepository (?). Please make sure that the argument Connection at index [0] is available in the TypeOrmModule context.
Minimal reproduction
// [users.module.ts]
#Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
// [users.service.ts]
#Injectable()
export class UsersService {
constructor(#InjectRepository(User) private usersRepository: UsersRepository) {}
...
}
// [notes.module.ts]
#Module({
imports: [
TypeOrmModule.forFeature([Note, User]),
UsersModule,
],
controllers: [NotesController],
providers: [NotesService],
})
export class NotesModule {
}
// [notes.service.ts]
#Injectable()
export class NotesService {
constructor(
#InjectRepository(Note) private notesRepository: NotesRepository,
#InjectRepository(User) private usersRepository: UsersRepository,
) {}
...
}
Here is my unit test configuration:
// [notes.service.spec.ts]
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [UsersModule],
providers: [
NotesService,
{ provide: getRepositoryToken(Note), useFactory: repositoryMockFactory },
{ provide: getRepositoryToken(User), useFactory: repositoryMockFactory },
],
}).compile();
notesService = module.get<NotesService>(NotesService);
notesRepositoryMock = module.get(getRepositoryToken(Note));
});
The problem is I can't make a proper mock of the UsersRepository that comes from another module.
I tried importing TypeOrmModule directly inside the test, and everything I could but I can't make it work.
You don't need to import the UsersModule if you are directly providing the NotesService and the mocks that it will depend on. The reason for the error is that Nest is trying to resolve TypeormModule.forFeature([User]) from the UsersModule. Simply remove the import in the test and you should be golden.