使用 Karma 测试 Angular 时选择性地模拟服务

Posted

技术标签:

【中文标题】使用 Karma 测试 Angular 时选择性地模拟服务【英文标题】:Selectively Mock Services when Testing Angular with Karma 【发布时间】:2017-08-15 06:10:32 【问题描述】:

虽然有很多关于在 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 或其他角度服务来正常运行,而当简单地将工厂对象附加到窗口时,这些服务没有正确注入。

我正在寻找一种方法来测试服务,同时在一个位置为所有服务定义模拟。我想像但未能成功的可能性:

A) 让 serviceMocks 模块的 .run() 块在 myApp 模块的配置阶段。 (在这种情况下我可以附上 每个服务到窗口,因为角度依赖将是 正确注入,如上图所示分别注入) B) 能够用每个服务的测试文件中的实际实现覆盖我正在测试的服务 C) 否则能够在全局范围内定义和访问这些模拟,同时仍确保每个模拟都可以访问某些角度服务,例如$q

【问题讨论】:

【参考方案1】:

问题包含答案的线索。如果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 时选择性地模拟服务的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法批量处理 Karma - Angular 单元测试?

Karma 测试 Angular - TypeError:无法读取 null 的属性“recipientAddressId”

Angular Karma 使用 CUSTOM_ELEMENTS_SCHEMA 测试超时测试 AppComponent

使用 karma 和 jamis 进行 Angular 单元测试

在 Angular 8 (Karma 4.1.0) 升级后,在 Angular 7 (Karma 2.0.4) 中成功完成的 Karma 测试失败

使用 Karma Jasmine 的 Angular 1.5 组件模板单元测试