AngularJS $httpBackend 装饰器后单元测试中断
AngularJS Unit tests broken after $httpBackend decorator
所以我遇到了这样一种情况,我们不得不点击一个不受我们控制的 "restful" 服务,为了在 GET 调用中从服务中返回 json,我们必须通过Content-Type="application/json"中的header。唯一的问题是 Angular 从 GET 请求 header 中剥离了 Content-Type。我发现一个博客 post 建议在 $httpBackend 上使用装饰器,它允许我们在调用发送之前拦截它并添加回内容类型:
angular
.module('MyApp')
.decorator('$httpBackend', [
'$delegate', function($delegate) {
return function() {
var contentType, headers;
headers = arguments[4];
contentType = headers != null ? headers['X-Force-Content-Type'] : null;
if (contentType != null && headers['Content-Type'] == null)
headers['Content-Type'] = contentType;
return $delegate.apply(null, arguments);
};
}]);
所以,效果很好!现在我们的问题是它破坏了我们使用模拟 $httpBackend 服务的所有单元测试。我们得到的唯一错误是 "undefined".
例如。单元测试方法:
it('should return service.model.error if service returns an exception code from EndProject',
inject(function($httpBackend) {
var mockResponse = sinon.stub({ 'exception': 'Unable to retrieve service data' });
$httpBackend.whenPUT(this.endProjectUrl).respond(mockResponse);
var data;
this.service.EndProject().then(function(fetchedData) {
data = fetchedData;
});
$httpBackend.flush();
expect(data.error.state).toBe(true);
expect(data.error.message).toEqual('Unable to retrieve service data');
}));
PhantomJS 2.1.1 (Mac OS X 0.0.0) projectService EndProject should return service.model.error if service returns an exception code from EndProject FAILED
undefined
/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/src/app/components/services/project/projectService.spec.js:213:41
invoke@/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/bower_components/angular/angular.js:4560:22
workFn@/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/bower_components/angular-mocks/angular-mocks.js:2518:26
列出的装饰器涵盖了简单的猴子修补场景,其中修补函数不是构造函数并且没有静态属性和方法。
$httpBackend
in ng
module也是如此,它只是一个没有额外属性的工厂函数。
对于覆盖 $httpBackend
并具有静态方法 at least some of them are documented.
的 ngMock
和 ngMockE2E
模块,情况并非如此
这意味着猴子修补工厂函数的一般安全方法(它不包括不可枚举和继承的属性)是
app.decorator('$httpBackend', ['$delegate', function ($delegate) {
var $httpBackend = function () {
...
return $delegate.apply(null, arguments);
};
angular.extend($httpBackend, $delegate);
return $httpBackend;
}]);
无论如何,将应用程序模块化到可以单独测试单元而没有过多移动部件的级别是一个好习惯(这个问题是一个很好的例子,为什么这很重要)。 app
(在生产中自举)、app.e2e
(在 e2e 测试中自举)、app.common
(公分母)、app.unitA
(在 [=18= 中加载)很方便] 并且可以在单元测试中单独加载)等
大部分应用程序范围的代码(config
和 run
块、路由)可能会移动到单独的模块中,并且只加载到直接依赖它们的模块中。除非这是测试 decorator
单元本身的规范,否则不应加载装饰器模块。
另请注意,在调试规范错误时 比 PhantomJS 好。
虽然我将 estus 的答案标记为解决方案,但完全基于我的问题...最终,最终这不是我们最终的结果。在不能透过树木看到森林的情况下,最简单的解决方案是向 $http 调用的配置添加一个空数据元素。我之前尝试过,但它没有用(或者看起来是这样),但在再次尝试之后,它确实起作用了,我们能够从应用程序中删除装饰器。
return $http.get(getItemInformationUrl + params, { dataType: 'json', data: '', headers: {'Content-Type': 'application/json'} }).then(getItemInformationCompleted).catch(getItemInformationFailed);
所以我遇到了这样一种情况,我们不得不点击一个不受我们控制的 "restful" 服务,为了在 GET 调用中从服务中返回 json,我们必须通过Content-Type="application/json"中的header。唯一的问题是 Angular 从 GET 请求 header 中剥离了 Content-Type。我发现一个博客 post 建议在 $httpBackend 上使用装饰器,它允许我们在调用发送之前拦截它并添加回内容类型:
angular
.module('MyApp')
.decorator('$httpBackend', [
'$delegate', function($delegate) {
return function() {
var contentType, headers;
headers = arguments[4];
contentType = headers != null ? headers['X-Force-Content-Type'] : null;
if (contentType != null && headers['Content-Type'] == null)
headers['Content-Type'] = contentType;
return $delegate.apply(null, arguments);
};
}]);
所以,效果很好!现在我们的问题是它破坏了我们使用模拟 $httpBackend 服务的所有单元测试。我们得到的唯一错误是 "undefined".
例如。单元测试方法:
it('should return service.model.error if service returns an exception code from EndProject',
inject(function($httpBackend) {
var mockResponse = sinon.stub({ 'exception': 'Unable to retrieve service data' });
$httpBackend.whenPUT(this.endProjectUrl).respond(mockResponse);
var data;
this.service.EndProject().then(function(fetchedData) {
data = fetchedData;
});
$httpBackend.flush();
expect(data.error.state).toBe(true);
expect(data.error.message).toEqual('Unable to retrieve service data');
}));
PhantomJS 2.1.1 (Mac OS X 0.0.0) projectService EndProject should return service.model.error if service returns an exception code from EndProject FAILED undefined /Users/mlm1205/Documents/THDSource/bolt-projects/html_app/src/app/components/services/project/projectService.spec.js:213:41 invoke@/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/bower_components/angular/angular.js:4560:22 workFn@/Users/mlm1205/Documents/THDSource/bolt-projects/html_app/bower_components/angular-mocks/angular-mocks.js:2518:26
列出的装饰器涵盖了简单的猴子修补场景,其中修补函数不是构造函数并且没有静态属性和方法。
$httpBackend
in ng
module也是如此,它只是一个没有额外属性的工厂函数。
对于覆盖 $httpBackend
并具有静态方法 at least some of them are documented.
ngMock
和 ngMockE2E
模块,情况并非如此
这意味着猴子修补工厂函数的一般安全方法(它不包括不可枚举和继承的属性)是
app.decorator('$httpBackend', ['$delegate', function ($delegate) {
var $httpBackend = function () {
...
return $delegate.apply(null, arguments);
};
angular.extend($httpBackend, $delegate);
return $httpBackend;
}]);
无论如何,将应用程序模块化到可以单独测试单元而没有过多移动部件的级别是一个好习惯(这个问题是一个很好的例子,为什么这很重要)。 app
(在生产中自举)、app.e2e
(在 e2e 测试中自举)、app.common
(公分母)、app.unitA
(在 [=18= 中加载)很方便] 并且可以在单元测试中单独加载)等
大部分应用程序范围的代码(config
和 run
块、路由)可能会移动到单独的模块中,并且只加载到直接依赖它们的模块中。除非这是测试 decorator
单元本身的规范,否则不应加载装饰器模块。
另请注意,在调试规范错误时
虽然我将 estus 的答案标记为解决方案,但完全基于我的问题...最终,最终这不是我们最终的结果。在不能透过树木看到森林的情况下,最简单的解决方案是向 $http 调用的配置添加一个空数据元素。我之前尝试过,但它没有用(或者看起来是这样),但在再次尝试之后,它确实起作用了,我们能够从应用程序中删除装饰器。
return $http.get(getItemInformationUrl + params, { dataType: 'json', data: '', headers: {'Content-Type': 'application/json'} }).then(getItemInformationCompleted).catch(getItemInformationFailed);