Dependencies when testing in NestJS - jestjs

As the first example in NestJS shows(https://docs.nestjs.com/fundamentals/testing), the dependency for CatsController is CatsService, so they make new instance of CatService and pass it in the params for CatsController, As easy as pie.
In my real world usage the service is the main service of the system, it depends on a multiple services which they depend on other services and almost all of them including the main one depend on injecting repositories of TypeORM.
How should I handle such of a complex dependency injection when testing?
An example:
Class MainService {
constructor(
#InjectRepository(SomeEntity1)
private readonly someRepo1: Repository<SomeEntity1>,
#InjectRepository(SomeEntity2)
private readonly someRepo2: Repository<SomeEntity2>,
#InjectRepository(SomeEntity3)
private readonly someRepo2: Repository<SomeEntity3>,
private readonly someService1: SomeService1,
private readonly someService2: SomeService2,
private readonly someService3: SomeService3,
private readonly someService4: SomeService4,
private readonly someService5: SomeService5,
private readonly someService6: SomeService6,
private readonly someService7: SomeService7,
private readonly someService8: SomeService8,
private readonly someService9: SomeService9
){}
}
Class SomeService1 {
constructor(
#InjectRepository(SomeEntity1)
private readonly someRepo4: Repository<SomeEntity4>,
#InjectRepository(SomeEntity2)
private readonly someRepo5: Repository<SomeEntity5>,
#InjectRepository(SomeEntity3)
private readonly someRepo6: Repository<SomeEntity6>,
private readonly someService4: SomeService4,
private readonly someService5: SomeService10,
private readonly someService9: SomeService11
){}
}
I could find a solution online.
Thanks!

Generally when you do unit testing, you want to mock the dependencies, because you want to isolate the unit under test from the rest of your application.
Nest offer multiple ways to mock and Jest himself also offers other possibilities (https://jestjs.io/docs/es6-class-mocks).
One way is to use custom providers and provide your own mock:
const module = await Test.createTestingModule({
providers: [
MainService,
{
provide: SomeService1
useValue: MockSomeService1
}
]
})
.compile()
MockSomeService1 can be an object, a class or a factory function. Refers to the documentation for more details: https://docs.nestjs.com/fundamentals/custom-providers
If you have a lot of dependencies, that could be a good use case for using auto mocking and https://www.npmjs.com/package/#golevelup/ts-jest combined.
import { createMock } from '#golevelup/ts-jest'
const module = await Test.createTestingModule({
providers: [
MainService
]
})
.useMocker(() => createMock())
.compile()
More details on auto mocking: https://docs.nestjs.com/fundamentals/testing#auto-mocking

Related

NestJS onModuleInit not working when specific service is added to constructor

I have a bunch of services defined in my NestJS project. Some of the services are used as common services in a lot of other services. So they are imported in a few modules as well. But I noticed that when specific service srvD is imported in another service srvE2, the onModuleInit is not being called when the project starts.
The project starts without any error. Not really sure what's happening.
An example of the project structure. Issue is in srvE2
srvA.ts
#Injectable()
export class SrvA {
constructor(
private somePkgSrv: SomePkgSrv,
) {}
}
srvB.ts
#Injectable()
export class SrvB {
constructor(
private srvA: SrvA,
) {}
}
srvC.ts
#Injectable()
export class SrvC {
constructor(
private srvA: SrvA,
private srvB: SrvB,
) {}
}
srvD.ts
#Injectable()
export class SrvD {
constructor(
private srvA: SrvA,
private srvB: SrvB,
private srvC: SrvC,
) {}
}
srvD.module.ts
#Module({
providers: [SrvA, SrvB, SrvC, SrvD],
exports: [SrvD],
})
srvE1.ts
export class SrvE1 implements OnModuleInit {
constructor(
private srvA: SrvA,
private srvB: SrvB,
private srvC: SrvC,
) {}
async onModuleInit() {
console.log ('I can print! Yay!')
}
}
srvE2.ts
export class SrvE2 implements OnModuleInit {
constructor(
private srvA: SrvA,
private srvB: SrvB,
private srvC: SrvC,
private srvD: SrvD,
) {}
async onModuleInit() {
console.log ('I refuse to print so long as SrvD is here. Comment it and I will
print')
}
}
srvE.module.ts
#Module({
import: [SrvD], // the module
providers: [SrvE1, SrvE2], // the services
exports: [SrvE1, SrvE2],
})
I am dealing with a similar issue. Based on the comments here I tried changing the scoping for my new dependency from Scope.REQUEST to Scope.DEFAULT and that resolved my issue.
Request scoped providers spread this scope to whole chain of injection. Consequently, if you use it, even deeply inside your code, then onModuleInit won't run.
That being said, well ... request scope isn't very recommended, performance wise. Here is an interesting article : Why You Should Avoid Using Request-scoped Injection in NestJS
I've also bumped into this question & its answers : How to inject a request scoped provider at NestJS controller?
I've tried nj-request-scope package from #profes, and it works fine, especially to fix your problem.
If you don't want to use it, you can still try to move your onModuleInit code in a default scoped service.

Multiple Services in the same module in Nestjs

I have a Post module which contains a controller, an entity class and two services - PostService and PostCacheService.
#Module({
controllers: [PostController],
providers: [PostService, PostCacheService, PaginationService, Logger],
imports: [
MikroOrmModule.forFeature({
entities: [Post, User, Comment],
})
],
exports: [PostService, PostCacheService],
})
export class PostModule {}
These are the classes I am injecting in PostService.
constructor(
#InjectRepository(Post)
private readonly postRepository: postRepository,
#InjectRepository(User)
private readonly userRepository: UserRepository,
#InjectRepository(Comment)
private readonly commentRepository: EntityRepository<Comment>,
private readonly logger: Logger,
private readonly postCacheService: PostCacheService,
) {}
And these are the classes I am injecting PostCacheService:
constructor(
#Inject(CACHE_MANAGER)
private cacheManager: Cache,
private logger: Logger,
private readonly postService: PostService,
) {}
I am injecting PostService in PostCacheService and injecting PostCacheService in PostService.
This is the error I am getting: Nest can't resolve dependencies of the PostCacheService (CACHE_MANAGER, Logger, ?). Please make sure that the argument dependency at index [2] is available in the PostModule context.
Any Idea how to fix this? And is it possible to use two services in a module?
Thanks.

nestjs instantiate not injectable class

i'm pretty new in nestJS, so you can address me directly to documentation if my question was covered there, but i can't figure out how to create instance of none-injectable class.
Here is simplified version of code
export default class BoardingPass {
constructor(
orderId: string,
companyIATa: string,
token: string,
private readonly checkinOrder = new CheckinOrder(orderId, companyIATa, token) // <-- i need to instantiate CheckinOrder
) {}
}
So i need to instantiate the CheckinOrder class but it requires ApolloClient which can be provided only via nestJS DI mechanism
export default class CheckinOrder implements ICheckinOrder {
private order: CheckinOrderObject;
constructor(
private readonly id: string,
private readonly carrierIATACode: string,
private readonly accessToken: string,
private readonly apolloClient: ApolloClient<NormalizedCacheObject> // can't figure out how to pass it via DI
) {}
}
update
Read about custom providers, it seems like it what i need, but as you can see there is some dynamic arguments passed to init method of CheckinOrder
const checkinOrderProvider: Provider<CheckinOrder> = {
useFactory: apolloClient => new CheckinOrder('1', '2', '3', apolloClient),
provide: CheckinOrder,
inject: [ApolloClient]
};
#Module({
imports: [ConfigModule.forRoot(), ApolloClientModule],
providers: [checkinOrderProvider],
exports: [checkinOrderProvider]
})
export class GooglePayModule {}
and only Apollo client is static, so its still unclear how to implement regular interface composition when class Boarding pass has instance of class CheckinOrder as property :(
You can provide the factory method and use it somewhere to create your service just in time. As we do here:
https://github.com/valueadd-poland/pimp-my-pr/blob/master/libs/server/repository/infrastructure/src/lib/repositories/repository-repository.adapter.ts#L18

How do you create a base service class using Nest.js + TypeORM?

I am working with Nest.js + TypeORM and hit a snag when trying to add inheritance to service classes.
I want to have a User service class that extends off of a Base service class, inheriting all the methods the it has.
This is what I've done:
export class BaseService<T> {
private repo;
constructor(repo: Repository<T>){
this.repo = repo;
}
async findAll(opts?): Promise<T[]> {
return this.repo.find(opts);
}
......
}
Then on my User service:
export class UserService extends BaseService<User> {
constructor(
#InjectRepository(User)
private userRepository: Repository<User>,
private readonly mailerService: MailerService,
) {
super(userRepository);
}
}
This works fine where I just need a single repository in the Service class but once I need more such as productRepository, as you can see it would fail due to constructor being hardcoded to accept a single repository.
I can't seem to figure out what would be the most elegant way of achieving something like this.
Does anyone know?
It is an old thread, anyways I meat this same problem and this answer may help someone else...
You can have an abstract class with common functions and boilerplate that you wich to be available in all your repositories to maintain patterns and so on...
export abstract class BaseService<T extends ObjectLiteral> {
protected repo: Repository<T>;
async findAll(opts?): Promise<T[]> {
return this.repo.find(opts);
}
......
}
Observe that constructor is omitted here and as an abstract class you should never have an instance of it, it is meant to be just an extension with common boilerplates and features that you don't want to write every single time you need a new service with the same features like a CRUD service or so...
Then you will use it like this:
export class UserService extends BaseService<User> {
constructor(
#InjectRepository(User) protected readonly repo: Repository<User>,
private readonly mailerService: MailerService,
) {
super();
}
...... extended stuff
}
I'm not sure if you need to inject repo with the same name and visibility but as it worked for me I didn't dive any further to get to know...
The bad side of this approach to me is that even if you don't have anything else to inject via constructor in your service you need to declare constructor and inject the base repository... it is a boilerplate that could be avoided too but I couldn't achieve this right now...

Nest.js get injector instance

I want to create an instance of a dynamically loaded class trough Nest.js dependency injection service.
In Angular I would use Injector.create, what would be the equivalent in Nest.js ?
First of all you should get a ModuleRef which references current module, and then use its "get" method to get an instance.
#Injectable()
export class AppletService {
files: FileService;
constructor(
private moduleRef: ModuleRef,
) {
this.files = moduleRef.get(FileService);
}
}

Resources