【问题标题】:How to test the config function of an Angular module?如何测试 Angular 模块的配置功能?
【发布时间】:2016-12-18 10:03:07
【问题描述】:

我在我想要进行单元测试的 Angular moduleconfig 函数中定义了一些设置代码。我不清楚我应该如何做到这一点。下面是一个简化的测试用例,显示了我是如何陷入困境的:

'use strict';

angular.module('myModule', []).config(['$http', '$log', function($http, $log) {
    $http.get('/api/getkey').then(function success(response) {
        $log.log(response.data);
    });
}]);

describe('myModule', function() {
    it('logs a key obtained from XHR', inject(function($httpBackend) {
        $httpBackend.expectGET('/api/getkey').respond(200, '12345');
        angular.module('myModule');
        $httpBackend.flush();
    }));
});

这显然不是正确的方法,因为我收到以下错误:

Error: No pending request to flush !

可以在on GitHub 找到带有上述测试代码的完整、可运行的 Angular 项目。如果您知道如何处理这种情况,请在 Stack Overflow 上回答。如果您还向 GitHub 存储库提交拉取请求,则会获得奖励积分。

【问题讨论】:

  • 正如@mzulch 的回答指出的那样,您只能将提供程序(而不是实例)注入配置块。那么,如果您的代码不起作用,您如何期望单元测试成功?
  • 一个问题可能会被误导(就像我现在理解的这个问题)。这可以在答案中指出。一个很好的答案解释了三件事:(1)如何在模块中全局使用服务,(2)如何测试config 块,(3)为什么不能同时拥有两者。
  • 我想我对你的过程更好奇。我通常在我确信我的代码可以工作之后再编写测试,但现在我想起来,有些人坚持要先编写测试,然后再编写代码以适应测试。很公平。
  • 中间还有第三种方式:先写代码,再写测试看看代码是否有效。我想这就是我在这里所做的。感谢您引起我的注意,我之前没有如此准确地反映过我的编程和测试顺序。 :-)
  • 您正在测试不可行的代码这一事实并不能增加问题的清晰度。关于 config vs run 和 serviceName vs serviceNameProvider,请参阅this answer。配置和运行阶段有两个不同的注入器,分别注入服务提供者和服务实例。可以使服务提供者在实例注入器中可用,但反之则不行。这就是为什么 config 块中的 $http 是一个难题。

标签: javascript angularjs karma-runner karma-jasmine angular-mock


【解决方案1】:

如果您的初始化需要注入服务,请使用 run 而不是 configconfig 函数只能接收提供程序和常量作为参数,不能像 $http (relevant docs) 这样的实例化服务。

angular.module('myModule', []).run(['$http', '$log', function($http, $log) {
    ...
}]);

初始化您的模块以进行测试

beforeEach(module('myModule'));

it('logs a key obtained from XHR', inject(function($httpBackend) {
    $httpBackend.expectGET('/api/getkey').respond(200, '12345');
    $httpBackend.flush();
}));

所以完整的工作版本看起来像

'use strict';

angular.module('myModule', []).run(['$http', '$log', function($http, $log) {
    $http.get('/api/getkey').then(function success(response) {
        $log.log(response.data);
    });
}]);

describe('myModule', function() {
    beforeEach(module('myModule'));

    it('logs a key obtained from XHR', inject(function($httpBackend) {
        $httpBackend.expectGET('/api/getkey').respond(200, '12345');
        $httpBackend.flush();
    }));
});

另外,这是一个测试配置块以检查提供程序上的方法是否被调用的示例:https://medium.com/@a_eife/testing-config-and-run-blocks-in-angularjs-1809bd52977e#71e0

【讨论】:

  • 我喜欢您的回答,但如果您从最后一个外部链接中获取见解并将其包含在您的回答中,您可以进一步增加其价值。毕竟,问题是如何测试配置功能。您可以为此目的更改示例,例如通过将$http$log 替换为可在config 块中使用的提供程序。另外,如果您能进一步解释为什么$http不能注入config,那就太好了;毕竟,文档还声明服务是提供者的特例。
  • 我已将您的解决方案推送至GitHub
  • 恭喜你获得了赏金,我认为你应得的。我可能会接受我自己的答案,因为它更直接地解决了问题的标题,但我会稍微推迟决定,以防有人提供我没有想到的论点。
【解决方案2】:

mzulch is right 指出不能在angular.module(...).config 块中注入服务。他还为您在模块初始化代码中实际需要使用服务的场景提供了正确的解决方案:使用.run 块而不是.config 块。他的回答非常适合这种情况。

如何为.config 块编写单元测试的问题仍然存在。让我们将我的问题中的幼稚代码调整为实际需要 .config 的场景。以下 sn-p 注入提供者依赖而不是服务依赖:

angular.module('myModule', []).config(['$httpProvider', function($httpProvider) {
    $httpProvider.useApplyAsync(true);
}]);

describe('myModule', function() {
    it('configures the $http service to combine response processing via $applyAsync', inject(function($httpProvider) {
        angular.module('myModule');
        expect($httpProvider.useApplyAsync()).toBeTruthy();
    }));
});

这一次,'myModule' 的实现是正确的。然而,与我的问题中的尝试类似的单元测试仍然不正确。现在 Karma 给了我以下错误:

Error: [$injector:unpr] Unknown provider: $httpProviderProvider <- $httpProvider

这个神秘的错误来自inject,它作为第二个参数传递给it。注意Provider 是如何结结巴巴的。这是因为inject 正在寻找$httpProvider 的提供者。我们可以称之为“元提供者”。这样的东西在 Angular 框架中不存在,但inject 无论如何都在尝试它,因为它希望您只请求服务依赖项。服务确实有提供者,例如,$http$httpProvider

所以inject(全名:angular.mock.inject,此处全球可用)不是在测试用例中获取$httpProvider 的正确方法。正确的方法是使用module (angular.mock.module) 定义一个匿名模块配置函数,它关闭一个我们可以捕获提供者的变量。这是因为提供者 可以在配置时注入(请参阅mzulch's answer 底部的link 以及我自己的answer 到我的另一个问题,以获取有关配置时间与运行时间的详细信息)。它看起来像这样:

var $httpProvider;

beforeEach(function() {
    module(function(_$httpProvider_) {
        // this is a .config function
        $httpProvider = _$httpProvider_;
    });
    // after this I can use inject() to make the magic happen
});

我天真的测试用例中的另一个错误是我试图通过调用angular.module('myModule') 来执行'myModule's 配置步骤。出于测试用例的目的,我应该使用全局 module (angular.mock.module),而最明智的做法是在 beforeEach 夹具中。总之,以下代码完成了这项工作:

describe('myModule', function() {
    var $httpProvider;

    beforeEach(function() {
        module(function(_$httpProvider_) {
            $httpProvider = _$httpProvider_;
        });
        module('myModule');
    });

    it('configures the $http service to combine response processing via $applyAsync', function() {
        inject();  // enforces all the module config steps
        expect($httpProvider.useApplyAsync()).toBeTruthy();
    });
});

我选择将inject() 放在测试用例的开头,但我也可以将它放在beforeEach 的末尾。后一种方法的优点是我可以在一个地方编写对inject 的调用,而无需在每个测试用例中重复它。这里实际采用的方法的优点是可以在以后的beforeEaches 甚至在单个测试用例中添加更多模块到injector

我将此替代解决方案推送到GitHub 上的新分支。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-18
    • 2019-11-12
    • 1970-01-01
    • 1970-01-01
    • 2016-12-23
    • 2020-05-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多