在 karma 茉莉花上与 Angular 隔离
Isolation with Angular on karma jasmine
我有一组分布在 2 个模块中的测试。
第一个模块有测试,对于它的依赖项,我声明模拟来测试它,而不受其依赖模块的任何影响,如下所示:
beforeEach(function(){
angular.mock.module(function ($provide) {
$provide.value("mockServiceFromModule1", mockServiceFromModule1);
$provide.value("mockServiceFromModule2", mockServiceFromModule2);
});
angular.module('module1', []);
angular.module('module2', []);
angular.mock.module('moduleThatIAmTesting');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromTestModule) {
});
})
第二个模块有一系列测试,当我 运行 只有它们时,所有测试都通过了。
beforeEach(function(){
angular.mock.module('module1');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {
});
})
当 运行 和 f(运行 只有它们)工作时,两个测试都有效,但是当我 运行 整个测试套装时,我得到错误,特别是关于模块声明或 $httpBackend
.
如何让 jasmine 运行 每次测试都好像它们是唯一的测试一样?
看来我在每次测试中都弄乱了 angular/modules/$httpBackEnd,并且在它开始新测试时正在传播更改。
更新 1
我有一个 jsFiddle 代表这个问题。
问题的结构是:
- 一些测试是 运行 与模拟依赖模块
- 稍后另一个测试想要测试实际的模拟模块
- 由于第一个模块已经加载,我们无法覆盖它,测试失败。
在 JSFiddle 上,有关 $httpBackend 的错误没有任何内容可刷新是因为从未命中 expectedGet 的请求,并且由于先前加载的空模块而从未命中
重要的是要注意测试的顺序是唯一与失败相关的事情,因为在这个 JSFiddle 中他们通过了相同的测试。
我当然可以制定测试顺序并绕过它,但我的目标是找到一种方法来隔离进行测试,而不必担心其他测试的副作用。
您遇到的问题是由于 $compileProvider
处理使用预先存在的名称注册的新指令的方式的性质。
简而言之; 您没有覆盖旧指令,您正在创建一个同名的辅助指令。因此,原始实现 运行s 并尝试获取 baz.html
和 $httpBackend
抛出,因为您没有为该调用设置期望。
看到这个 updated fiddle that did two changes from your original fiddle。
- 不要将
parentModule
注入到您的 childModule
规范中。不需要该行,这是您看到这些错误的部分原因。哦,angular.module
在考验之地是 邪恶的 。尽量不要使用它。
- 修饰 原始指令,如果你想使用与原始指令相同的名称滚动,或者将其命名为其他名称。 I我选择在 fiddle 中将其命名为其他名称,但我在答案末尾提供了代码以显示 装饰器方式。
这是以下场景中发生的情况的屏幕截图:
- 模块 A 注册了一个名为
baz
的指令。
- 模块 B 依赖于模块 A。
- 模块 B 注册了一个名为
baz
的指令。
正如您可能想象的那样,为了让模块系统不通过让人们覆盖彼此的指令来 insta-gib 本身 - $compileProvider
将简单地注册 另一个指令 与同名。 两者都会运行。
以此 ng-click example or this article 来概述我们如何利用同名的多个指令。
请参阅下面随附的屏幕截图,了解您的情况。
71 到 73 行的代码是您可以实施我在回答开头提到的解决方案 #2 的地方。
装修中baz
在您的 beforeEach for testModule
中,替换以下内容:
$compileProvider.directive('baz', function () {
return {
restrict: 'E',
template: '{{value}}<div ng-transclude></div>',
controllerAs: 'bazController',
controller: function ($scope, fooService) {
$scope.value = 'baz' + fooService.get()
},
transclude: true
};
});
有了这个:
$provide.decorator('bazDirective', function ($delegate) {
var dir = $delegate[0];
dir.template = '{{value}}<div ng-transclude></div>';
dir.controller = function ($scope, fooService) {
$scope.value = 'baz' + fooService.get();
};
delete dir.templateUrl;
return $delegate;
});
jsFiddle showing the decorator approach
调用 angular.module('parent', [])
怎么样?
You should not call angular.module('name', [])
in your specs, unless you happen to be using the angular-module gist. And even then it's not doing much for you in the land of testing.
只使用 .mock.module
或 window.module
,否则你 将 杀死与指定模块相关的即将到来的规范,因为你已经有效地杀死了spec 运行.
其余部分的模块定义
此外,由于以下原因,parentModule
中 baz
的指令定义将自动在您的 testModule
规范中可用:
angular.module('parent', []).directive('baz', fn());
angular.module('child', ['parent']);
// In your test:
module('child'); // Will automatically fetch the modules that 'child' depend on.
因此,即使 if 我们在您的规范中终止了 angular.module('parent', [])
调用,原始 baz
定义 仍然 正在加载。
因此,由于 $compileProvider
支持多个具有相同名称的指令的性质,HTTP 请求失败,这就是您的规范套件失败的原因。
此外,作为最后一点;您正在 beforeEach
块中配置 undefined
模块。如果目标是配置测试模块,那你就错了。
语法如下:
mock.module('child', function ($compileProvider, /** etc **/) {
});
// Not this:
mock.module('child');
mock.module(function ($compileProvider, /** etc **/) {
});
这可以在我发布的屏幕截图中看到。您的模拟 baz
定义的 $$moduleName
属性 是 undefined
,而我假设您希望它是 child
.
我有一组分布在 2 个模块中的测试。
第一个模块有测试,对于它的依赖项,我声明模拟来测试它,而不受其依赖模块的任何影响,如下所示:
beforeEach(function(){
angular.mock.module(function ($provide) {
$provide.value("mockServiceFromModule1", mockServiceFromModule1);
$provide.value("mockServiceFromModule2", mockServiceFromModule2);
});
angular.module('module1', []);
angular.module('module2', []);
angular.mock.module('moduleThatIAmTesting');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromTestModule) {
});
})
第二个模块有一系列测试,当我 运行 只有它们时,所有测试都通过了。
beforeEach(function(){
angular.mock.module('module1');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {
});
})
当 运行 和 f(运行 只有它们)工作时,两个测试都有效,但是当我 运行 整个测试套装时,我得到错误,特别是关于模块声明或 $httpBackend
.
如何让 jasmine 运行 每次测试都好像它们是唯一的测试一样?
看来我在每次测试中都弄乱了 angular/modules/$httpBackEnd,并且在它开始新测试时正在传播更改。
更新 1 我有一个 jsFiddle 代表这个问题。 问题的结构是:
- 一些测试是 运行 与模拟依赖模块
- 稍后另一个测试想要测试实际的模拟模块
- 由于第一个模块已经加载,我们无法覆盖它,测试失败。
在 JSFiddle 上,有关 $httpBackend 的错误没有任何内容可刷新是因为从未命中 expectedGet 的请求,并且由于先前加载的空模块而从未命中
重要的是要注意测试的顺序是唯一与失败相关的事情,因为在这个 JSFiddle 中他们通过了相同的测试。
我当然可以制定测试顺序并绕过它,但我的目标是找到一种方法来隔离进行测试,而不必担心其他测试的副作用。
您遇到的问题是由于 $compileProvider
处理使用预先存在的名称注册的新指令的方式的性质。
简而言之; 您没有覆盖旧指令,您正在创建一个同名的辅助指令。因此,原始实现 运行s 并尝试获取 baz.html
和 $httpBackend
抛出,因为您没有为该调用设置期望。
看到这个 updated fiddle that did two changes from your original fiddle。
- 不要将
parentModule
注入到您的childModule
规范中。不需要该行,这是您看到这些错误的部分原因。哦,angular.module
在考验之地是 邪恶的 。尽量不要使用它。 - 修饰 原始指令,如果你想使用与原始指令相同的名称滚动,或者将其命名为其他名称。 I我选择在 fiddle 中将其命名为其他名称,但我在答案末尾提供了代码以显示 装饰器方式。
这是以下场景中发生的情况的屏幕截图:
- 模块 A 注册了一个名为
baz
的指令。 - 模块 B 依赖于模块 A。
- 模块 B 注册了一个名为
baz
的指令。
正如您可能想象的那样,为了让模块系统不通过让人们覆盖彼此的指令来 insta-gib 本身 - $compileProvider
将简单地注册 另一个指令 与同名。 两者都会运行。
以此 ng-click example or this article 来概述我们如何利用同名的多个指令。
请参阅下面随附的屏幕截图,了解您的情况。
71 到 73 行的代码是您可以实施我在回答开头提到的解决方案 #2 的地方。
装修中baz
在您的 beforeEach for testModule
中,替换以下内容:
$compileProvider.directive('baz', function () {
return {
restrict: 'E',
template: '{{value}}<div ng-transclude></div>',
controllerAs: 'bazController',
controller: function ($scope, fooService) {
$scope.value = 'baz' + fooService.get()
},
transclude: true
};
});
有了这个:
$provide.decorator('bazDirective', function ($delegate) {
var dir = $delegate[0];
dir.template = '{{value}}<div ng-transclude></div>';
dir.controller = function ($scope, fooService) {
$scope.value = 'baz' + fooService.get();
};
delete dir.templateUrl;
return $delegate;
});
jsFiddle showing the decorator approach
调用 angular.module('parent', [])
怎么样?
You should not call
angular.module('name', [])
in your specs, unless you happen to be using the angular-module gist. And even then it's not doing much for you in the land of testing.
只使用 .mock.module
或 window.module
,否则你 将 杀死与指定模块相关的即将到来的规范,因为你已经有效地杀死了spec 运行.
此外,由于以下原因,parentModule
中 baz
的指令定义将自动在您的 testModule
规范中可用:
angular.module('parent', []).directive('baz', fn());
angular.module('child', ['parent']);
// In your test:
module('child'); // Will automatically fetch the modules that 'child' depend on.
因此,即使 if 我们在您的规范中终止了 angular.module('parent', [])
调用,原始 baz
定义 仍然 正在加载。
因此,由于 $compileProvider
支持多个具有相同名称的指令的性质,HTTP 请求失败,这就是您的规范套件失败的原因。
此外,作为最后一点;您正在 beforeEach
块中配置 undefined
模块。如果目标是配置测试模块,那你就错了。
语法如下:
mock.module('child', function ($compileProvider, /** etc **/) {
});
// Not this:
mock.module('child');
mock.module(function ($compileProvider, /** etc **/) {
});
这可以在我发布的屏幕截图中看到。您的模拟 baz
定义的 $$moduleName
属性 是 undefined
,而我假设您希望它是 child
.