Reputation: 689
I am using Angular 9+ with karma test runner and jasmine test framework for unit tests.
I wanted to unit test only app component
which has a dependency injection:
app.component.ts
import { Component, EmbeddedViewRef } from '@angular/core';
import * as moment from 'moment-timezone';
import { OtherServiceService } from './other-service.service';
import { Location } from '@angular/common';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private sampleService: OtherService, private location: Location){}
async func(){
await this.sampleService.func2().toPromise();
console.log('Async call completed');
this.location.go('/index.html');
}
}
other-service.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class OtherServiceService {
constructor(private http: HttpClient) { }
func2(){
return this.http.post('' , null);
}
}
The unit test i have tried so far:
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, async, fakeAsync, tick } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { AppComponent } from './app.component';
import { OtherServiceService } from './other-service.service';
const locationStub = {
go: jasmine.createSpy('go')
}
describe('AppComponent', () => {
let otherServiceStub;
let fixture, component;
beforeEach(async(() => {
otherServiceStub = jasmine.createSpyObj(['func2']);
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
providers: [
{provide: OtherServiceService , useValue: otherServiceStub},
{provide: Location, useValue: locationStub}
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
});
it('should create the app', () => {
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it('should check for correct logs',fakeAsync(() => {
otherServiceStub.func2.and.returnValue(of('Garbage'));
const location = fixture.debugElement.injector.get(Location);
let spyObj = spyOn(console,'log');
component.func();
tick();
expect(spyObj).toHaveBeenCalledWith('Async call completed');
expect(location.go).toHaveBeenCalledWith('/index.html');
}));
});
On running ng test --code-coverage, it always shows error Expected spy go to have been called with: [ '/index.html' ] but it was never called.
.
On the coverage report, it shows the line as covered (this.location.go) , but the test fails, and I can't seem to understand why. I have also taken help from this, but to no success.
UPDATE 1.0
:
I have tried as suggested by @AliF50:
async func(){
await this.sampleService.func2().toPromise();
console.log('Async call completed');
this.location.go('/index.html');
console.log('After location go in component');
}
it('should check for correct logs',fakeAsync(() => {
otherServiceStub.func2.and.returnValue(of('Garbage'));
let spyObj = spyOn(console,'log');
component.func();
tick();
const location = fixture.debugElement.injector.get(Location);
expect(spyObj).toHaveBeenCalledWith('Async call completed');
console.log('Before location go in test');
expect(location.go).toHaveBeenCalledWith('/index.html');
console.log('After location go in test');
}));
But to my suprise none of the console logs
are being printed, even though it shows lines as covered.
UPDATE 2.0
:
As suggested by @AliF50:
it('should check for correct logs',fakeAsync(() => {
otherServiceStub.func2.and.returnValue(of('Garbage'));
let spyObj = spyOn(console,'log').and.callThrough();
component.func();
tick();
const location = fixture.debugElement.injector.get(Location);
expect(spyObj).toHaveBeenCalledWith('Async call completed');
console.log('Before location go in test');
expect(location.go).toHaveBeenCalledWith('/index.html');
console.log('After location go in test');
}));
All the console logs
are being printed, but the error still remains.
UPDATE 3.0
:
Any luck anyone? It is still an open question
UPDATE 4.0
:
Thanks to Andrei. Question is solved now.
Upvotes: 2
Views: 4687
Reputation: 689
Finally after so much time, and @Andrei
's contribution, I was able to come up with the answer to this question:
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, async, fakeAsync, tick } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { AppComponent } from './app.component';
import { OtherServiceService } from './other-service.service';
import { Location } from '@angular/common';
fdescribe('AppComponent', () => {
let otherServiceStub;
let fixture, component;
beforeEach(async(() => {
otherServiceStub = jasmine.createSpyObj(['func2']);
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
providers: [
{provide: OtherServiceService , useValue: otherServiceStub},
Location
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
});
it('should create the app', () => {
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it('should check for correct logs',async () => {
otherServiceStub.func2.and.returnValue(of('Garbage'));
let locationObj = TestBed.inject(Location);
let spyObj = spyOn(locationObj,'go');
await component.func();
expect(spyObj).toHaveBeenCalledWith('/index.html');
});
});
The Unit test is passing now with all the lines are being shown as covered.
Upvotes: 0
Reputation: 12206
there is a problem with promises and async await in js, they can't be synchronised by any means. So if your code involves using async/await the correct way would be to make your test function also async.
it('should check for correct logs',async () => {
otherServiceStub.func2.and.returnValue(of('Garbage'));
let spyObj = spyOn(console,'log').and.callThrough();
await component.func();
expect(spyObj).toHaveBeenCalledWith('Async call completed');
});
whole test with location check should look like
const locationStub = {
go: jasmine.createSpy('go')
}
...
it('should check for correct navigation', async () => {
otherServiceStub.func2.and.returnValue(of('Garbage'));
await component.func();
const location = TestBed.get(Location); // or just locationStub can be referred directly here
expect(location.go).toHaveBeenCalledWith('/index.html');
});
Upvotes: 1
Reputation: 18879
Try this:
describe('App component' , () => {
let otherServiceStub;
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
beforeEach(async(() => {
otherServiceStub = jasmine.createSpyObj(['func2']);
TestBed.configureTestingModule({
declarations: [AppComponent],
providers: [{
provide: OtherService , useValue: otherServiceStub
}]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
});
it('should check for correct logs',fakeAsync(() => {
otherServiceStub.func2.and.returnValue(of('Garbage'));
let spyObj = spyOn(console,'log');
component.func();
tick(); // remove the 1000 to remove time elapsing of 1000ms but instead
// to wait until all promises resolve
expect(spyObj).toHaveBeenCalledWith('Async call completed');
}));
});
Upvotes: 0