如何在 Karma 上使用 Jasmine 将模拟依赖项注入到角度指令中 [英] How do I inject a mock dependency into an angular directive with Jasmine on Karma

查看:27
本文介绍了如何在 Karma 上使用 Jasmine 将模拟依赖项注入到角度指令中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下指令:

function TopLevelMenuDirective ($userDetails, $configuration) {返回 {限制:'A',templateUrl: staticFilesUri + 'templates/TopLevelMenu.Template.html',范围: {活动标签:'='},链接:函数(范围、元素、属性){var userDetails = $userDetails;如果($userDetails){范围.用户 = {名称:userDetails.name ?userDetails.name : 'KoBoForm 用户',头像: userDetails.gravatar ?userDetails.gravatar: (staticFilesUri + '/img/avatars/example-photo.jpg')};} 别的 {范围.用户 = {name: 'KoBoForm 用户',头像:staticFilesUri + '/img/avatars/example-photo.jpg'}}scope.sections = $configuration.sections();scope.isActive = 函数(名称){返回名称 === scope.activeTab ?'活跃' : '';}}}}

我想模拟依赖项以使用单元测试已知的值对不同的代码路径进行单元测试.我有以下示例单元测试:

it('应该将 $scope.user 设置为 $userDetails 传递的值',注入(函数($编译){var element = '

';元素 = $compile(element)($scope);$scope.$apply();expect(element.isolateScope().user.name).toBe('test name');expect(element.isolateScope().user.avatar).toBe('test avatar');}));

这给我带来了两个问题.

首先,由于模板位于外部文件中,因此在加载时会尝试获取它并出错,因为找不到该文件,这是合乎逻辑的,因为它位于测试环境中,而不是实际服务器中.

其次,没有明显的方法可以模拟通过其构造函数注入指令的依赖项.测试控制器时,您可以使用 $controller 服务,但由于指令是通过编译具有传递范围的 html 标记间接实例化的,因此无法直接实例化它(例如,没有类似的 $指令).这妨碍我将 $userDetails.name$userDetails.gravatar 设置为 'test name''test avatar' 分别.

如何使指令正确编译并使用自定义 $userDetails 依赖项运行?

解决方案

要加载模板文件,您必须在 karma 中配置 karma-ng-html2js-preprocessor.

首先,访问此页面并按照安装说明进行操作.然后,您需要在 karma.config.js 文件中添加几个条目:

文件:['模板/*.html'],

这告诉 karma 加载模板文件夹中的所有 html 文件(如果你的模板在其他地方,把那个文件夹放在那里).

预处理器:{ '**/*.html': 'ng-html2js' },

这告诉 karma 通过 ng-html2js 预处理器传递所有 html 文件,然后将它们转换为 angular 模块,将模板放入 $templateCache 服务.这样,当 $httpBackend 向服务器"查询模板时,它会被模板缓存拦截并返回正确的 html.这里一切正常,除了模板的 URL:它必须匹配指令中的 templateUrl 属性,并且 ng-html2js 默认将完整路径作为 uri 传递.所以我们需要转换这个值:

ngHtml2Js预处理器:{cacheIdFromPath:函数(文件路径){var 匹配 =/^\/(.+\/)*(.+)\.(.+)$/.exec(filepath);返回模板/"+匹配[2]+."+ 匹配 [3];}},

这接收 filepath 并通过 正则表达式传递它 将路径、文件名和扩展名提取到数组中.然后,您将 'templates/ 附加到文件名和扩展名,您将获得预期的 uri.

在完成所有这些之后,使模板可用就是在运行测试之前加载模块的问题:

beforeEach(module('templates/TopLevelMenu.Template.html'));

请记住,module 是位于 angular-mocks.js.

为了将自定义服务注入指令中,您需要覆盖服务的提供者:

beforeEach(module(function ($provide) {$provide.provider('$userDetails', function () {this.$get = 函数 () {返回 {name: '测试名称',gravatar: '测试头像'};}});}));

$provide 是为您的提供者提供的服务.因此,如果您想注入模拟依赖项,请在此处覆盖提供程序.

在测试之前执行该代码后,您将拥有一个模拟 $userDetails 服务,该服务返回您的预定义字符串.

I have the following directive:

function TopLevelMenuDirective ($userDetails, $configuration) {
    return {
        restrict:'A',
        templateUrl: staticFilesUri + 'templates/TopLevelMenu.Template.html', 
        scope: {
            activeTab: '='
        },
        link: function (scope, element, attributes) {
            var userDetails = $userDetails;
            if ($userDetails) {
                scope.user = {
                    name: userDetails.name ? userDetails.name : 'KoBoForm User',
                    avatar: userDetails.gravatar ? userDetails.gravatar: (staticFilesUri + '/img/avatars/example-photo.jpg')
                };
            } else {
                scope.user = {
                    name: 'KoBoForm User',
                    avatar: staticFilesUri + '/img/avatars/example-photo.jpg'
                }
            }

            scope.sections = $configuration.sections();

            scope.isActive = function (name) {
                return name === scope.activeTab ? 'is-active' : '';
            }
        }
    }
}

I want to mock the dependencies to unit test the different code paths with values known by the unit tests. I have the following sample unit test:

it('should set $scope.user to values passed by $userDetails', 
    inject(function($compile) {
        var element = '<div top-level-menu></div>';
        element = $compile(element)($scope);
        $scope.$apply();

        expect(element.isolateScope().user.name).toBe('test name');
        expect(element.isolateScope().user.avatar).toBe('test avatar');
    }
));

This gives me two problems.

First, since the template is in an external file, when it loads it tries to fetch it and errors out beacause the file is nowhere to be found, which is logical since it's in a test environment and not an actual server.

Second, there's no apparent way to mock the dependencies injected into the directive through its constructor. When testing controllers you can use the $controller service, but since directives are instantiated indirectly by compiling an html tag with a passed scope, there's no way to instantiate it directly (e.g. there's no analogous $directive). This impedes me from setting $userDetails.name and $userDetails.gravatar to 'test name' and 'test avatar' respectively.

How do I get the directive to compile properly and run with a custom $userDetails dependency?

解决方案

To load the template file you must configure karma-ng-html2js-preprocessor in karma.

First, visit this page and follow the installation instructions. Then, you need to add a couple of entries in your karma.config.js file:

files: [
    'templates/*.html'
],

this tells karma to load all html files in the templates folder (if your templates are somewhere else, put that folder there).

preprocessors: { '**/*.html': 'ng-html2js' },

this tells karma to pass all html files through the ng-html2js preprocessor, which then transforms them into angular modules that put the templates into the $templateCache service. This way, when $httpBackend queries the "server" for the template, it get's intercepted by the template cache and the correct html is returned. All fine here, except for the template's URL: it has to match the templateUrl property in the directive, and ng-html2js passes the full path as the uri by default. So we need to transform this value:

ngHtml2JsPreprocessor: {
    cacheIdFromPath: function(filepath) {

        var matches = /^\/(.+\/)*(.+)\.(.+)$/.exec(filepath);

        return 'templates/' + matches[2] + '.' + matches[3];
    }
},

this receives filepath and passes it through a regular expression that extracts the path, file name and extension into an array. You then prepend 'templates/ to the file name and extension and you get the expected uri.

After all this is done making the template available is a matter of loading the module before your test is run:

beforeEach(module('templates/TopLevelMenu.Template.html'));

keep in mind, module is an external service located in angular-mocks.js.

for injecting a custom service into the directive you need to override the service's provider:

beforeEach(module(function ($provide) {
    $provide.provider('$userDetails', function () { 
        this.$get = function () {
            return {
                name: 'test name', 
                gravatar: 'test avatar'
            };
        }
    });
}));

$provide is the service that provides your providers. So, if you want to inject a mock dependency you override the provider here.

With that code executing before your test you'll have a mock $userDetails service that returns your predefined strings.

这篇关于如何在 Karma 上使用 Jasmine 将模拟依赖项注入到角度指令中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆