Reputation: 2760
I need to write unit tests for the following DataService
,
@Injectable()
export class DataService {
constructor(private config: ConfigService, private http: HttpClient) {
}
.....
someMethod(){
let apiUrl = this.config.get('api').url; // LINE 1
}
}
The ConfigService
is injected to DataService
that has a load
function which gets the configuration from a json file. This load
function will be invoked when the app is initialized.
export function configServiceFactory(config: ConfigService) {
return () => config.load();
}
...
providers: [
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: configServiceFactory,
deps: [ConfigService],
multi: true
}
]
Here's a part of data-service.spect.ts
file,
...
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [DataService, ConfigService]
});
mock = TestBed.get(HttpTestingController);
service = TestBed.get(DataService);
});
....
so when i run the test, in LINE 1
i get that the this.config.get('api')
is undefined. I can understand that it is because the ConfigService
did not load the data from JSON. So now how i can i make the injected services also make the async calls during unit tests?
Upvotes: 3
Views: 4478
Reputation: 8859
When writing unit tests, you would want to mock every dependency you have. You already do this for HttpClient
by importing HttpClientTestingModule
so you need to do the same for ConfigService
.
There are two ways to do so.
1- Fake Service (Stub)
export class ConfigServiceStub {
get(input: string) {
// return static value instead of calling an API
}
}
...
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [DataService,
{provide: ConfigService, useClass: ConfigServiceStub}
]
});
mock = TestBed.get(HttpTestingController);
service = TestBed.get(DataService);
});
With this way, your DataService
won't call the real ConfigService
, but instead it'll call the get
method of ConfigServiceStub
. When using Stub
s, you don't need to worry about other dependencies ConfigService
has. You just implement the methods that you want to override.
2-Spies
You can create spies on the method that you don't want to invoke.
it('run some test', inject([DataService], (service: DataService) => {
spyOn(service.config, 'get').and.returnValue('someString');
// run your tests here
});
Even though, ConfigService.get
method won't get called in the example above, Angular still needs to create an instance of ConfigService
which may be hard to do so in some examples or may result in creating too many other services for simple test.
I'd go with option 1
For more information on spies, check here
Upvotes: 3