使用AngularJS,我试图对一个多次调用$ http的函数进行单元测试.
我的测试看起来像这样:
it('traverses over a hierarchical structure over multiple chained calls', function() { myService.traverseTheStuff() .then(function(theAggregateResult) { // ...is never fulfilled }); $httpBackend.flush(); });
其他单调用测试将注册传递给.then()的回调,并在我调用.flush()后立即执行.
测试中的代码看起来像这样.
function traverseTheStuff(){ // This will make a call to $http to fetch some data return getRootData() // It is fulfilled at the end of the test when I $httpBackend.flush() .then(function(rootData){ // Another call to $http happens AFTER $httpBackend.flush() return getNextLevel(rootData.someReference); }) // The second promise is never fulfilled and the test fails .then(function(nextLevel){ return aggregateTheStuff(...); }); }
值得一提的是,每个单独的呼叫都是单独测试的.在这里,我想遍历一棵树,聚合一些数据和单元测试a)承诺链正确连接和b)聚合是准确的.将其展平为单独的离散调用已经完成.
我是测试Angular的初学者,但是我已经设置了一个plnkr,用一个成功的"秒"然后/保证调用来测试与你的设置非常相似的设置
http://plnkr.co/edit/kcgWTsawJ36gFzD3CbcW?p=preview
以下代码片段是上述plnkr的略微简化版本.
我发现的关键点是
我注意到函数traverseTheStuff根本没有调用$ http/$ httpBackend.它只使用$ q promises中定义的函数,因此测试假设使用$ q,并注入它
var deferred1 = null; var deferred2 = null; var $q = null; beforeEach(function() { inject(function(_$q_) { $q = _$q_; }); }); beforeEach(function() { deferred1 = $q.defer(); deferred2 = $q.defer(); }
异步调用的函数使用其promise返回值进行间谍/存根,其中promise在测试本身中创建,因此在测试traverseTheStuff时不会调用它们的实际实现
spyOn(MyService,'traverseTheStuff').andCallThrough(); spyOn(MyService,'getRootData').andReturn(deferred1.promise); spyOn(MyService,'getNextLevel').andReturn(deferred2.promise); spyOn(MyService,'aggregateTheStuff');
在测试中没有任何"then"的调用,只是为了"解析"测试中创建的promise,然后是$ rootScope.$ apply(),然后实际调用traverseTheStuff中的"then"回调,我们也可以测试一下
beforeEach(function() { spyOn(deferred1.promise, 'then').andCallThrough(); }); beforeEach(function() { deferred1.resolve(testData); $rootScope.$apply(); // Forces $q.promise then callbacks to be called }); it('should call the then function of the promise1', function () { expect(deferred1.promise.then).toHaveBeenCalled(); });
必须解决每个承诺/ $ apply-ed以调用链中的下一个"then"函数.所以.要获得测试调用aggregateTheStuff(或者更确切地说,它的存根),还必须解析从getNextLevel存根返回的第二个promise:
beforeEach(function() { deferred2.resolve(testLevel); $rootScope.$apply(); // Forces $q.promise then callbacks to be called }); it('should call aggregateTheStuff with ' + testLevel, function () { expect(MyService.aggregateTheStuff).toHaveBeenCalledWith(testLevel); });
上述所有问题都是它假定来自$ q和$ rootScope的某些行为.我是在理解单元测试这样的假设不应该做出这样的假设,以便真正只测试一点代码.我没有弄清楚如何解决这个问题,或者我是否误解了.