DJDMorrison
DJDMorrison

Reputation: 1319

Jasmine spyOn reporting functions have been called when they haven't

I have the following code:

$scope.deleteJob = function(job) {
    SandboxService.deleteJob(job.id).then(res => {
        if (res.status == 200) {
            ngToast.success();
            $scope.refreshApps();
        }
        else {
            ngToast.danger();
        }
    });
};

And the following unit test:

it('should show success toast on delete and refresh apps', () => {
    spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 500}));
    spyOn(ngToast, 'success');
    spyOn(scope, 'refreshApps');

    let mockJob = {
        'id': 1
    };

    scope.deleteJob(mockJob);

    sandboxService.deleteJob().then(() => {
        expect(ngToast.success).toHaveBeenCalled();
        expect(scope.refreshApps).toHaveBeenCalled();
    });
});

Basically when deleting a job, if the delete was a success with a returned status of 200 then show a success toast and refresh, else show a danger toast.

I expect the test to fail, as it returns a status of 500, but it passes. This implies that ngToast.success() and scope.refreshApps() have been called.

I added some logs to the code, and it does return status: 500 and go to the else block.

What am I missing here?

Upvotes: 0

Views: 255

Answers (2)

DJDMorrison
DJDMorrison

Reputation: 1319

@uminder's answer pointed out that the test was finishing before the expect functions were even called due the asynchronous nature - verified by adding some logs inside the test.

The solution was to add an argument to the test that is to be called when the test has finished: https://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support

it('should show success toast on delete and refresh apps', (done) => {
    spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 200}));
    spyOn(ngToast, 'success');
    spyOn(scope, 'refreshApps');

    let mockJob = {
        'id': 1
    };

    scope.deleteJob(mockJob);

    sandboxService.deleteJob().then(() => {
        expect(ngToast.success).toHaveBeenCalled();
        expect(scope.refreshApps).toHaveBeenCalled();
        done();
    });
});

Upvotes: 0

uminder
uminder

Reputation: 26190

The problem is related to the asynchronous nature of deleteJob. Your it test ends even before expect is performed. Therefore you need some sort of synchronization. This could basically be done with fakeAsync and tick from @angular/core/testing.

it('should show success toast on delete and refresh apps', fakeAsync(() => {
    ...

    sandboxService.deleteJob();
    tick();

    expect(ngToast.success).toHaveBeenCalled();
    expect(scope.refreshApps).toHaveBeenCalled();
}));

The problem however is that you're overwriting the original behaviour of deleteJob with the spy below, hence ngToast.success and scope.refreshApps won't be called and the test will fail.

 spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 500}));

Upvotes: 1

Related Questions