Ketu
Ketu

Reputation: 1718

How to test angular async function which has await statement in the code block

I am trying to test a simple function in angular using karma and jasmine.

class

export class Acl {
  async caller() {
    console.log("first statement");
    const calledMe = await this.callMe().toPromise();
    return calledMe;
  }

  callMe() {
    return new Observable((observer) => observer.next({ something: "key" }));
  }
}

test file

import { Acl } from "./main";
import { fakeAsync, tick } from "@angular/core/testing";
import { Observable } from "rxjs";

describe("Hello world", () => {
  it("test the async", fakeAsync(() => {
    const t = new Acl();
    spyOn(t, "callMe").and.returnValue(
      new Observable((observer) => observer.next({ something: "key" }))
    );

    const cl = t.caller();

    console.log("a print ", cl);
    tick();
  }));
});

the output for the print statement in the test case is enter image description here

How to test such functions.

Upvotes: 1

Views: 811

Answers (2)

Coderer
Coderer

Reputation: 27294

The test shown in the other answer is correctly written and should work -- the expect statement should fire and pass.

If it's not passing, it could be due to this Zone.js issue. If your tests are running as native async/await (target ES2017 or higher), Zone cannot hook your await statement, so tick() will not cause it to proceed. You can test this by putting logging statements immediately before and after the await line and the tick() line:

  async caller() {
    console.log("before await");
    const calledMe = await this.callMe().toPromise();
    console.log("after await");
    return calledMe;
  }

  // ...

  it("test the async", fakeAsync(() => {
    const t = new Acl();
    const spy = spyOn(t, "callMe").and.returnValue(
      new Observable((observer) => observer.next({ something: "key" }))
    );

    t.caller();
    console.log("before tick");
    tick();
    console.log("after tick");

    expect(spy).toHaveBeenCalled()
  }));

If you run this with target: "es2015" in your tsconfig, you should see "before await", "before tick", "after await", "after tick", then your test should pass. If you run it with target: "es2017" or later, like 2018/2020/esnext, you should see "before await", "before tick", "after tick", then your test should fail, then "after await" will probably log after the test body function has totally finished executing.

As an aside, this drove me crazy for about 2 days. Users of the ng CLI get a warning when they try to go past es2015, but not if you compile your project using the AngularCompilerPlugin webpack plugin directly, though that should be fixed soon.

Upvotes: 0

ggradnig
ggradnig

Reputation: 14189

Your test should be made up of at least one expectation, else you aren't testing for a specific result but just that the code runs without errors.

In your case I think you could store the spy as a constant and then expect that it has been called after tick, like that:

describe("Hello world", () => {
  it("test the async", fakeAsync(() => {
    const t = new Acl();
    const spy = spyOn(t, "callMe").and.returnValue(
      new Observable((observer) => observer.next({ something: "key" }))
    );

    t.caller();
    tick();

    expect(spy).toHaveBeenCalled()
  }));
});

Upvotes: 1

Related Questions