Jasmine问题:AngularJS服务单元测试中异步调用无法获取响应
解决AngularJS Jasmine异步服务测试无法获取响应的问题
看起来你在测试AngularJS服务的异步方法时踩了几个典型的异步测试陷阱,我来帮你一步步排查和修复:
你的测试代码里的关键问题
- 重复调用
done():在then、catch、finally里都调用了done(),这会导致测试在第一个回调触发时就提前结束,哪怕then里的断言还没执行完成。比如promise被resolve后,then和finally都会触发,两次调用done()会导致测试报错或者结果不可靠。 $rootScope.$apply()的时机可能不对:Angular的promise依赖digest循环来触发回调,如果你是手动模拟promise的resolve,需要在resolve之后调用$apply();如果用$httpBackend,则需要调用$httpBackend.flush()来触发响应。- 缺少对依赖服务的模拟:如果
getData()是通过$http或者其他异步服务获取数据,测试时必须模拟这些依赖的响应,否则真实的异步请求不会在测试环境中完成。
修正后的测试用例示例
假设你的service.getData()依赖$http,我们用$httpBackend来模拟响应,同时修复done()的问题:
describe('#getData', function () { // 注入测试所需的服务 beforeEach(inject(function (_service_, _$rootScope_, _$httpBackend_) { service = _service_; $rootScope = _$rootScope_; $httpBackend = _$httpBackend_; // 模拟getData对应的HTTP请求 $httpBackend.whenGET('/api/data').respond(mockData); })); afterEach(function () { // 确保所有模拟的请求都被处理 $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); it('Gets the data', function (done) { var promise = service.getData(); // 触发模拟的HTTP响应 $httpBackend.flush(); promise.then(function (resp) { expect(resp).toEqual(mockData); done(); // 只在成功断言后调用done }).catch(function (error) { // 如果进入catch,测试应该失败,用fail()标记后再结束 fail('getData promise rejected unexpectedly: ' + error); done(); }); }); });
关键修复点说明
- 移除重复的
done()调用:只在断言完成后调用一次done(),catch分支触发时说明测试失败,用fail()标记后再调用done()结束测试。 - 使用
$httpBackend模拟异步请求:对于依赖$http的服务,必须用$httpBackend拦截并响应请求,再调用flush()同步触发响应,这样promise会立即resolve。 - 手动触发digest循环(针对自定义promise):如果你的
getData()是用$q手动创建的promise,比如:
那测试时需要在调用this.getData = function() { var deferred = $q.defer(); // 模拟异步操作 setTimeout(function() { deferred.resolve(mockData); }, 0); return deferred.promise; };getData()后,手动调用$rootScope.$apply()来触发promise的回调:it('Gets the data', function (done) { var promise = service.getData(); $rootScope.$apply(); // 触发digest循环,让promise回调执行 promise.then(function (resp) { expect(resp).toEqual(mockData); done(); }).catch(function () { fail('Promise rejected unexpectedly'); done(); }); });
额外简化技巧
如果你用的是Jasmine 2.x及以上版本,其实可以不用done(),直接返回promise来处理异步测试,代码会更简洁:
it('Gets the data', function () { var promise = service.getData(); $httpBackend.flush(); return promise.then(function (resp) { expect(resp).toEqual(mockData); }); });
Jasmine会自动等待promise完成,如果promise被reject,测试会自动标记为失败。
内容的提问来源于stack exchange,提问作者31piy
相关产品推荐
相关产品推荐

