Spring is in the air here in sunny NYC! Well, it was sunny for a bit. Now it’s turned into that classic film-noir drizzle. That’s authentic New York weather outside our window as we do the Round Up!
Ask for Help
I asked, “In Jasmine, how do you spy on a constructor?” Suppose you have a constructor called
Widget. SayingspyOn(window, "Widget")swaps out the realWidgetfunction with a spy. The realWidgetimplementation takes its prototype with it, which means that Widgets created while the constructor is spied on don’t get the methods aWidgetwould get. That’s true even when you spy withspyOn(window, "Widget").andCallThrough().Also, there doesn’t seem to be a way to stub out object construction and return an object of your choosing. In Rspec in Ruby you can say,
Widget.should_receive(:new).and_return(my_fake_widget)
because
.newis a class method. But in Javascript,newis a keyword which always creates a new object. I’m guessing there’s no good way around this, but if there is one, we could really use it.Update: This is, as it turns out, entirely incorrect. Jasmine’s
.andReturn()does let you stub constructors properly. I still have no idea how this works. See the comments for more discussion.
Interesting Stuff
Todd notifies us that in ActiveRecord, dynamic finders with too-few arguments fail silently. That is, if you say,
Person.find_by_first_name_and_last_name_and_email(”Todd”, “Persen”)
and fail to specify a value for
email, the value is taken to benil. That means that the finder will look for someone named “Todd Persen” with anilemail address. You might have meant that. More likely, you made a mistake. AR should probably check the number of arguments it’s given against the number of columns in the finder name.You already know that Pivotal does real Extreme Programming. But did you know that we have…Extreme Breakfast?

Regarding Jasmine and stubbing constructors, you can do this:
Since the
Widgetctor is just a function,newcalls the function and gets your fake widget, and then it does its extra magic with the prototype and such on your fake widget. Should work for most things you’d want to do, I believe.March 12, 2010 at 8:21 pm
The problem is that constructors don’t return their new objects; they just do stuff to them. So even if I do that, `new Widget()` won’t return `fakeWidget`, it’ll return `{}` (since we’ve stubbed out the constructor implementation, and so no properties are added to the new object).
Just saying `Widget()` *will* return `fakeWidget`, but that’s not how `Widget` is used.
March 13, 2010 at 7:54 am
Aaah, I see the problem you’re having with stubbing the constructor. It’s not that the function is stubbed, it’s that the function’s prototype is not on the stub. Calling new will return the object in the andReturn parameter, but it won’t attach the prototype (or, really, it attaches the prototype associated with the stub to the new object).
Hm. What happens if you do this:
March 13, 2010 at 10:41 am
That was the workaround we went with for the prototype problem that doesn’t solve the fact that `andReturn` doesn’t work on constructors…
…which turns out to be a complete lie. I really thought we saw that fail, but an isolated test case shows that it works.
Which is curious to me: how is that possible? My understanding is that `spyOn` replaces the spied function with a spy. That spy can be told what to return with `.andReturn`. But the return value of a constructor is discarded! Saying `new Foo()` *always* creates and returns a new object, doesn’t it? So how does this pass?
var namespace = {};
namespace.Constructor = function() {
this.wasMadeWithRealConstructor = true;
};
var myFakeObject = { wasMadeWithRealConstructor: false };
spyOn(namespace, “Constructor”).andReturn(myFakeObject);
expect(new namespace.Constructor()).toEqual(myFakeObject);
March 13, 2010 at 11:25 am
OK, who let their pair stick a knife in the toaster? Bad pair…
March 13, 2010 at 11:22 pm
fwiw, I believe the following will generally get you the behavior you need, but it requires a fixed set of arguments. We’ll look at supporting functions with properties better in jasmine; hopefully in the future this sort of thing will be transparent.
>>> function Foo(myVal) { console.log("Passed '" + myVal + "' to Foo Constructor"); this.bar = myVal; } >>> Foo.prototype.quux = function() { console.log("Called quux"); return this.bar; } function() >>> FooClone = Foo >>> spyOn(window, "Foo").andCallFake(function(myVal) { console.log("passed '"+ myVal + "' to Fake"); return new FooClone(myVal); }); function() >>> var f = new Foo("test"); passed 'test' to Fake Passed 'test' to Foo Constructor >>> f.quux(); Called quux "test" >>> expect(window.Foo).wasCalledWith("test") trueMarch 14, 2010 at 1:27 pm