admin管理员组文章数量:1277372
I have a controller that expose a function that returns some text after a rest call. It works fine, but I'm having trouble testing it with Jasmine. The code inside the promise handler in the test never executes.
The controller:
/* global Q */
'use strict';
angular.module('myModule', ['some.service'])
.controller('MyCtrl', ['$scope', 'SomeSvc', function ($scope, SomeSvc) {
$scope.getTheData = function (id) {
var deferred = Q.defer();
var processedResult = '';
SomeSvc.getData(id)
.then(function (result) {
//process data
processedResult = 'some stuff';
deferred.resolve(processedResult);
})
.fail(function (err) {
deferred.reject(err);
});
return deferred.promise;
}
}]);
The test:
describe('some tests', function() {
var $scope;
var $controller;
var $httpBackend;
beforeEach(function() {
module('myModule');
inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
$scope = _$rootScope_.$new();
$controller = _$controller_;
$httpBackend = _$httpBackend_;
//mock the call that the SomeSvc call from the controller will make
$httpBackend.expect('GET', 'the/url/to/my/data');
$httpBackend.whenGET('the/url/to/my/data')
.respond({data:'lots of data'});
$controller ('MyCtrl', {
$scope: $scope
});
});
});
describe('test the returned value from the promise', function() {
var prom = $scope.getTheData(someId);
prom.then(function(result) {
expect(result).toBe('something expected'); //this code never runs
})
});
});
I have a controller that expose a function that returns some text after a rest call. It works fine, but I'm having trouble testing it with Jasmine. The code inside the promise handler in the test never executes.
The controller:
/* global Q */
'use strict';
angular.module('myModule', ['some.service'])
.controller('MyCtrl', ['$scope', 'SomeSvc', function ($scope, SomeSvc) {
$scope.getTheData = function (id) {
var deferred = Q.defer();
var processedResult = '';
SomeSvc.getData(id)
.then(function (result) {
//process data
processedResult = 'some stuff';
deferred.resolve(processedResult);
})
.fail(function (err) {
deferred.reject(err);
});
return deferred.promise;
}
}]);
The test:
describe('some tests', function() {
var $scope;
var $controller;
var $httpBackend;
beforeEach(function() {
module('myModule');
inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
$scope = _$rootScope_.$new();
$controller = _$controller_;
$httpBackend = _$httpBackend_;
//mock the call that the SomeSvc call from the controller will make
$httpBackend.expect('GET', 'the/url/to/my/data');
$httpBackend.whenGET('the/url/to/my/data')
.respond({data:'lots of data'});
$controller ('MyCtrl', {
$scope: $scope
});
});
});
describe('test the returned value from the promise', function() {
var prom = $scope.getTheData(someId);
prom.then(function(result) {
expect(result).toBe('something expected'); //this code never runs
})
});
});
Share
Improve this question
edited Oct 12, 2014 at 15:03
Bergi
665k161 gold badges1k silver badges1.5k bronze badges
asked Oct 11, 2014 at 22:22
binarygiantbinarygiant
6,42210 gold badges52 silver badges73 bronze badges
7
- What are the symptoms? Any errors? – alecxe Commented Oct 11, 2014 at 22:39
- @alecxe the symptoms are that the code inside the then never runs, so I cant make an assertion. Updated the code and question to better reflect the issue. Thank you. – binarygiant Commented Oct 11, 2014 at 23:04
-
1
Any reason why you're using
Q
and not$q
? And what sort of promise doesSomeSvc.getData
return: one created using$q
(such as those returned from$http
), or one fromQ
)? – Michal Charemza Commented Oct 12, 2014 at 7:37 - This project uses big Q rather than the provided $q with Angular, because $q was missing something that Q provided. The design decision was made a while back, and I dont recall the details at the moment. Both promises in question return a promise created by big Q. – binarygiant Commented Oct 12, 2014 at 12:34
- Looks like you use the Deferred antipattern (although hardly the reason for your problems) – Bergi Commented Oct 12, 2014 at 15:05
3 Answers
Reset to default 9Anything 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
});
本文标签: javascriptHow to Test Value Returned in Promise from AngularJS Controller with JasmineStack Overflow
版权声明:本文标题:javascript - How to Test Value Returned in Promise from AngularJS Controller with Jasmine? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741282169a2370068.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论