Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jasmine - Spying on a method call within a constructor

I want to test whether the following method is called with in my Javascript object constructor. From what I have seen in the Jasmine documentation, I can spy on a constructor method and I can spy on methods after an object has been instantiated, but I can't seem to be able to spy on a method before the object is constructed.

The object:

Klass = function() {     this.called_method(); };  Klass.prototype.called_method = function() {   //method to be called in the constructor. } 

I want to do something like this in the spec:

it('should spy on a method call within the constructor', function() {     spyOn(window, 'Klass');     var obj = new Klass();     expect(window.Klass.called_method).toHaveBeenCalled(); }); 
like image 860
Levi McCallum Avatar asked Jan 04 '12 21:01

Levi McCallum


People also ask

How do I spy on service property in Jasmine?

In Jasmine, you can do anything with a property spy that you can do with a function spy, but you may need to use different syntax. Use spyOnProperty to create either a getter or setter spy. it("allows you to create spies for either type", function() { spyOnProperty(someObject, "myValue", "get").

What does Jasmine spyOn do?

Jasmine spies are used to track or stub functions or methods. Spies are a way to check if a function was called or to provide a custom return value. We can use spies to test components that depend on service and avoid actually calling the service's methods to get a value.

How do you use Jasmine createSpy?

Jasmine: createSpy() and createSpyObj() Jasmine's createSpy() method is useful when you do not have any function to spy upon or when the call to the original function would inflict a lag in time (especially if it involves HTTP requests) or has other dependencies which may not be available in the current context.


2 Answers

Spy directly on the prototype method:

describe("The Klass constructor", function() {   it("should call its prototype's called_method", function() {       spyOn(Klass.prototype, 'called_method');  //.andCallThrough();       var k = new Klass();       expect(Klass.prototype.called_method).toHaveBeenCalled();   }); }); 
like image 198
Dave Newton Avatar answered Sep 28 '22 00:09

Dave Newton


Broadly, I agree with Dave Newton's answer above. However, there are some edge-cases to this approach that you should consider.

Take a variation to Dave's solution, with another test-case:

// production code var Klass = function() {   this.call_count = 0;   this.called_method(); }; Klass.prototype.called_method = function() {   ++this.call_count; };  // test code describe("The Klass constructor", function() {   it("should call its prototype's called_method", function() {     spyOn(Klass.prototype, 'called_method');     var k = new Klass();     expect(k.called_method).toHaveBeenCalled();   });   it('some other test', function() {     var k = new Klass();     expect(k.call_count).toEqual(1);   }); }); 

The second test will fail because the spy setup in the first test persists across the test boundaries into the second method; called_method doesn't increment call_count, so this.call_count does not equal 1. It's also possible to come up with scenarios with false positives - tests that pass, that shouldn't.

On top of this, because the spy remains, the more Klass instances that are created, the bigger the memory heap the spy will consume, because the spy will record each call to called_method. This probably isn't a problem in most circumstances, but you should be aware of it, just in case.

A simple solution to this problem would be to make sure that the spy is removed after it has been used. It can look a bit ugly, but something like this works:

// test code describe("The Klass constructor", function() {   it("should call its prototype's called_method", function() {     var spy = jasmine.createSpy('called_method');     var method = Klass.prototype.called_method;     Klass.prototype.called_method = spy;     var k = new Klass();     expect(spy).toHaveBeenCalled();     Klass.prototype.called_method = method;   }); 

[NOTE - a little opinion to finish] A better solution would be to change the way you write production code to make the code easier to test. As a rule, spying on prototypes is probably a code-smell to be avoided. Instead of instantiating dependencies in the constructor, inject them. Instead of doing initialization in the constructor, defer to an appropriate init method.

like image 23
alecmce Avatar answered Sep 28 '22 01:09

alecmce