【问题标题】:Testing AngularUI Bootstrap modal instance controller测试 AngularUI Bootstrap 模态实例控制器
【发布时间】:2014-08-13 22:10:30
【问题描述】:

这是这个问题的后续问题:Mocking $modal in AngularJS unit tests

引用的 SO 是一个很好的问题,答案非常有用。然而,在此之后我留下的问题是:如何对模态实例控制器进行单元测试?在引用的 SO 中,调用控制器被测试,但模态实例控制器被模拟。可以说后者也应该进行测试,但事实证明这非常棘手。原因如下:

我将从此处引用的 SO 中复制相同的示例:

.controller('ModalInstanceCtrl', function($scope, $modalInstance, items){
  $scope.items = items;
  $scope.selected = {
    item: $scope.items[0]
  };

  $scope.ok = function () {
    $modalInstance.close($scope.selected.item);
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
});

所以我的第一个想法是直接在测试中实例化控制器,就像任何其他被测控制器一样:

beforeEach(inject(function($rootScope) {
  scope = $rootScope.$new();
  ctrl = $controller('ModalInstanceCtrl', {$scope: scope});
});

这不起作用,因为在这种情况下,Angular 没有提供程序来注入 $modalInstance,因为它是由 UI modal 提供的。

接下来,我转向计划 B:使用 $modal.open 实例化控制器。这将按预期运行:

beforeEach(inject(function($rootScope, $modal) {
  scope = $rootScope.$new();
  modalInstance = $modal.open({
    template: '<html></html>',
    controller: 'ModalInstanceCtrl',
    scope: scope
  });
});

(注意,模板不能是空字符串,否则它会神秘地失败。)

现在的问题是我无法查看范围,这是对资源收集等进行单元测试的惯用方式。在我的真实代码中,控制器调用资源服务来填充选项列表;我尝试对此进行测试设置了一个 expectGet 来满足我的控制器正在使用的服务,并且我想验证控制器是否将结果放在其范围内。但是模态正在为模态实例控制器创建一个 new 范围(使用我作为原型传入的范围),我不知道如何才能获得该范围的一个洞。 modalInstance 对象没有进入控制器的窗口。

对“正确”的测试方法有什么建议吗?

(注意:为模态实例控制器创建派生作用域的行为并不意外——这是记录在案的行为。无论如何,我关于如何测试它的问题仍然有效。)

【问题讨论】:

    标签: javascript angularjs unit-testing angular-ui


    【解决方案1】:

    按照以下给定步骤:

    • 为 ModalInstance 定义存根,如下所示

              uibModalInstanceStub = {
                  close: sinon.stub(),
                  dismiss: sinon.stub()
              };
      
    • 在创建控制器时传递模态实例存根

          function createController() {
              return $controller(
                  ppcConfirmGapModalComponentFullName,
                  {
                      $scope: scopeStub,
                      $uibModalInstance: uibModalInstanceStub
                  });
          }
      });
      
    • 存根方法 close()、dismiss() 将作为测试的一部分被调用

      it('确认模式 - 验证确认操作,在 ok() 调用调用 modalInstance close() 函数', function() { 行动='好'; scopeStub.item = testItem; 创建控制器(); scopeStub.ok(); });

    【讨论】:

      【解决方案2】:

      $uidModalInstance 也有同样的问题,你可以用类似的方法解决它:

      var uidModalInstance = { close: function() {}, dismiss: function() {} };
      
      $ctrl = $controller('ModalInstanceCtrl', {
         $scope: $scope,
         $uibModalInstance: uidModalInstance
      });
      

      或者如@yvesmancera 所说,您可以改用 jasmine.createSpy 方法,例如:

      var uidModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);
      
      $ctrl = $controller('ModalInstanceCtrl', {
         $scope: $scope,
         $uibModalInstance: uidModalInstance
      });
      

      【讨论】:

        【解决方案3】:

        或者,如果您使用 jasmine,您可以使用 createSpy 方法模拟 $uibModalInstance

        beforeEach(inject(function ($controller, $rootScope) {
          $scope = $rootScope.$new();
          $uibModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);
        
          ModalCtrl = $controller('ModalCtrl', {
            $scope: $scope,
            $uibModalInstance: $uibModalInstance,
          });
        }));
        

        无需在每个方法上调用 spyOn 即可对其进行测试,假设您有 2 个作用域方法,cancel()confirm()

        it('should let the user dismiss the modal', function () {
          expect($scope.cancel).toBeDefined();
          $scope.cancel();
          expect($uibModalInstance.dismiss).toHaveBeenCalled();
        });
        
        it('should let the user confirm the modal', function () {
          expect($scope.confirm).toBeDefined();
          $scope.confirm();
          expect($uibModalInstance.close).toHaveBeenCalled();
        });
        

        【讨论】:

        • 尽管 Sunil D 的答案肯定有效,但我认为这绝对应该是大多数实现的公认答案,因为您很可能想要监视它们。如果你不想这样,那么 Sunil D 的作品就很棒。
        • 加一。我同意,我发现自己以这种方式为模拟对象创建间谍,而不是我上面所做的。
        • @perry 谢谢!但是这个问题没有被标记为 jasmine,甚至没有被提及,所以 Sunil 的答案适用于任何测试框架,而我的只有在你使用 jasmine 时才有效。
        • @yvesmancera 这是真的。我想我错过了。所以现在进入 Jasmine,当我看到 Angular 测试我的想法时,我就会去那里:D
        • @perry FWIW:我在所选答案中使用了 Sunil 的方法,并且 spyOn 可以正常使用。
        【解决方案4】:

        我通过直接实例化控制器来测试模态对话框中使用的控制器(与您最初想在上面做的方式相同)。

        由于没有 $modalInstance 的模拟版本,我只需创建一个模拟对象并将其传递给控制器​​。

        var modalInstance = { close: function() {}, dismiss: function() {} };
        var items = []; // whatever...
        
        beforeEach(inject(function($rootScope) {
          scope = $rootScope.$new();
          ctrl = $controller('ModalInstanceCtrl', {
              $scope: scope, 
              $modalInstance: modalInstance, 
              items: items
          });
        }));
        

        现在控制器的依赖关系已经满足,您可以像测试任何其他控制器一样测试此控制器。

        例如,我可以执行spyOn(modalInstance, 'close'),然后断言我的控制器正在适当的时间关闭对话框。

        【讨论】:

        • 谢谢!我对 Angular 还很陌生,我没有意识到你可以用自己的值覆盖控制器 DI。
        • 我收到此错误Argument 'ModalInstanceCtrl' is not a function, got undefined ...
        猜你喜欢
        • 2015-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多