Aj1
Aj1

Reputation: 973

Angular Jasmine test function call inside then

Here is my controller function I want to test.

  saveItem = (): void => {
    this.updateItem();
    this.loadingDialogService.showIndicator("Saving Item");        
this._editItemService.updateItem(this.item).then((updatedItem: Item) => {
            this.loadingDialogService.cancelDialog();
            this.goToMainView();
        }).catch(() => {
            this.loadingDialogService.showErrorDialog("Failed to Save Item");
            //this._log.error("Error CallingItemService");
        });
    }

Here is my test:

it("should call method saveItem", () => {
            spyOn(controller, 'updateItem');
            spyOn(loadingDialogService, 'showIndicator');
            spyOn(editItemService, 'updateItem').and.callFake(() => {
                let result: Item
                deferred.resolve(result);  
                return deferred.promise;              
            });
            spyOn(loadingDialogService, 'cancelDialog');
            spyOn(controller, 'goToMainView');
            controller.saveItem();
            expect(controller.updateItem).toHaveBeenCalled();
            expect(loadingDialogService.showIndicator).toHaveBeenCalled();
            expect(_editItemService.updateItem).toHaveBeenCalled();
            expect(loadingDialogService.cancelDialog).toHaveBeenCalled();
            expect(controller.goToMainView).toHaveBeenCalled();
        });

The test fails at the last two expects, throws error saying that

Expected spy cancelDialog to have been called.

Expected spy goToMainView to have been called.

I guess the test doesn't execute the functions inside then function. Could some one point out where the mistake is?

Upvotes: 6

Views: 6773

Answers (2)

Boris Yakubchik
Boris Yakubchik

Reputation: 4453

Solution:

run test inside fakeAsync and run tick() before the expect

Service:

getFirebaseDoc() {   
  this.db.firestore.doc('some-doc').get()
    .then(this.getFirebaseDocThen)
    .catch(this.getFirebaseDocCatch);
}

Unit testing:

it('should call getFirebaseDocThen', fakeAsync(() => {            // note `fakeAsync`
    spyOn(service, 'getFirebaseDocThen');
    spyOn(service.db.firestore, 'doc').and.returnValue({
      get: (): any => {
        return new Promise((resolve: any, reject: any): any => {
          return resolve({ exists: true });
        });
      },
    });
    service.getFirebaseDoc();
    tick();                                                       // note `tick()`
    expect(service.getFirebaseDocThen).toHaveBeenCalled();
}));

Upvotes: 1

MBielski
MBielski

Reputation: 6620

You have a promise to resolve, so you need to run a digest cycle after your function call but before your tests.

it("should call method saveItem", () => {
        spyOn(controller, 'updateItem');
        spyOn(loadingDialogService, 'showIndicator');
        spyOn(editItemService, 'updateItem').and.callFake(() => {
            let result: Item
            deferred.resolve(result);  
            return deferred.promise;              
        });
        spyOn(loadingDialogService, 'cancelDialog');
        spyOn(controller, 'goToMainView');
        controller.saveItem();
        $scope.$digest();
        expect(controller.updateItem).toHaveBeenCalled();
        expect(loadingDialogService.showIndicator).toHaveBeenCalled();
        expect(_editItemService.updateItem).toHaveBeenCalled();
        expect(loadingDialogService.cancelDialog).toHaveBeenCalled();
        expect(controller.goToMainView).toHaveBeenCalled();
    });

Having said that, your test is going to lead you to problems later because it has 5 assertions (expect()s) in it. When one fails you will have to waste time figuring out which one it is. Stick to One Assertion Per Test (OAPT.) This should be 5 tests with one assertion each. That way, when something fails you know right what it is.

Upvotes: 6

Related Questions