Reputation: 1022
I'm trying to test code that's in one of my module's constructor. Basically, the module (named GraphqlModule
) configures a service (named Graphql
) and provides it. The configuration happens in the module's constructor.
Here's the code I use to test the module.
it('should use the GraphqlConfigs and ServerConfigs', (done: DoneFn) => {
// Adding spies to Config classes
let serverConfigs = new ServerConfigs();
let serverDomainSpy = spyOnProperty(serverConfigs, 'ServerDomain', 'get').and.callThrough();
let serverPortSpy = spyOnProperty(serverConfigs, 'ServerPort', 'get').and.callThrough();
let gqlConfigs = new GraphqlConfigs();
let protocolSpy = spyOnProperty(gqlConfigs, 'EndpointProtocol', 'get').and.callThrough();
let endpointNameSpy = spyOnProperty(gqlConfigs, 'EndpointName', 'get').and.callThrough();
TestBed.configureTestingModule({
imports: [ GraphqlModule ],
providers: [
{provide: ServerConfigs, useValue: serverConfigs}, // Replacing real config classes with the ones spied on.
{provide: GraphqlConfigs, useValue: gqlConfigs}
]
}).compileComponents().then(() => {
// This line seems to make Angular instantiate GraphqlModule
const graphql = TestBed.get(Graphql) as Graphql;
expect(serverDomainSpy.calls.count()).toBe(1, 'ServerConfigs.ServerDomain was not used.');
expect(serverPortSpy.calls.count()).toBe(1, 'ServerConfigs.ServerPort was not used.');
expect(protocolSpy.calls.count()).toBe(1, 'GraphqlConfigs.EndpointProtocol was not used.');
expect(endpointNameSpy.calls.count()).toBe(1, 'GraphqlConfigs.EndpointName was not used.');
done();
});
});
As is, the test passes and works, but if I dont use the following (useless) line const graphql = TestBed.get(Graphql) as Graphql;
the GraphqlModule
gets instantiated after the test has been executed, which makes the test fail.
Since it's GraphqlModule
that provides the Graphql
service, I understand that there's some lazy loading algorithm in Angular that's triggered when I do TestBed.get(Graphql)
. That's fine... my question is, is there a way to make my module load in a more explicit way?
Here's the GraphqlModule class definition:
imports...
@NgModule({
imports: [
CommonModule,
HttpClientModule,
ApolloModule,
HttpLinkModule
],
declarations: [],
providers: [
Graphql, // Is an alias for Apollo
GraphqlConfigs
]
})
export class GraphqlModule {
constructor(
@Optional() @SkipSelf() parentModule: GraphqlModule,
graphql: Graphql,
httpLink: HttpLink,
serverConfigs: ServerConfigs,
graphqlConfigs: GraphqlConfigs
) {
// Making sure this is not imported twice.
// https://angular.io/guide/ngmodule#prevent-reimport-of-the-coremodule
if (parentModule) {
throw new Error(
'GraphqlModule is already loaded. Import it in the '+CoreModule.name+' only.');
}
// Gql setup:
const gqlHttpLink = httpLink.create({
uri: GraphqlModule.buildEndpointUrl(serverConfigs, graphqlConfigs)
});
graphql.create({
link: gqlHttpLink,
cache: new InMemoryCache(),
});
}
private static buildEndpointUrl(serverConfigs: ServerConfigs, graphqlConfigs: GraphqlConfigs): string {
return graphqlConfigs.EndpointProtocol + // eg. http://
serverConfigs.ServerDomain+":"+serverConfigs.ServerPort+'/' + // eg. example.com:80/
graphqlConfigs.EndpointName; // eg. graphql
}
}
Upvotes: 2
Views: 4163
Reputation: 222309
Graphql
is already instantiated in GraphqlModule
constructor for root injector, and GraphqlModule
is eagerly instantiated when it's specified in TestBed imports
. There's no lazy loading involved.
As it's explained in this answer, TestBed injector is instantiated on first inject
callback call, or TestBed.get
, or TestBed.createComponent
call. The injector doesn't exist until the first injection, so don't any module or provider instances. Since almost all TestBed tests perform at least one of these calls in it
or beforeEach
, this issue usually never appears.
Since graphql
instance isn't needed in this test, in order for the test to pass it can be just:
TestBed.get(Injector);
Or:
TestBed.get(TestBed);
Also, .compileComponents().then(() => { ... })
and done()
are unnecessary, the test doesn't involve components and is synchronous.
The fact that the injector needs to be instantiated manually suggests that TestBed isn't required for this test, although it's beneficial because this way GraphqlModule
constructor DI annotation can be tested, including the one for parentModule
.
Upvotes: 3