Reputation: 2439
The following Unit Tests:
const mockActiveService1 = {
.......
}
const mockNotActiveService1 = {
.......
}
describe('Component1 ', () => {
let component: Component1 ;
let fixture: ComponentFixture<Component1>;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [Component1],
providers: [
{ provide: Service1, useValue: mockActiveService1 }
],
imports: [HttpClientTestingModule, ToastrModule.forRoot()]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(Component1);
component = fixture.componentInstance;
service1 = TestBeb.inject(Service1);
});
it('should load the current dataService2', async () => {
spyOn(service1, 'get').and.returnValue(of(mockActiveService1));
fixture.detectChanges();
await component.ngOnInit();
expect(service1.get).toHaveBeenCalled();
expect(component.dataService2).toEqual(mockActiveService1);
expect(component.showCountdown).toBeFalse();
expect(component.loading).toBeFalse();
});
it('should show info message and navigate to default route when there is no active dataService2', fakeAsync (() => {
const toastrServiceSpy = spyOn(toastrServiceMock, 'info');
const routerSpy = spyOn(routerMock, 'navigate');
const getSpy = spyOn(service1, 'get').and.callThrough().and.returnValue(of(mockNotActiveService1));
fixture.detectChanges();
component.ngOnInit();
tick();
expect(getSpy).toHaveBeenCalled();
expect(toastrServiceSpy).toHaveBeenCalledWith('no active dataService2', 'Info!');
expect(routerSpy).toHaveBeenCalledWith(['/default']);
}));
Both tests are returning the same error:
Error: Expected spy get to have been called.
at <Jasmine>
at UserContext.apply (src/app/features/home/home.component.spec.ts:70:37)
at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:409:30)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:303:43)
I cannot figure the reason out. I tried using different format to build the spy, but nothing looks work. Any clue to help me? The code of the component must call the get method as far as it is:
ngOnInit(): void {
this.loading = true;
this.showCountdown = false;
this.loadCurrentdataService2();
}
loadCurrentdataService2(): void {
this.service2.get(0).subscribe(results => {
if (results) {
this.dataService2= results;
if (this.dataService2 && this.dataService2.isActive) {
...............
I have checked the code and the service2.get(0) is being called properly. But if I added the next console.log to the unit test:
it('should load the current dataService2', fakeAsync(() => {
spyOn(service1, 'get').and.callThrough();
fixture.detectChanges();
component.ngOnInit();
tick();
console.log('service1.get.calls', (service1.get as any).calls);
expect(service1.get).toHaveBeenCalled();
expect(component.dataService2).toEqual(mockActiveService1);
expect(component.showCountdown).toBeFalse();
expect(component.loading).toBeFalse();
}));
I receive:
LOG: 'service1.get.calls', CallTracker{track: function(context) { ... }, any: function() { ... }, count: function() { ... }, argsFor: function(index) { ... }, thisFor: function(index) { ... }, all: function() { ... }, allArgs: function() { ... }, first: function() { ... }, mostRecent: function() { ... }, reset: function() { ... }, saveArgumentsByValue: function() { ... }}
I tried as well:
spyOn(service1, 'get').and.callFake(() => {
return of(mockActiveService1);
});
I can´t figure it out why get method is not being called in the unit test. Please, any help or hint will be really appreciated.
Upvotes: 0
Views: 2941
Reputation: 2439
Finally I found the answer. The problem was the service of the component wasn´t the same than the service injected in the test. To solve this, I force to the component´s service to be the same injecting it in the test by the constructor:
it('should load the current dataService2', fakeAsync(inject([Service1], (service1: Service1) => {
spyOn(service1, 'get').and.callFake(() => {
return of(mockService1);
});
sanitazer.bypassSecurityTrustUrl.and.callFake(input => input);
sanitazer.bypassSecurityTrustStyle.and.callFake(input => input);
sanitazer.bypassSecurityTrustHtml.and.callFake(input => input);
component = new HomeComponent(sanitazer, service1, router, toastrService);
component.ngOnInit();
tick(1000);
fixture.detectChanges();
expect(service1.get).toHaveBeenCalledWith(0);
expect(component.campaign).toEqual(mockService1);
expect(sanitazer.bypassSecurityTrustUrl).toHaveBeenCalledTimes(1);
expect(sanitazer.bypassSecurityTrustHtml).toHaveBeenCalledTimes(10);
expect(sanitazer.bypassSecurityTrustStyle).toHaveBeenCalledTimes(5);
expect(component.showCountdown).toBeTrue();
expect(component.loading).toBeFalse();
})));
Upvotes: 1
Reputation: 18859
Ok, try the following out, pay attention to comments with !!.
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
// !! Declare campaignServiceMock
let campaignServiceMock: jasmine.SpyObj<CampaignService>;
beforeEach(async () => {
// !! Assign campaignServiceMock to spyObject with the public methods
// it will have.
campaignServiceMock = = jasmine.createSpyObj<CampaignService>('CampaignService', ['get']);
// !! Add await here since compileComponents is asynchronous.
await TestBed.configureTestingModule({
declarations: [HomeComponent],
providers: [
// !! We are providing the mock campaignServiceMock for the real
// CampaignService
{ provide: CampaignService, useValue: campaignServiceMock }
],
imports: [HttpClientTestingModule, ToastrModule.forRoot()]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
});
// !! We can get rid of async here, it should not be needed
it('should load the current campaign', /* async */ () => {
// !! No need to spy anymore since we have a spy object
// spyOn(campaignService, 'get').and.returnValue(of(mockCampaign));
// !! Return mock value for the get
mockCampaignService.get.and.returnValue(of(mockCampaign));
// !! The first fixture.detectChanges() will call ngOnInit for us.
// Make sure you don't call fixture.detectChanges() anywhere above this one
fixture.detectChanges();
// !! No need to call ngOnInit anymore and also it is not asynchronous
// so no need for await
// await component.ngOnInit();
// !! The rest should work
expect(campaignService.get).toHaveBeenCalled();
expect(component.campaign).toEqual(mockCampaign);
expect(component.showCountdown).toBeFalse();
expect(component.loading).toBeFalse();
});
Here is how to test components depending on services: https://testing-angular.com/testing-components-depending-on-services/#testing-components-depending-on-services.
Upvotes: 2