KL_KISNE_DEKHA_HAI
KL_KISNE_DEKHA_HAI

Reputation: 689

Unit Testing location in async-await in angular

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

Answers (3)

KL_KISNE_DEKHA_HAI
KL_KISNE_DEKHA_HAI

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

Andrei
Andrei

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

AliF50
AliF50

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

Related Questions