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
HomeServiceto your component via the component'sprovidersarray, the component is not getting the instance ofHomeServicecreated at the module level—the one you replaced withuseValue—it's getting its own, separate instance.If you remove
HomeServicefrom AppComponent'sprovidersarray, your original setup withoutoverrideProvidershould work.From the docs: