使用 Karma 测试 Angular 时有选择地模拟服务
Selectively Mock Services when Testing Angular with Karma
虽然关于在 Karma 中模拟单个 Angular 服务有很多问题,但我在测试我的应用程序的过程中让我的模拟更加普遍存在问题。
在我当前的设置中,我有一个名为 serviceMocks
的模块,其中包含一个工厂,其中包含应用程序中每个服务的模拟。
人为的例子:
angular.module('serviceMocks',['ngMock'])
.factory('myServiceOne', function() {...})
.factory('myServiceTwo', function($httpBackend,$q) {...})
这在测试可能使用一个或多个服务作为依赖项的控制器和指令时效果很好。我在我的测试文件中包含了我的应用程序模块和 serviceMocks 模块,并且每个服务都被正确替换了。
beforeEach(module('myApp'));
beforeEach(module('serviceMocks'));
it('properly substitutes mocks throughout my tests', function() {...});
但是,在测试服务本身时,我不能包含 serviceMocks 模块,因为我正在测试的服务被其模拟所取代,使得测试无用。但是,我仍然希望 所有其他 服务被模拟为服务可能依赖于一个或多个服务来执行。
我想到的一种方法是让我的服务模拟在全球范围内可用,也许是通过将一个对象附加到 window
来保存模拟。然后我可以像这样测试服务时单独包含模拟:
beforeEach(module('myApp', function($provide) {
$provide.value('myServiceOne',window.mocks.myServiceOneMock);
$provide.value('myServiceTwo',window.mocks.myServiceTwoMock);
});
但是这种方法并没有奏效,因为一些模拟使用 $q
或其他 angular 服务来正常运行,而当简单地将工厂对象附加到window.
我正在寻找一种测试服务的方法,同时在一个位置为所有服务定义模拟。我想象但未能成功的可能性:
- A) 在
serviceMocks
模块的 .运行() 块 运行 之前
myApp
模块的配置阶段。 (在这种情况下我可以附上
window 作为 angular 依赖项的每项服务将是
正确注入,如上所示分别注入)
- B) 能够在每个服务的测试文件
中使用其实际实现覆盖我正在测试的服务
- C) 否则能够全局定义和访问这些模拟,同时仍然确保每个模拟都可以访问某些 angular 服务,例如
$q
.
问题包含答案的线索。如果 serviceMocks
模块导致设计问题,则使用它是错误的。
正确的模式是每个单元有一个模块(在这种情况下为模拟服务)。 ngMock
不需要,它会在 Jasmine 测试中自动加载。模块可以一一使用:
beforeEach(module('app', 'serviceOneMock', 'serviceTwoMock'));
或连在一起:
angular.module('serviceMocks', ['serviceOneMock', 'serviceTwoMock'])
serviceMocks
模块应该存在的情况并不多。只是因为每个 describe
块都决定哪些服务应该被模拟,哪些不应该被模拟。
大多数时候,模拟服务对于当前规范来说是独立的,或者取决于局部变量。在这种情况下,服务是就地模拟的:
var promiseResult;
beforeEach(module('app'));
beforeEach(module({ foo: 'instead of $provide.value(...)' });
beforeEach(($provide) => {
$provide.factory('bar', ($q) => {
return $q.resolve(promiseResult);
}
});
...
在专用 serviceOneMock
等模块中执行此操作可能需要在任何时候重构模拟服务,因为很明显它们过于通用且不适合这种情况。
如果在 WET 测试中行为和结果略有不同的规范中多次使用模拟服务,最好制作一个辅助函数来为当前规范生成它,而不是将其硬编码为 serviceOneMock
, 等模块.
虽然关于在 Karma 中模拟单个 Angular 服务有很多问题,但我在测试我的应用程序的过程中让我的模拟更加普遍存在问题。
在我当前的设置中,我有一个名为 serviceMocks
的模块,其中包含一个工厂,其中包含应用程序中每个服务的模拟。
人为的例子:
angular.module('serviceMocks',['ngMock'])
.factory('myServiceOne', function() {...})
.factory('myServiceTwo', function($httpBackend,$q) {...})
这在测试可能使用一个或多个服务作为依赖项的控制器和指令时效果很好。我在我的测试文件中包含了我的应用程序模块和 serviceMocks 模块,并且每个服务都被正确替换了。
beforeEach(module('myApp'));
beforeEach(module('serviceMocks'));
it('properly substitutes mocks throughout my tests', function() {...});
但是,在测试服务本身时,我不能包含 serviceMocks 模块,因为我正在测试的服务被其模拟所取代,使得测试无用。但是,我仍然希望 所有其他 服务被模拟为服务可能依赖于一个或多个服务来执行。
我想到的一种方法是让我的服务模拟在全球范围内可用,也许是通过将一个对象附加到 window
来保存模拟。然后我可以像这样测试服务时单独包含模拟:
beforeEach(module('myApp', function($provide) {
$provide.value('myServiceOne',window.mocks.myServiceOneMock);
$provide.value('myServiceTwo',window.mocks.myServiceTwoMock);
});
但是这种方法并没有奏效,因为一些模拟使用 $q
或其他 angular 服务来正常运行,而当简单地将工厂对象附加到window.
我正在寻找一种测试服务的方法,同时在一个位置为所有服务定义模拟。我想象但未能成功的可能性:
- A) 在
serviceMocks
模块的 .运行() 块 运行 之前myApp
模块的配置阶段。 (在这种情况下我可以附上 window 作为 angular 依赖项的每项服务将是 正确注入,如上所示分别注入) - B) 能够在每个服务的测试文件 中使用其实际实现覆盖我正在测试的服务
- C) 否则能够全局定义和访问这些模拟,同时仍然确保每个模拟都可以访问某些 angular 服务,例如
$q
.
问题包含答案的线索。如果 serviceMocks
模块导致设计问题,则使用它是错误的。
正确的模式是每个单元有一个模块(在这种情况下为模拟服务)。 ngMock
不需要,它会在 Jasmine 测试中自动加载。模块可以一一使用:
beforeEach(module('app', 'serviceOneMock', 'serviceTwoMock'));
或连在一起:
angular.module('serviceMocks', ['serviceOneMock', 'serviceTwoMock'])
serviceMocks
模块应该存在的情况并不多。只是因为每个 describe
块都决定哪些服务应该被模拟,哪些不应该被模拟。
大多数时候,模拟服务对于当前规范来说是独立的,或者取决于局部变量。在这种情况下,服务是就地模拟的:
var promiseResult;
beforeEach(module('app'));
beforeEach(module({ foo: 'instead of $provide.value(...)' });
beforeEach(($provide) => {
$provide.factory('bar', ($q) => {
return $q.resolve(promiseResult);
}
});
...
在专用 serviceOneMock
等模块中执行此操作可能需要在任何时候重构模拟服务,因为很明显它们过于通用且不适合这种情况。
如果在 WET 测试中行为和结果略有不同的规范中多次使用模拟服务,最好制作一个辅助函数来为当前规范生成它,而不是将其硬编码为 serviceOneMock
, 等模块.