虽然我相信我正在跟踪instructions here设置$ httpBackend以将选定的请求传递给服务器,但它对我不起作用。
Here is a Plunkr with a failing test显示了我正在做的事情,并在评论中解释了什么似乎出错了。
我的洞察力表明,由于某种原因,模拟$httpBackend
没有真实$httpBackend
的内部副本,因此,当需要通过XHR请求时,它会通过它改为模拟$httpBackend
。第二个调用抛出异常,因为它不知道如何处理请求。
我很遗憾地记得你在中途测试的帖子。您确定了一系列重要的集成测试,它们介于单元测试和E2E测试之间。我站在那个中间地带。
我认为你根本不是很讽刺。你的答案是完全合理的......或者 是合理的 如果它与“API reference / ngMockE2E / $httpBackend”的文字没有矛盾。我引用:
此实施可用于通过
when
api及其快捷方式(whenGET
,whenPOST
等)响应静态或动态响应, 可选择通过通过向真实$httpBackend
请求特定请求(例如与某些远程api交互或从网络服务器获取模板) ......[我]是一个端到端的测试场景,或者在使用真实的后端api替换为模拟开发应用程序的场景中,通常需要某些类别的请求绕过模拟和 发出真实的http请求 ....要使用此行为配置后端,请使用when
passThrough
请求处理程序而不是{ {1}}。[强调我的]。
文档没有提及Jasmine环境中E2E respond
的使用问题。我想不出有理由排除它。如果有这样的理由,他们应该清楚说明。说真的,谁读到了模拟组件并且预计不会在测试环境中使用它?
要“将请求传递给真实$httpBackend
以获取特定请求,例如与某些远程apis进行交互”正是我打算做的。除了该组件的非模拟版本之外,“真正的$ httpBackend”可能意味着什么呢?
我不明白你的说法
$httpBackend
模块设计用于实际角度应用程序正在执行的事物的“服务器”端。
“服务器”一词在该页面上出现了3次,而不是曾经暗示任何应用程序代码将在“服务器”上执行。我不知道你在“服务器”方面执行的“实际角度应用程序”是什么意思。“
文档非常明确,E2E ngMocksE2E
不仅限于E2E测试。它也适用于“在使用真实的后端api替换为模拟开发应用程序时的情况。”
这距离我的情景只有半步之遥一个应用程序正在测试与真正的后端api 。“
在我的场景中,SUT正在调用从服务器获取数据的组件。我的测试用于验证此依赖组件是否成功完成了对真实后端的此类请求,并将以预期方式检索或保存数据。这是一个集成测试,通过模拟后端的行为无法充分满足。
当然,我可以使用模拟XHR响应测试(并进行测试)组件能够正确响应我预测将成为后端的行为。这与验证组件对实际后端的行为做出适当响应是不同的......这可能会随着应用程序的发展而变化,并以某种显着的方式偏离模拟的响应。
如果我了解如何将其交换到SUT的代码路径中,我会考虑使用您的中途测试仪。我不。我认为您的$httpBackend
无法访问发出XHR请求的组件。但是我知道如果必须的话,如何将真正的XHR帮助器堵塞到管道中。
这就是我现在所处的位置。
任何人都可以展示如何让ngMidwayTester
将某些请求传递到服务器 - 正如文档所宣称的那样 - 或者我将使用有效的XHR实现替换$httpBackend
实现。
我更喜欢第一种选择。如果被推到第二个,我将在这里提供一个链接,以便其他人分享我的需求和我对API文档的解释。
我缺少第三种方式吗?
答案 0 :(得分:23)
我偶然发现了同样的问题,但是没有实现丰富的API或替换原始的角度模拟,只需添加到以下帮助器中:
angular.module('httpReal', ['ng'])
.config(['$provide', function($provide) {
$provide.decorator('$httpBackend', function() {
return angular.injector(['ng']).get('$httpBackend');
});
}])
.service('httpReal', ['$rootScope', function($rootScope) {
this.submit = function() {
$rootScope.$digest();
};
}]);
它修补了两个阻止HTTP请求通过的问题:
恢复原始$httpBackend
;
提供一种方法来完成请求,否则它们将在AngularJS的队列中等待摘要循环。
describe('my service', function() {
var myService, httpReal;
beforeEach(module('myModule', 'httpReal'));
beforeEach(inject(function( _myService_, _httpReal_ ) {
myService = _myService_;
httpReal = _httpReal_;
}));
it('should return valid data', function(done) {
myService.remoteCall().then(
function(data) {
expect(data).toBeDefined();
done();
}, function(error) {
expect(false).toBeTruthy();
done();
});
httpReal.submit();
});
});
答案 1 :(得分:10)
以下是对$httpBackend
模块中ngMockE2E
目的的解释。
ngMockE2E
模块根本没有设计,也不打算在茉莉花规范中使用。
进行端到端测试时,测试有两个方面。一个是正在测试的角度应用程序,另一个是生活在Jasmine规范中的角度场景代码。
在E2E测试中,没有角度模块或ng-mocks,或者在茉莉花方面与角色相关的任何东西(情景跑步者除外)。
ngMocksE2E
模块设计用于执行实际角度应用程序的“服务器”端。它的主要目的是使我们能够预先响应,以便集成级UI测试可以比每个页面实际进入服务器以获取JSON更快地进行。
当使用jasmine和ng-mocks
时,angular将始终用模拟后端替换$ httpBackend。添加ngMocksE2E
模块时,它将无法获得任何“真实”$httpBackend
,并且正如您已经发现的那样,只需将模拟包裹并传递给传递。
您尝试编写的测试似乎是不测试UI集成的测试,而是测试应用程序javascript和服务器集成。
这是完全合法的测试方式(在角度社区中称为“midwayTesting”)。您的问题是您使用的是错误的工具。
我会看看这个:
https://github.com/yearofmoo/ngMidwayTester
你可以使用它来代替angular-mocks和angular.module(),以便进行我假设你想做的测试。
您可以在此处详细了解:
http://www.yearofmoo.com/2013/01/full-spectrum-testing-with-angularjs-and-karma.html
(如果您已经在那里链接,请道歉)
编辑:(以解决有关的其他评论)
你有一个真正的好处,因为文档不清楚ngMockE2E
不能在端到端测试设置的客户端(即karma / jasmine)端使用。解释像解释它们之类的东西并不是不合理的,但它并没有改变解释错误的事实。
如果在应用程序的服务器端而不是客户端使用时,ngMockE2E将通过请求。这意味着您仍然可以将某些难以模拟的请求作为预先设置的响应进行传递。客户端和服务器端的意思是端到端测试有两个目的。您有一个由标准应用程序服务器提供服务的应用程序,并且您拥有驱动应用程序的测试代码,该应用程序通常在Karma或其他测试运行程序中执行,该测试运行程序使用标准HTTP请求与正在执行的应用程序进行通信。另一个过程。
如果您查看文档以及如何设置ngMockE2E
,您会注意到没有提及Jasmine,并且说明如何在真正的角度应用程序中进行设置:
myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
myAppDev.run(function($httpBackend) {
phones = [{name: 'phone1'}, {name: 'phone2'}];
// returns the current list of phones
$httpBackend.whenGET('/phones').respond(phones);
// adds a new phone to the phones array
$httpBackend.whenPOST('/phones').respond(function(method, url, data) {
phones.push(angular.fromJson(data));
});
$httpBackend.whenGET(/^\/templates\//).passThrough();
//...
});
正如您在本示例中所看到的,他们正在模拟所有JSON数据指令,同时让它仍然从服务器获取模板。
为了从茉莉花中使用它,设置将完全不同,使用angular.mock.module('ngMockE2E')
,然后在$httpBackend.whenGET()
而不是beforeEach()
中设置module.run()
。
就我ngMidwayTester
而言,我相信这会与ngMockE2E
兼容。基本上ngMidwayTester
用它自己的实现替换angular.mock.module()
和inject()
。所以你可以像这样使用它:
beforeEach(function(){
tester = ngMidwayTester('app', 'ngMockE2E');
$http = tester.inject('$http');
$httpBackend = tester.inject('$httpBackend');
$rootScope = tester.inject('$rootScope');
});
这应该有效,因为您不再使用ngMock模块(使用angular.mock.module()
时总是包含该模块)。事情应该像你希望他们使用ngMidwayTester
一样工作。
答案 2 :(得分:0)
要使用真实的后端调用来测试我的应用,我使用了modified version of angular-mocks
它就像Jasmine中的单元测试一样。
我在Jasmine 2.0中使用它,所以测试如下:
it(' myTest', function (done) {
_myService.apiCall()
.then(function () {
expect(true).toBeTruthy();
done()
});
});
注意:由于异步调用,需要done
。
答案 3 :(得分:0)
这是我在使用ngMock进行单元测试时用来进行真正的HTTP调用的解决方案。我主要用它来调试,试用API,获取JSON示例等。
我在博客上写了一篇关于解决方案的更详细的帖子:How to Unit Test with real HTTP calls using ngMockE2E & passThrough。
解决方案如下:
angular.mock.http = {};
angular.mock.http.init = function() {
angular.module('ngMock', ['ng', 'ngMockE2E']).provider({
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
};
angular.mock.http.reset = function() {
angular.module('ngMock', ['ng']).provider({
$browser: angular.mock.$BrowserProvider,
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
};
在ngMock之后包含此源文件,例如:
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="angular-mocks.js"></script>
<!-- this would be the source code just provided -->
<script type="text/javascript" src="ngMockHttp.js"></script>
describe('http tests', function () {
beforeEach(module('moviesApp'));
var $controller;
var $httpBackend;
var $scope;
describe('real http tests', function() {
beforeEach(angular.mock.http.init);
afterEach(angular.mock.http.reset);
beforeEach(inject(function(_$controller_, _$httpBackend_) {
$controller = _$controller_;
$scope = {};
$httpBackend = _$httpBackend_;
// Note that this HTTP backend is ngMockE2E's, and will make a real HTTP request
$httpBackend.whenGET('http://www.omdbapi.com/?s=terminator').passThrough();
}));
it('should load default movies (with real http request)', function (done) {
var moviesController = $controller('MovieController', { $scope: $scope });
setTimeout(function() {
expect($scope.movies).not.toEqual([]);
done();
}, 1000);
});
});
});
它使用ngMockE2E的$ httpBackEndProvider版本,它为我们提供了我们在测试中使用的 passThrough 函数。这就像名称所示,并允许本机HTTP调用通过。
我们需要在没有虚假版本的$ BrowserProvider的情况下重新定义ngMock模块,因为这会阻止使用ngMock的单元测试中的真实HTTP调用。
我喜欢能够在使用假货和真正的HTTP调用之间轻松切换的灵活性,因为它有助于我在编写测试时的工作流程,这就是使用ngMockE2E版本的$ httpBackEndProvider的原因。它还允许我以与使用ngMock相同的方式对单元测试进行编码,在那里我可以简单地放入/取出beforeEach / afterEach行来注册angular.mock.http.init / reset。