我正在学习如何使用AngularJS承诺,我在为它们编写单元测试时遇到了问题。我编写了一个带有factory
的模块,可以查询RSS提要,解析文章中的标题,并返回一个将解析标题字符串数组的promise。
以下是模块代码:
angular.module('rss', [])
.factory('rssService', ['$http', '$q', '$rootScope', function($http, $q,
$rootScope) {
return function(url) {
this.getTitles = function() {
var deferred = $q.defer(),
titles = [];
$http({
method: 'GET',
url: url
}).success(function(data) {
$(data).find('title').each(function(index, item) {
var title = $(item).text();
console.log(title);
titles.push(title);
});
deferred.resolve(titles);
});
return deferred.promise;
};
};
}])
;
这是我的Jasmine单元测试代码:
beforeEach(module('rss'));
describe("RSS Module", function() {
var rssService, $httpBackend;
beforeEach(inject(function(_rssService_, _$httpBackend_) {
rssService = new _rssService_('/rss');
$httpBackend = _$httpBackend_;
}));
it("Can parse an RSS Feed and get the titles", function(done) {
$httpBackend.expectGET('/rss').respond(mockRSS); // mock data declared earlier
var titles = rssService.getTitles();
$httpBackend.flush();
titles.then(function(data) {
console.log("Done!");
expect(data.length).toBe(17);
done();
});
});
});
当我运行单元测试时,出现以下错误:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
这是因为,虽然我的承诺得到了解决,但它没有调用$apply
这些更改。我读到的解决方案是在解决承诺后注入$rootScope
并调用$rootScope.$apply()
。这样做会导致以下错误
Error: [$rootScope:inprog] $digest already in progress
这是因为我在$rootScope.$apply()
块内调用了success()
,所以摘要已经发生了。
在单元测试之外(使用实际的RSS源),代码工作正常。控制器的范围处理摘要,并且按预期工作。
有什么方法吗?
答案 0 :(得分:4)
您的回答是关于flush
和then
的顺序。 flush
应该在定义回调之后。
此外,当您从$ httpBackend或$ timeout使用flush
时,无需使用done
:测试变为同步。所以测试可以是:
it("Can parse an RSS Feed and get the titles", function() {
$httpBackend.expectGET('/rss').respond(mockRSS); // mock data declared earlier
rssService.getTitles().then(function(data) {
expect(data.length).toBe(17);
});
$httpBackend.flush();
});
此外,我并不认为与您的问题直接相关,但是当您想要对$http
的结果进行后期处理时,通常无需手动创建承诺。使用then
的{{1}}回调代替自定义$http
,您可以使用标准承诺链:
success
答案 1 :(得分:0)
我找到的解决方案是移动我的代码行,在代码之后将$httpBackend
刷新到以声明then
块。发生的事情是GET请求被刷新,我的承诺将在我给它then
之前宣布并解决。
新的单元测试代码如下所示:
it("Can parse an RSS Feed and get the titles", function(done) {
$httpBackend.expectGET('/rss').respond(mockRSS);
var titles = rssService.getTitles();
titles.then(function(data) {
console.log("Done!");
expect(data.length).toBe(17);
done();
});
$httpBackend.flush();
});
我没有必要更改任何实际的模块代码,但如果有更好的方法使用链式承诺来编写它,我会很奇怪。非常欢迎评论或其他答案。