如何测试Angular模块的配置功能?

How to test the config function of an Angular module?

我正在 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 !

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

如果您的初始化需要注入服务,请使用 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

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

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

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'的执行是正确的。但是,类似于我的问题中的尝试的单元测试仍然不正确。现在业力给我以下错误:

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

此密码错误来自 inject,它作为第二个参数传递给 it。请注意 Provider 是如何被卡住的。这是因为 inject 正在寻找 $httpProvider 的提供商。 A "meta provider",我们可以这样称呼它。 Angular 框架中不存在这样的东西,但是 inject 正在尝试它,因为 它希望您只请求服务依赖项 。服务确实有提供者,例如,$http$httpProvider.

所以inject(全名:angular.mock.inject,这里全球可用)不是在测试用例中获取$httpProvider的正确方法。正确的方法是使用 module (angular.mock.module) 定义一个匿名模块配置函数,它关闭一个我们可以在其中捕获提供者的变量。这是可行的,因为提供程序 可以 在配置时注入(有关配置时间与 运行 时间的详细信息,请参阅我的其他问题的 link at the bottom of as well as my own )。它看起来像这样:

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 的调用,而不需要在每个测试用例中重复它。这里实际采用的方法的优点是可以在以后的 beforeEach 中甚至在单个测试用例中将更多模块添加到 injector

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