【问题标题】:Get compiled template for controller in unit test在单元测试中获取控制器的编译模板
【发布时间】:2014-09-10 08:00:20
【问题描述】:

我有以下控制器:

angular.module('app').controller('userList'
 , ['$scope','appRules',function ($scope,appRules) {
  $scope.isUserInRole=function(user,role){
    console.log("exucuting userIsInRole:",user,role);//never happens
    return  appRules.userIsInRole(user,role);
  }
  window.sscope=$scope;
  console.log($scope.menu);
}]); 

这用于以下路线:

angular.module('app', [,'ui.bootstrap','ngRoute'])
.config(['$routeProvider','$locationProvider'
,function($routeProvider,$locationProvider){
  $locationProvider.html5Mode(true);
  $locationProvider.hashPrefix('!');
  $routeProvider
          .when('/admin/users',{
            templateUrl:'app/js/partials/admin/users/list.html',
            controller:'userList'
          });
}]);

模板会有一些东西来检查用户是否可以看到这个页面(即使有人破解了javascript也没关系,因为当数据被获取或提交时,服务器也会检查)。

<div data-ng-if="!isUserInRole(login.user,'Administrator')" class="red">
  Please log in or log in as another user.
</div>
<div data-ng-if="isUserInRole(login.user,'Administrator')" class="green">
  Page to edit users.
</div>

现在我想在我的测试中创建一个控制器,看看它何时渲染是否正确:

  var controller,scope,element;

  beforeEach(inject(function($controller
   ,$rootScope,$compile,appRules) {
    scope=$rootScope;
    scope.login={};
    scope.isUserInRole=appRules.userIsInRole;
    controller = $controller('userList',{
      $scope:scope
    });
    //here I have the controller but how to get the html it generates
    //  when rendering the template?
    element = $compile("<div ng-controller='userList'>"
      +"<div data-ng-view=''></div></div>")(scope);
    //here element isn't rendered at all, works on directives but how
    //  to do this with controllers?
    console.log("element is:",element);
  }));

我想写一个类似的测试

  it('Check page won\'t show if user is not set or is not Administrator.'
  , function() {
    expect($(element).text().trim()
      .indexOf("Please log in or log in as another user."))
      .toBe(0,'Show login message when login.user is not set.');
  });

不知道如何获取元素。

[更新]

我实际上是在尝试测试一个模板,因为这是由一个控制器使用的,我想用一个控制器来测试它,但正如 Jon 建议的那样,它可以作为模板加载。

我不知道如何使它编译和操作范围并再次编译。以下:

var el = angular.element(
  $templateCache
  .get('app/js/partials/admin/users/list.html'));
$rootScope = _$rootScope_;
$rootScope.menu={login:{}};
el.scope($rootScope);
$rootScope.$apply();
console.log(el.text());

这给了我Please log in or log in as another user.Page to edit users. 的输出看起来元素只是未编译的原始元素。

尝试这样的事情也无济于事:

  beforeEach(inject(function(_$compile_
   , _$rootScope_,appSettings,$templateCache){
    console.log("before each");
    var el = angular.element($templateCache
      .get('app/js/partials/admin/users/list.html'));
    var compile=_$compile_;
    var $rootScope=_$rootScope_;
    $rootScope.login:{};
    $rootScope.isUserInRole=appSettings.userIsInRole;
    //I would think that compile would invoke the ng-if and appSettings.userIsInRole
    //  but no console log in that function is shown.
    var element = compile($templateCache
      .get('app/js/partials/admin/users/list.html'))($rootScope);
    console.log("element is:",element.html());//=undefined    
  }));

【问题讨论】:

  • 控制器范围是隔离的,因此除非您的 userList 控制器应用于封装所有其他内容的 html 元素,否则这将无法作为可扩展的解决方案,因为它需要大量嵌套范围,从而增加内存。我写了一篇关于 Angular 用户安全性的博文,这是一种更好、更具可扩展性的方法 - jonsamwell.com/url-route-authorization-and-security-in-angular
  • @JonSamwell 感谢您的回复。一般来说;如果我想检查在使用路由/控制器时是否显示加载图像,因为在其他地方指令需要获取一些数据,然后想查看在获取/解决获取数据时这是否消失了我将如何进行测试那?我可以模拟 http 服务承诺并查看指令如何呈现,但这是在某些路由上使用的控制器。还没有找到任何工作代码来测试这个。
  • 这听起来更像是一个集成测试问题。单元测试应该测试隔离,所以你应该测试加载图像的东西是通过路由拦截器在 http 调用上触发的,然后你应该有单独的测试来测试实际执行 dom 操作以将图像放在屏幕上的东西 - 也许为那个问一个单独的问题。
  • @JonSamwell 我想测试模板以在 scope.login.loading 为真时显示一件事,当 scope.login.loading 为假且 scope.login.user 为真时显示另一件事scope.login.user 为真时的事情。由于模板由控制器使用,我认为我在控制器中添加了测试,但如果有办法编译具有不同范围的模板,也可以将控制器排除在外。更改范围的部分已在另一个单元测试中介绍,但我想确保模板行为正确。

标签: javascript angularjs unit-testing controller jasmine


【解决方案1】:

使用 ng-templates 或 ng-html2js 将模板添加到缓存中,以便在 Karma 运行时可用。然后这样做:

var 范围 = null;

beforeEach(inject(function($templateCache,_$compile_,_$rootScope_, _$controller_) {

    //get the template from the cache
    template = $templateCache.get('src/main/app/components/someController/widget-search.tpl.html');

    $compile = _$compile_;
    $rootScope = _$rootScope_;
    $controller = _$controller_;

    scope = $rootScope.new();
    //compile it
    element = $compile(template)(scope);
}));

 //then shove your compiled element into your controller instance



it( 'should be instantiated since it does not do much yet', function(){

            ctrl = $controller('SomeController',
                {
                    $scope: scope,
                    $element: element
                });

             scope.$digest();

            // my controller should set a variable called hasInstance to true when it initializes. Here i'm testing that it does.
            expect( ctrl.hasInstance).toBeTruthy();

        });

欲了解更多信息,请参阅http://angular-tips.com/blog/2014/06/introduction-to-unit-test-directives/

【讨论】:

  • 感谢您的回答,您确定这应该有效吗?我得到的只是测试失败:Expected undefined to be truthy. 目前我正在使用带有 transclude true 的虚拟指令编译模板。
  • 当然,您必须有一个名为 hasInstance 的属性——这是我正在测试的变量。我的控制器在初始化时创建了 hasInstance = true。在这里,我确保它做我想做的事。
  • 哦,完全忘记了,你需要 $scope.$digest() 否则你编译的元素不会发生任何事情。在 karma 中,它不会为您执行这些操作,例如 $scope.$apply() 和 $timeout.flush()
  • $rootScope.new 不是一个函数,它应该是 $rootScope.$new。
  • 以这种方式创建控制器 - 是否可以通过播放提供的元素来与其交互(例如。.triggerHandler('click'))?
【解决方案2】:

如果您使用 Karma,您可以使用 ng-html2js karma 预处理器,它基本上会运行您的模板并将它们转换为您可以包含在测试中的模块,该模块会填充 $templateCache 以便您的模板可用。

【讨论】:

  • 是的,我已经做到了。没有加载模板。它们都编译为js文件。在模板文件中是这样的:$templateCache.put('app/js/partials/admin/users/list.html',...
  • 那么你可以从模板缓存中获取它吗? $templateCache.get('app/js/partials/admin/users/list.html') 并且您将拥有 html 字符串 - 然后您可以只执行 angular.element(htmlStr) 并且您将拥有一个新元素。跨度>
  • 我已经更新了我的答案。我究竟该如何使用这个 angular.element 并编译它?
猜你喜欢
  • 2015-01-17
  • 1970-01-01
  • 2014-09-07
  • 2022-01-16
  • 2014-07-30
  • 1970-01-01
  • 2012-02-27
  • 2020-01-05
  • 1970-01-01
相关资源
最近更新 更多