I have an Angular service which has as private member a class instance (Keycloak). The class is defined in an external npm library (keycloak-js). In the constructor of the service, I call keycloak.init() which makes some HTTP requests. When writing unit tests for the service, I want to avoid calling the real keycloak.init function. All other methods/properties of the service should remain the original ones. Here is some minimal code:
//defined in external npm library
export class Keycloak {
init() {
//makes HTTP calls
console.log('Obj func');
}
}
@Injectable({
providedIn: 'root',
})
export class KeycloakService{
private keycloak;
constructor() {
this.keycloak = new Keycloak();
this.keycloak.init();
}
//other methods and props
}
describe('KeycloakService', () => {
let service: KeycloakService;
const objectSpy: jasmine.SpyObj<Keycloak> = jasmine.createSpyObj('Keycloak', [
'init',
]);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [KeycloakService],
//somehow override KeycloakService.keycloak with keycloakSpy, while keeping other methods and properties untouched
});
service = TestBed.inject(KeycloakService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
How could I achieve this?
Update: I'm managed to solve the problem by wrapping the Keycloak class into an Angular service, but I still wonder if this can be avoided. Updated complete code:
//defined in external npm library
export class Keycloak {
init() {
//makes HTTP calls
console.log('Obj func');
}
}
@Injectable({
providedIn: 'root',
})
export class KeycloakWrapper extends Keycloak {}
@Injectable({
providedIn: 'root',
})
export class KeycloakService {
constructor(private keycloak: KeycloakWrapper) {}
//other methods and props
}
describe('KeycloakService', () => {
let service: KeycloakService;
const keycloakSpy: jasmine.SpyObj<KeycloakWrapper> = jasmine.createSpyObj(
'KeycloakWrapper',
['init']
);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
KeycloakService,
{ provide: KeycloakWrapper, useValue: keycloakSpy },
],
});
service = TestBed.inject(KeycloakService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
I found a solution, using @Inject and @Optional:
@Injectable({
providedIn: 'root',
})
export class KeycloakService {
constructor(@Inject(Keycloak) @Optional() private keycloak?: Keycloak) {
this.keycloak = keycloak || new Keycloak();
this.keycloak.init();
}
//other methods and props
}
describe('KeycloakService', () => {
let service: KeycloakService;
const keycloakSpy: jasmine.SpyObj<Keycloak> = jasmine.createSpyObj(
'Keycloak',
['init']
);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: Keycloak, useValue: keycloakSpy },
KeycloakService,
],
});
service = TestBed.inject(KeycloakService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});