How to Test Value Returned in Promise from AngularJS Controller with Jasmine?

血红的双手。 提交于 2019-12-01 09:01:26

Anything inside a then will not be run unless the promise callbacks are called - which is a risk for a false positive like you experienced here. The test will pass here since the expect was never run.

There are many ways to make sure you don't get a false positive like this. Examples:

A) Return the promise

Jasmine will wait for the promise to be resolved within the timeout.

  • If it is not resolved in time, the test will fail.
  • If the promise is rejected, the test will also fail.

Beware If you forget the return, your test will give a false positive!

describe('test the returned value from the promise', function() {
    return $scope.getTheData(someId)
    .then(function(result) {
        expect(result).toBe('something expected');
    });
});

B) Use the done callback provided by Jasmine to the test method

  • If done is not called within the timeout the test will fail.
  • If done is called with arguments the test will fail.
    The catch here will pass the error to jasmine and you will see the error in the output.

Beware If you forget the catch, your error will be swallowed and your test will fail with a generic timeout error.

describe('test the returned value from the promise', function(done) {
    $scope.getTheData(someId)
    .then(function(result) {
        expect(result).toBe('something expected');
        done();
    })
    .catch(done);
});

C) Using spies and hand cranking (synchronous testing)

If you're not perfect this might be the safest way to write tests.

it('test the returned value from the promise', function() {
    var
      data = { data: 'lots of data' },
      successSpy = jasmine.createSpy('success'),
      failureSpy = jasmine.createSpy('failure');

    $scope.getTheData(someId).then(successSpy, failureSpy);

    $httpBackend.expect('GET', 'the/url/to/my/data').respond(200, data);
    $httpBackend.flush();

    expect(successSpy).toHaveBeenCalledWith(data);
    expect(failureSpy).not.toHaveBeenCalled();
});

Synchronous testing tricks
You can hand crank httpBackend, timeouts and changes to scope when needed to get the controller/services to go one step further. $httpBackend.flush(), $timeout.flush(), scope.$apply().

In case there is a promise created by $q somewhere (and since you seem to be using $httpBackend, then this might well be the case), then my suggestions are to trigger a digest cycle after the call to getTheData, and make sure that the call to expect isn't in a then callback (so that if something is broken, the test fails, rather than just not run).

var prom = $scope.getTheData(someId);
$scope.$apply();
expect(result).toBe('something expected');

The reason this might help is that Angular promises are tied to the digest cycle, and their callbacks are only called if a digest cycle runs.

The problem you face is that you are testing async code (promise based) using synchronous code. That won't work, as the test has finished before the Thenable in your test code has started running the callback containing the test. The Jasmine docs specifically show how to test async code (same as Mocha), and the fix is very small:

describe('test the returned value from the promise', function(done) {
    var prom = $scope.getTheData(someId);
    prom.then(function(result) {
        expect(result).toBe('something expected');  //this code never runs
        done(); // the signifies the test was successful
    }).catch(done); // catches any errors
});
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!