Reputation: 4742
In the process of learning Jasmine, I've come to this issue. I want a basic function to run, then set a timeout to call itself again... simple stuff.
class @LoopObj
constructor: ->
loop: (interval) ->
#do some stuff
setTimeout((=>@loop(interval)), interval)
But I want to test to make sure the setTimeout was called with the proper args
describe "loop", ->
xit "does nifty things", ->
it "loops at a given interval", ->
my_nifty_loop = new LoopObj
interval = 10
spyOn(window, "setTimeout")
my_nifty_loop.loop(interval)
expect(setTimeout).toHaveBeenCalledWith((-> my_nifty_loop.loop(interval)), interval)
I get this error: Expected spy setTimeout to have been called with [ Function, 10 ] but was called with [ [ Function, 10 ] ]
Is this because the (-> my_nifty_loop.loop(interval))
function does not equal the (=>@loop(interval))
function? Or does it have something to do with the extra square brackets around the second [ [ Function, 10 ] ]
? Something else altogther?
Where have I gone wrong?
Upvotes: 4
Views: 588
Reputation: 15276
Use a bind
helper function.
The reason you're having problems comparing the argument of setTimeout is because it's hidden in a lambda. Every lambda you create is different. In this case, the lambda actually adds no value besides binding the function to the proper value of this
.
It turns out that this is a common pattern in JavaScript, and the Underscore.js library has a function called bind
which does exactly that.
By factoring out this pattern and composing it with setTimeout
(which is the same as _.delay
), we can compare the arguments passed to it since they won't be wrapped in a lambda.
window.bindAndDelay = (wait, fn, obj, args...) ->
setTimeout((-> obj.fn(args...)), wait)
class @LoopObj
constructor: ->
loop: (interval) ->
#do some stuff
bindAndDelay(interval, @loop, @, interval)
Then in your test:
describe "loop", ->
xit "does nifty things", ->
it "loops at a given interval", ->
my_nifty_loop = new LoopObj
interval = 10
spyOn(window, "bindAndDelay")
my_nifty_loop.loop(interval)
expect(bindAndDelay).toHaveBeenCalledWith(interval, my_nifty_loop.loop, my_nifty_loop, interval)
I chose to put the delay amount as the first argument of bindAndDelay
so that it works with functions with an arbitrary number of arguments applied.
An alternative is to use _.bindAll
, but you have to remember to use it on every object, and it's not conducive to functions with parameters. So I think the above is better.
This should work, but I still think it stinks. So if anyone else has a better answer, please post!
Upvotes: 1
Reputation: 1196
I don't know CoffeeScript too well, but you could debug by replacing
expect(setTimeout).toHaveBeenCalledWith((-> my_nifty_loop.loop(interval)), interval)
with
expect(setTimeout).toHaveBeenCalledWith(jasmine.any(Function), interval)
and re-running the spec. I would think if the extra square brackets go away, then your issue is because you have two different function references. If they don't go away, there's some weirdness with your LoopObj definition, possibly the fat arrow operator (which looks unnecessary to my n00b eyes).
Upvotes: 1