I'm trying to understand why my test doesn't work as expected. Here is the component:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [HomeService]
})
export class AppComponent {
title = 'testing-angular';
constructor (private service: HomeService ) {}
CallHomeService(): void {
this.service.DoStuff();
}
}
and here the service:
@Injectable()
export class HomeService {
constructor() { }
DoStuff(): void {
console.log('homeService has been called')
}
}
I'd like only to check if DoStuff
has been called, so I did this test:
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let appComponent: AppComponent;
let homeServiceMock: jasmine.SpyObj<HomeService>; // Use jasmine.SpyObj type for mock
beforeEach(() => {
// Create a spy object for HomeService
homeServiceMock = jasmine.createSpyObj('HomeService', ['DoStuff']);
TestBed.configureTestingModule({
declarations: [AppComponent],
providers: [{provide: HomeService, useValue: homeServiceMock}] // Provide the spy object directly
});
fixture = TestBed.createComponent(AppComponent);
appComponent = fixture.componentInstance;
});
it('should call DoStuff() when CallHomeService() is called', () => {
// Call the CallHomeService() method of AppComponent
appComponent.CallHomeService();
// Expect that DoStuff() was called on the mock HomeService
expect(homeServiceMock.DoStuff).toHaveBeenCalled(); // Use toHaveBeenCalled() matcher
});
});
But I get
AppComponent should call DoStuff() when CallHomeService() is called FAILED
Expected spy HomeService.DoStuff to have been called.
at <Jasmine>
at UserContext.apply (src/app/app.component.spec.ts:28:37)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:375:26)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:287:39)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:374:52)
So I tried using the TestBed.overrideProvider and it worked:
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { HomeService } from './service/home.service';
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let appComponent: AppComponent;
let homeService: jasmine.SpyObj<HomeService>; // Use jasmine.SpyObj type for mock
beforeEach(() => {
// Create a mock object for HomeService
const homeServiceMock = jasmine.createSpyObj('HomeService', ['DoStuff']);
TestBed.configureTestingModule({
declarations: [AppComponent],
providers: []
});
// Override the provider for HomeService with the mock
TestBed.overrideProvider(HomeService, { useValue: homeServiceMock });
fixture = TestBed.createComponent(AppComponent);
appComponent = fixture.componentInstance;
});
it('should call DoStuff() when CallHomeService() is called', () => {
// Call the CallHomeService() method of AppComponent
appComponent.CallHomeService();
// Expect that DoStuff() was called on the mock HomeService
expect(homeService.DoStuff).toHaveBeenCalled();
});
});
I saw here that if I use providers
in the ts component it will override my Spy, but I still do not understand why my useValue is being override. It is not supposed only to provide the mock service in providers
of TestBed.configureTestingModule
with useValue
to call the mock service? Thanks for any help.
Since you're providing HomeService
to your component via the component's providers
array, the component is not getting the instance of HomeService
created at the module level—the one you replaced with useValue
—it's getting its own, separate instance.
If you remove HomeService
from AppComponent's providers
array, your original setup without overrideProvider
should work.
Angular DI has a hierarchical injection system, which means that nested injectors can create their own service instances. Whenever Angular creates a new instance of a component that has providers specified in @Component(), it also creates a new child injector for that instance.
Child modules and component injectors are independent of each other, and create their own separate instances of the provided services.