问题 如何测试angularjs指令以监视函数调用?


下面的代码执行但抱怨没有调用element.popover。我似乎无法弄清楚问题是什么。

提前感谢您的帮助。

指示:

angular.module('directives', []).

directive('popOver', function ($http) {

    return {
        restrict:'C',

        link: function (scope, element, attr) {
            element.bind('mouseover', function (e) {
                $http.get("someurl" + attr.chatid + ".json").success(function (data) {
                    element.popover({content: data.firstName + " " + data.lastName });
                });
            });
        }
    }
})

茉莉花测试:

'user strict'

describe('directives', function() {
    beforeEach(module('directives'));
    describe('popOver', function() {
    var $scope, compile, location,  $httpBackend, elm;

    beforeEach(inject(function($rootScope, $compile, _$httpBackend_) {
        $scope = $rootScope.$new();
        compile = $compile;
        $httpBackend = _$httpBackend_;
        elm = angular.element('<i class="pop-over" data-placement="top" data-chatid="testChatId" > </i>');
        compile(elm)($scope);

    }));

    it('should call element.popover()', function() {
        $httpBackend.expectGET('someurl/testChatId.json').
            respond([ {firstName: 'test', lastName: 'user'}]);

        spyOn(elm, 'popover').andCallThrough();

        elm.trigger('mouseover');
        $httpBackend.flush();

        expect(elm.popover).toHaveBeenCalled();
    });
  });
});

输出:

Chrome 26.0 (Mac) directives popOver should call element.popover() FAILED
Expected spy popover to have been called.
Error: Expected spy popover to have been called.

3010
2018-03-30 15:17


起源



答案:


更新:

我无法解决您的具体问题。主要是因为我无法获得角度种子/这是永远的,但我想我会让我的答案更完整。

一般来说有两种方法可以解决这个问题:

  1. 监视除某些人触发的功能之外的其他功能 事件/中介
  2. 在创建对象之前监视函数的原型。换一种说法: spyOn(MyObjectNamespace.Class.prototype, 'functionToSpyOn')

然后恢复,你应该没事。


我只是模糊地熟悉角度,但也经历过类似的问题。

解决方案1

您可以将功能分开,而不是匿名指定。这有助于专门测试您的功能并避免所有有角度的东西。

解决方案2

有时使用框架这是不可能的。这里的主要问题是你的间谍附加太晚而且引用丢失或被覆盖。

测试:

describe('directives', function() {
    beforeEach(module('directives'));
    describe('popOver', function() {
    var $scope, compile, location,  $httpBackend, elm;

    beforeEach(inject(function($rootScope, $compile, _$httpBackend_) {
        $scope = $rootScope.$new();
        compile = $compile;
        $httpBackend = _$httpBackend_;
        elm = angular.element('<i class="pop-over" data-placement="top" data-chatid="testChatId" > </i>');
        compile(elm)($scope);

    }));

    it('should call element.popover()', function() {
        var popoverFunction = $.fn.popover;
        $httpBackend.expectGET('someurl/testChatId.json').
            respond([ {firstName: 'test', lastName: 'user'}]);

        spyOn($.fn, 'popover').andCallThrough();

        elm.trigger('mouseover');
        $httpBackend.flush();

        expect($.fn.popover).toHaveBeenCalled();
        //restore popover, use sinon's restore fn instead here
        $.fn.popover = popoverFunction
    });
  });
});

你可以将Sinon与Jasmine一起使用。 Sinon有一个spy.restore函数,可以为你排除第一行和最后一行。在我自己的测试中,我将第一行和间谍创建放在beforeEach中,并在afterEach中恢复。


8
2018-04-09 20:38



通过以这种方式重构代码,您正在测试指令的调用而不是元素上的popover函数。 - anazimok
@anazimok ok我改变了它...如果你现在调用myPopOver并提供了一个范围(null),元素(一个dom元素)和attr(有问题的attr)。您实际上可以测试您的弹出功能。这对我来说很有意义,因为你不想测试角度JS的指令语句,而是测试你自己的代码。此外,您甚至不需要监视myPopOver。您只需测试它的作用即可。这是一个更强大的考验。 - Parris
感谢您的更新,但我认为您错过了重点。我不想测试myPopOver函数我想测试element.popover(...)被调用。是的,我可以用不同的方式编写这个测试,但我想理解为什么它不起作用。 ;) - anazimok
@anazimok哈哈!抱歉。有一个更新让我知道,如果这对您有用。 - Parris
@Paris老兄,你得到A +尝试。但还是没有运气! - anazimok


答案:


更新:

我无法解决您的具体问题。主要是因为我无法获得角度种子/这是永远的,但我想我会让我的答案更完整。

一般来说有两种方法可以解决这个问题:

  1. 监视除某些人触发的功能之外的其他功能 事件/中介
  2. 在创建对象之前监视函数的原型。换一种说法: spyOn(MyObjectNamespace.Class.prototype, 'functionToSpyOn')

然后恢复,你应该没事。


我只是模糊地熟悉角度,但也经历过类似的问题。

解决方案1

您可以将功能分开,而不是匿名指定。这有助于专门测试您的功能并避免所有有角度的东西。

解决方案2

有时使用框架这是不可能的。这里的主要问题是你的间谍附加太晚而且引用丢失或被覆盖。

测试:

describe('directives', function() {
    beforeEach(module('directives'));
    describe('popOver', function() {
    var $scope, compile, location,  $httpBackend, elm;

    beforeEach(inject(function($rootScope, $compile, _$httpBackend_) {
        $scope = $rootScope.$new();
        compile = $compile;
        $httpBackend = _$httpBackend_;
        elm = angular.element('<i class="pop-over" data-placement="top" data-chatid="testChatId" > </i>');
        compile(elm)($scope);

    }));

    it('should call element.popover()', function() {
        var popoverFunction = $.fn.popover;
        $httpBackend.expectGET('someurl/testChatId.json').
            respond([ {firstName: 'test', lastName: 'user'}]);

        spyOn($.fn, 'popover').andCallThrough();

        elm.trigger('mouseover');
        $httpBackend.flush();

        expect($.fn.popover).toHaveBeenCalled();
        //restore popover, use sinon's restore fn instead here
        $.fn.popover = popoverFunction
    });
  });
});

你可以将Sinon与Jasmine一起使用。 Sinon有一个spy.restore函数,可以为你排除第一行和最后一行。在我自己的测试中,我将第一行和间谍创建放在beforeEach中,并在afterEach中恢复。


8
2018-04-09 20:38



通过以这种方式重构代码,您正在测试指令的调用而不是元素上的popover函数。 - anazimok
@anazimok ok我改变了它...如果你现在调用myPopOver并提供了一个范围(null),元素(一个dom元素)和attr(有问题的attr)。您实际上可以测试您的弹出功能。这对我来说很有意义,因为你不想测试角度JS的指令语句,而是测试你自己的代码。此外,您甚至不需要监视myPopOver。您只需测试它的作用即可。这是一个更强大的考验。 - Parris
感谢您的更新,但我认为您错过了重点。我不想测试myPopOver函数我想测试element.popover(...)被调用。是的,我可以用不同的方式编写这个测试,但我想理解为什么它不起作用。 ;) - anazimok
@anazimok哈哈!抱歉。有一个更新让我知道,如果这对您有用。 - Parris
@Paris老兄,你得到A +尝试。但还是没有运气! - anazimok


我得到了它的工作。
在测试期间,需要在angular.js之前加载jquery和jquery popover js文件。此命令应在testacular.conf.js文件中指定。此外,http的网址缺少'/'。这是适合我的代码:


angular.module('directives', []).

directive('popOver', function($http) {

  return {
    restrict: 'C',

    link: function(scope, element, attr) {
      element.bind('mouseover', function(e) {
        $http.get("someurl/" + attr.chatid + ".json").success(function(data) {
          element.popover({
            content: data.firstName + " " + data.lastName
          });
        });
      });
    }
  }
})



'user strict'

describe('directives', function() {
  beforeEach(module('directives'));
  describe('popOver', function() {
    var $scope, compile, location, $httpBackend, elm;

    beforeEach(inject(function($rootScope, $compile, _$httpBackend_) {
      $scope = $rootScope.$new();
      compile = $compile;
      $httpBackend = _$httpBackend_;
      elm = angular.element('<i class="pop-over" data-placement="top" data-chatid="testChatId" > </i>');
      compile(elm)($scope);

    }));

    it('should call element.popover()', function() {
      $httpBackend.expectGET('someurl/testChatId.json').
      respond([{
        firstName: 'test',
        lastName: 'user'
      }]);
      //spyOn(elm, 'popover').andCallThrough();
      spyOn($.fn, 'popover').andCallThrough();

      elm.trigger('mouseover');
      $httpBackend.flush();

      //expect(elm.popover).toHaveBeenCalled();
      expect($.fn.popover).toHaveBeenCalled();
    });
  });
});

5
2018-04-19 15:32