angulartestingpromisejasminebeforeinstallprompt

How can I test the BeforeInstallPromptEvent in Jasmine/Angular?


I'm writing a test for a dialog component, that handles the PWA install event prompted manually when a function is called. It will then resolve the promise coming back from the BeforeInstallPromptEvent.userChoice property.

However when I try to test this function I get an error from jasmine:

'userChoice.then is not a function'

Which I understand since I tried spying on that with a spyOn to then check the result.

I tried a few other things with Promises, since the userChoice returns a resolved promise (https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent) but nothing has worked so far.

This is the function I am trying to test. The BeforeInstallPromptEvent is stored in the installEvent object that gets passed down to the component from the app.ts and then stored in data.

this.data.installEvent.userChoice
            .then((choiceResult) => {
                if (choiceResult.outcome === 'accepted') {
                    doSomething();

In my test spec I created a mockData object with the following:

const mockData = {
  installEvent: {
    prompt: () => {},
    userChoice: Promise.resolve().then(() => {
      return { choiceResult: { outcome: "accepted" } };
    }),
  },
};

My test spec so far (after many different tries) is either this:

it("should do something after user accepted", fakeAsync(() => {
 mockData.installEvent.userChoice.then((result) => {
      return result.choiceResult.outcome;
//this test returns true
      expect(result.choiceResult.outcome).toEqual("accepted");
    }); 
flush();
//but the function gets never called
expect(component.doSomething).toHaveBeenCalled();
});

Or with the spyOn function on the userChoice that produces the error:

it("should do something after user accepted", () => {
spyOn(mockData.installEvent, "userChoice").and.returnValue("accepted");

expect(mockData.installEvent.userChoice).toBe("accepted)
expect(component.doSomething).toHaveBeenCalled();
});

With that I managed to already test that the data.installEvent.prompt function is getting called.

But I don't know how to test that the userChoice Promise resolves to "accepted" and then triggers the if condition.

Please help me. Since I am a beginner regarding testing (in Angular) it would be nice to get an explanation of the method behind testing such Promises.

Thank you very much in advance!


Solution

  • The issue is you're resolving the promise already in mockData and the spy is not returning a promise.

    Method 1

    const mockData = {
      installEvent: {
        prompt: () => {},
        userChoice: Promise.resolve({ choiceResult: { outcome: "accepted" } }),
      },
    };
    
    it("should do something after user accepted", fakeAsync(() => {
     mockData.installEvent.userChoice.then((result) => {
          // return result.choiceResult.outcome;, remove return here and only test the assertion
    //this test returns true
          expect(result.choiceResult.outcome).toEqual("accepted");
          expect(component.doSomething).toHaveBeenCalled(); // make sure doSomething gets called
        }); 
    flush(); // flush the promises
    });
    

    Method 2 (can't be done)

    it("should do something after user accepted", () => {
      // you can't spy on userChoice since you can only spy on methods/functions and not variables
      spyOn(mockData.installEvent, "userChoice").and.returnValue("accepted");
    
      expect(mockData.installEvent.userChoice).toBe("accepted)
      expect(component.doSomething).toHaveBeenCalled();
    });
    

    Method 2 can't be done because you can only spyOn methods and functions and not properties.