E2E 模拟$httpBackend实际上并没有为我传递



尽管我相信我正在按照这里的说明设置$httpBackend以将所选请求传递到服务器,但它对我不起作用。

这是一个测试失败的Plunkr,它显示了我正在做的事情,并在评论中解释了似乎出了问题。

我的研究表明,出于某种原因,mock$httpBackend没有真正$httpBackend的内部副本,因此,当需要传递XHR请求时,它会将其传递给mock$httpBackend。第二个调用抛出一个异常,因为它不知道该如何处理请求。

对dtabuenc的响应

我很感激地记得你关于中途测试的帖子。您确定了介于单元测试和E2E测试之间的一个重要的集成测试范围。我站在中间立场。

我认为你一点也不刻薄。你的回答非常合理。。。或者如果与"API引用/ngMockE2E/$httpBackend"的文本不矛盾,则是合理的。我引用:

此实现可用于通过whenapi及其快捷方式(whenGETwhenPOST等)以静态或动态响应进行响应,并且可选择将请求传递给实际的$httpBackend以进行特定请求(例如,与某些远程api交互或从Web服务器获取模板)。。。

[I] 在端到端测试场景中,或者在开发应用程序时使用mock替换真实后端api的场景中,通常希望某些类别的请求绕过mock,发出真实的http请求。。。。要使用此行为配置后端,请使用when的passThrough请求处理程序,而不是respond。[强调我的]。

文档对Jasmine环境中的E2E$httpBackend使用问题保持沉默。我想不出任何理由来排除它。如果有这样的理由,他们应该把它说清楚。说真的,谁读过mock组件,却不打算在测试环境中使用它?

">将特定请求的请求传递给真正的$httpBackend,例如与某些远程api进行交互"正是我想要做的。除了该组件的非mock版本之外,"真正的$httpBackend"可能意味着什么?

我不理解你所说的

ngMocksE2E模块设计用于执行实际角度应用程序的"服务器"端。

"服务器"一词在该页面上整整出现了3次,没有一次表明任何应用程序代码都将在"服务器"上执行。我不知道你所说的在"服务器端"执行的"实际的角度应用程序"是什么意思

文件非常清楚,E2E$httpBackend不限于E2E测试。它也适用于">一个场景,当一个应用程序被实际的后端api替换为mock时"。

这离我的场景只有半步之遥,在我的场景中,应用程序正在用真正的后端api进行测试。">

在我的场景中,SUT调用一个从服务器获取数据的组件。我的测试是为了验证这个依赖组件是否成功地向真正的后端发出这样的请求,并将以预期的方式检索或保存数据。这是一个集成测试,不能通过模拟后端的行为来充分满足。

当然,我可以用模拟XHR响应来测试(并进行测试)组件正确响应我预测的的能力将是后端的行为。这与验证组件是否正确响应实际后端的行为不同。。。这可能会随着应用程序的发展而变化,并以某种重要的方式偏离模拟响应。

如果我了解如何将中途测试仪交换到SUT的代码路径中,我会考虑使用您的中途测试仪。我没有。我认为您的ngMidwayTester无法访问发出XHR请求的组件。但如果必须的话,我知道如何将一个真正的XHR助手插入管道。

这是我此刻的立场

要么有人可以展示如何让$httpBackend将某些请求传递到服务器——正如文档所宣称的那样——要么我将用一个有效的XHR实现来代替passThrough实现。

我更喜欢第一种选择。如果是第二个,我将在这里提供一个链接,以方便其他分享我的需求和我对API文档的解释的人。

我还有第三条路要走吗?

我偶然发现了同样的问题,但我没有实现丰富的API,也没有简单地替换添加在以下助手中的原始angular-mock:

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请求通过的两个问题:

  1. 恢复原始$httpBackend

  2. 提供一个方法来完成请求,否则它们将在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();
});
});

以下是对ngMockE2E模块中$httpBackend用途的解释。

ngMockE2E模块并非设计或打算在茉莉花规范中使用。

在进行端到端测试时,测试有两个方面。一个是正在测试的角度应用程序,另一个是Jasmine规范中的角度场景代码。

在E2E测试中,除了场景运行器之外,没有角度模块、ng模型或任何与角度相关的东西。

ngMocksE2E模块设计用于执行实际角度应用程序的"服务器"端。它的主要目的是使我们能够预处理响应,这样集成级别的UI测试就可以比每个页面实际进入JSON服务器更快地进行。

当将jasmine与ng-mocks一起使用时,angular将始终用mock后端替换$httpBackend。当添加ngMocksE2E模块时,它将无法获得任何"真实"的$httpBackend,正如您已经发现的,它将只包装mock并在传递时委托给它。

您尝试编写的测试似乎不是测试UI集成,而是测试应用程序javascript和服务器集成。

这是完全合法的测试风格(在angular社区中被称为"中途测试")。你的问题是你用错了工具。

我会看看这个:

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数据指令,同时仍然让它从服务器中获取模板。

为了从jasmine中使用它,设置将大不相同,使用angular.mock.module('ngMockE2E'),然后在beforeEach()中设置$httpBackend.whenGET(),而不是在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,事情应该完全按照您希望的方式工作。

为了用真正的后端调用测试我的应用程序,我使用了angular-mocks的修改版本

它的工作原理就像Jasmine中的单元测试一样。

我在Jasmine 2.0中使用它,所以测试如下:

it(' myTest', function (done) {
_myService.apiCall()
.then(function () {
expect(true).toBeTruthy();
done()
});
});

注意:由于异步调用,需要done

这里有一个解决方案,当我使用ngMock进行单元测试时,我使用它来进行真正的HTTP调用。我主要将其用于调试、试用API、获取JSON示例等。

我在博客上写了一篇关于解决方案的更详细的文章:如何使用ngMockE2E&通过。

解决方案如下:

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调用通过。

我们需要重新定义ngMock模块,而不使用其假的$BrowserProvider版本,因为这是在使用ngMock的单元测试中阻止真正HTTP调用的原因。

我为什么这样做

我喜欢能够在使用伪造和真实HTTP调用之间轻松切换的灵活性,因为这有助于我在编写测试时的工作流程,这就是为什么使用ngMockE2E版本的$httpBackEndProvider的原因。它还允许我以与使用ngMock相同的方式对单元测试进行编码,在那里我可以简单地插入/退出beforeEach/afterEach行来注册angular.mock.http.init/reset.

Plunkr

这是一个Plunkr的例子。

相关内容

最新更新