Hi all!
I've been working with NestJS on a service that I have here and I got caught in a problem when creating test cases. I'm using NestJS + GraphQL. Most of my modules have circular reference, in order to create type resolvers and allow data to be queried from both sides of the relation.
I have the current scenario:
person.module.ts
@Module({
exports: [PersonService],
providers: [PersonService, PersonResolver],
imports: [forwardRef(() => AddressModule))]
})
export class PersonModule {}
address.module.ts
@Module({
exports: [AddressService],
providers: [AddressService, AddressResolver],
imports: [forwardRef(() => PersonModule)]
})
export class AddressModule {}
All fine for the application perspective, works like a charm. My problem is with testing. I'm creating mocked modules with @nestjs/testing features and I got caught with a problem because of this circular reference.
export function createAddressModule(): Promise<TestingModule> {
return Test.createTestingModule({
imports: [forwardRef(() => PersonModule)], // <----- here lies the issue!
providers: [
AddressService,
AddressResolver,
{
provide: getRepositoryToken(Address),
useClass: Repository
}
]
}).compile()
}
I'm not able to create a TestingModule instance because the function is not being able to mock this import's providers. Is there a proper way to do that that I'm not seeing here?
Thanks y'all for the support!
If you're unit testing a service, you pretty much shouldn't ever have an imports property. Instead, you should add the PersonService to the array of providers as a custom provider and provide a mock for it that way, similarly to how you have done for getRepositoryToken(Address)
Thanks for the reply, @jmcdo29 , but the issue is to create the module itself, since it requires these dependencies:
PersonService contains an AddressService, because my address output object contains a field that resolves as person; therefore, I need PersonService to query this guy;AddressService contains PersonService, because my person output object contains a list of addresses a person can have; therefore, I need AddressService to query the list of those.My scenario is bigger than that, because Person resolves addresses, phoneNumbers, and more entities related to it. I'm importing a module to make easier to refer to a given service and the repositories attached to them.
In this specific scenario, do you know a way to import a mocked module under imports property?
I'm gonna go ahead and quote myself
If you're unit testing a service, you pretty much shouldn't ever have an
importsproperty.
You should be setting up a TestModule like so instead:
beforeEach(async () => {
const mod = await Test.createTestingModule({
providers: [
AddressService, // <-- The service you are testing,
{
provide: getRepositoryToken(Address), // <-- dependency of service you are testing
useClass: Repository
},
{
provide: PersonService, // <-- another dependency of the service you are testing
useValue: objectWithMethodsPersonServiceHasButNowTheyAreMocked
}
]
}).compile();
});
Now there are no problems with imported modules creating instances of other module or providers and you can get on with you're unit testing. You also don't have to worry about what PersonService has dependencies on because that version doesn't exist in this test, and you can focus purely on testing the AddressService instead of doing an integration test of AddressService with PersonService with XService, YService, andZService`.
The problem isn't the framework. It's how the test was set up.
@jmcdo29 , I'm not saying the problem is the framework. I'm saying I'm using a different design here and it seems the testing module does not support this kind of scenario - yet.
I totally understand what you mean here, but it does not apply to my scenario. I'll change my architecture design without this circular dependency to see if makes the modules testable.
Thanks for the reply!
@jmcdo29 is fully right here. For unit tests, using imports is considered as bad practice because otherwise, it can't really be named "unit test" anymore. Also, circular references are considered as bad practice too. There are always (in 99% scenarios) better ways to organize the code to avoid having them.
As for your issue specifically, instead of passing the module definition to the createTestingModule, define the AddressTestModule class and the PersonsTestModule class, specify relations and import AddressTestModule in the createTestingModule().
Please, use our Discord channel (support) for such questions. We are using GitHub to track bugs, feature requests, and potential improvements.