Currently, I am working on a service that has a dependency on another service, which in turn relies on two abstract classes as dependencies.
(ThemeConfigService -> (SettingsService -> SettingsLoader, NavigationLoader))
During testing, the failure occurred because the methods exposed via the abstract classes could not be found (resulting in a not a function exception).
I have been unable to find a solution to this issue despite conducting several online searches.
Below is the code for the theme configuration service I am attempting to test, located in "theme-config.service.ts"
@Injectable({
providedIn: 'root'
})
export class ThemeConfigService {
constructor(
private platform: Platform,
private router: Router,
private settings: SettingsService
) {
// code removed for brevity
}
}
Here is the service under test, located in "settings.service.ts"
@Injectable()
export class SettingsService {
constructor(public settingsLoader: SettingsLoader,
public navigationLoader: NavigationLoader) { }
public settings(): Observable<any> {
return this.settingsLoader.retrieveSettings();
}
public navigation(): Observable<any> {
return this.navigationLoader.retrieveNavigation();
}
}
Shown below is the SettingsLoader class, with the NavigationLoader looking identical. Both classes are required to be separate based on the design:
export abstract class SettingsLoader {
abstract retrieveSettings(): Observable<any>;
}
My unit test implementation is as follows:
describe('ThemeConfigService', () => {
let service: ThemeConfigService;
let router: Router;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([])
],
providers: [
Platform,
SettingsService,
SettingsLoader,
NavigationLoader
]
});
router = TestBed.inject(Router);
service = TestBed.inject(ThemeConfigService);
});
it('should be created', async(inject([Platform, Router, SettingsService, SettingsLoader, NavigationLoader],
(platform: Platform, router: Router, settings: SettingsService, settingsLoader: SettingsLoader, navigationLoader: NavigationLoader) => {
expect(service).toBeTruthy();
})));
});
Karma returns the following error:
TypeError: this.settingsLoader.retrieveSettings is not a function
which indicates that the abstract classes cannot be resolved.
To address this issue, I created the following:
export class SettingsFakeLoader extends SettingsLoader {
retrieveSettings(): Observable<any> {
return of({});
}
}
I attempted to update the injection of SettingsLoader and NavigationLoader classes with these, which led to the Karma error:
NullInjectorError: R3InjectorError(DynamicTestModule)[ThemeConfigService -> SettingsService -> SettingsLoader -> SettingsLoader]:
NullInjectorError: No provider for SettingsLoader!
Below is the modified beforeEach block for the "theme-config.service.spec.ts" file:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterModule,
RouterTestingModule.withRoutes([])
],
providers: [
Platform,
SettingsService,
SettingsFakeLoader,
NavigationFakeLoader
]
});
router = TestBed.inject(Router);
service = TestBed.inject(ThemeConfigService);
});
Typically, I would avoid testing a scenario this intricate. Perhaps I am missing a simple solution. Any insights provided will be valuable as similar challenges may arise during the application's development.