benjovanic
benjovanic

Reputation: 537

Jasmine mock chained methods with Karma and Angular

I want to mock angular.element. And I want to ensure angular.element has been called a certain amount of times and that anguler.element.attr has been called too.

I have the following code:

var things = $scope.getThings();

for (var i = 0; i < things.length; i++) {
  if (things[i].type == "xyz") {
    angular.element("#thing-" + things[i].id)
      .attr("foo", things[i].bar);
  };
};

In my test I have:

var things = [
  {
    id: 1,
    type: "xyz",
    bar: 10
  },
  {
    id: 2,
    type: "abc",
    bar: 33
  }
];

spyOn($rootScope, "getThings").and.returnValue(things);
spyOn(angular, "element").and.returnValue();

$rootScope.doThings(); // call controller method

expect(angular.element.calls.count()).toBe(1);

But it gives the following error:

TypeError: undefined is not an object (evaluating 'angular.element("#thing-" + things[i].id).attr')

I also want my test to have something like:

expect(angular.element.attr.calls.count()).toBe(1);
expect(angular.element.attr).tohaveBeenCalledWith("foo", things[0].bar);

Upvotes: 1

Views: 2346

Answers (2)

Estus Flask
Estus Flask

Reputation: 222309

The way chained methods should be spied or mocked solely depends on how they are defined on the spied objects.

In the case of Angular jqLite, or in your case, jQuery (both are transparently served through angular.element facade) chained methods are defined on constructor prototype, which is exposed on factory function as angular.element.prototype or jQuery.prototype (angular.element === jQuery when jQuery is loaded).

In order to spy on both angular.element and angular.element(...).attr it should be:

spyOn(angular, 'element').and.callThrough();
spyOn(angular.element.prototype, 'attr').and.callThrough();
...
expect(angular.element).toHaveBeenCalled();
expect(angular.element.prototype.attr).toHaveBeenCalled();

callThrough is important in this case because otherwise the whole chain should be stubbed manually.

Upvotes: 1

Oron Bendavid
Oron Bendavid

Reputation: 1533

try to add this code:

var spy;

beforeEach(function() {
    spy = spyOn(angular, 'element').....
});

afterEach(function() {
    spy.andCallThrough();
});

you can find more in: Jasmin docs

Upvotes: 1

Related Questions