【问题标题】:How to test _.defer() using Jasmine, AngularJs如何使用 Jasmine、AngularJs 测试 _.defer()
【发布时间】:2015-01-13 12:12:11
【问题描述】:

我已经问过这个question,主要的一点是范围在终端中不存在,但它存在于 Chrome 调试工具中。尽管得到了答案,但它并没有得到解决。

问题是测试以下指令的正确语法是什么,尤其是expect(scope.measurementScroll).toBe(true); 行。在浏览网页时,我找不到任何类似的问题,大多数问题都与$q.defer() 有关,在我的情况下,有下划线方法_.defer()

控制器

'use strict';
angular.module('myApp')
  .controller('MeasurementsTimelineCtrl', ['$scope', '$state', 'Measurements', function($scope, $state, Measurements) {
    $scope.measurements = null;
    var userId = $scope.currentUser ? $scope.currentUser.id : null;
    if (userId) {
      var listOfMeasurements = Measurements.userIndex(userId);
      listOfMeasurements.then(function(data){
        $scope.measurements = data;
        $scope.$broadcast('measurements-updated', $scope.measurements);
      });
    }
  }]);

指令:

'use strict';
angular.module('myApp')
  .directive('dashboardMeasurementTimeline', ['$window', function($window) {
    return {
      restrict: 'E',
      templateUrl: 'myView.html',
      controller: 'MeasurementsTimelineCtrl',
      link: function(scope, element){
        scope.$on('measurements-updated', function(measurements) {
          _.defer(function(){
            if(measurements) {
              scope.measurementScroll = true;
            }
          });
        });
      }
    };
  }]);

测试

'use strict';
describe('Directive: dashboardMeasurementTimeline', function () {

  var $rootScope, $compile, element, scope;

  beforeEach(function() {
    module('myApp');

    inject(function($injector) {
      $rootScope = $injector.get('$rootScope');
      $compile = $injector.get('$compile');
    });

    scope = $rootScope.$new();
    element = angular.element('<dashboard-measurement-timeline></dashboard-measurement-timeline>');
    element = $compile(element)(scope);

    scope.currentUser = {id : 'someId'};
    scope.$digest();
    scope.measurements = [{id: 'someId', time_of_test: 'Tue, 30 Dec 2014 14:00:00 -0000'},
      {id: 'someId', time_of_test: 'Thu, 20 Nov 2014 03:00:00 -0000'},];
    scope.$broadcast('measurements-updated', scope.measurements);
    scope.$apply();
  });

  it('should assign true value to measurementScroll', function () {
    expect(scope.measurementScroll).toBe(true);
  });
});

【问题讨论】:

  • 我用的插件是karma-jasmine: 0.1.5,google了一下发现相当于Jasmine 2.0

标签: angularjs unit-testing jasmine underscore.js


【解决方案1】:

您可以通过注入一个模拟下划线库来做到这一点,并在测试中定义一个 defer 函数。一种方法是定义自己的工厂_,然后可以轻松地对其进行模拟:

app.factory('_', function($window) {
  return $window._;
});

然后在指令中,你必须通过注入来使用它:

app.directive('dashboardMeasurementTimeline', ['_', function(_) {

在测试中,你可以模拟它:

var deferCallback;
beforeEach(module(function($provide) {
  deferCallback = null;
  $provide.value('_', {
    defer: function(callback) {
      deferCallback = callback;
    }
  });
}));

这意味着该指令将使用模拟 _ 而不是真实的,这会将传递给 defer 的回调保存为 deferCallback,以便您可以在需要时调用它:

scope.$broadcast('measurements-updated', scope.measurements);
deferCallback();

这使得测试同步,这通常比使用done() 更好,因为它使测试尽可能快。

你可以在http://plnkr.co/edit/r7P25jKzEFgE5j10bZgE?p=preview看到以上工作

【讨论】:

  • 非常感谢您亲爱的@Michal Charezma,您的解决方案很棒,解决了问题
  • 我现在有一个小问题,在模拟 _ 并将其导入指令后,使用 _ 的其他方法时会引发错误,例如我正在使用:angular.element(scrollContainer).bind('scroll', _.throttle(scope.disableButtons, 500)); 好像throttle()这个方法不存在
  • @Max 您应该可以将throttle 添加到传递给$provide.value 的对象中
【解决方案2】:

如果您没有 lodash 作为要注入的服务,您可以监视 defer 方法,如果您关心传递的函数的执行,那么您可以设置一个 callFake 并调用参数函数传递给真正的defer

spyOn(_, 'defer').and.callFake(f => f());

更深入地假设您有以下调用:

function toTest() {
 _.defer(() => service.callAFunction());
}

那么在你的测试中你可以说:

it('should call service.callAFunction', () => {
   spyOn(service, 'callAFunction');
   spyOn(_, 'defer').and.callFake(f => f());
   toTest();
   expect(_.defer).toHaveBeenCalled();
   expect(service.callAFunction).toHaveBeenCalled();
}

【讨论】:

    【解决方案3】:

    @Michal Charezma 为这个问题提供了一个很好的解决方案,实际上是一个解决方案,但事实证明它对 _ 的其余功能还有一些其他限制。 例如:

    angular.element(scrollContainer).bind('scroll', _.throttle(scope.disableButtons, 500));
    

    引发throttle 未定义的错误。

    按照@Michal 的逻辑,找到了另一个解决方案,可以让_.throttle() 等函数正常工作。因此,不要导入 _ 并使用:

    app.factory('_', function($window) {
      return $window._;
    });
    

    人们只能模拟 defer 函数,来自如下规范:

    var deferCallback = $window._.defer.mostRecentCall.args[0];
    deferCallback()
    

    【讨论】:

    • 我不认为这个方法实际上是一个模拟对象。我怀疑它甚至可能导致回调延迟执行两次,一次是强制执行,另一次是在调用 defer 引发的超时之后。
    • 哦,这真的很有趣,必须再次检查规范。谢谢你。 )
    猜你喜欢
    • 2012-10-12
    • 2015-12-09
    • 1970-01-01
    • 1970-01-01
    • 2016-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-20
    相关资源
    最近更新 更多