Sometimes when you’re writing Javascript unit tests you have to give Javascript a chance to catch up before checking your assertions. Javascript uses a single event loop, so it isn’t multi-threaded in the same way as other languages. You can still get a “multi-threaded feel” by using callbacks (promises, observables, etc), but at the end of the day, Javascript processes things on a single thread.
Recently I was trying to write a test to check reconnect logic. If a disconnect is detected, I want to keep trying to reconnect until the connection is established. I accomplished this by using setTimeout and recursively calling our reconnect function. Something like this:
Testing
When it came to testing, I wanted to test to make sure that reconnect gets called again if connect returns false.
As explained in a previous blog post, jasmine.clock() can be used to manipulate time (so we don’t have to wait for that 500ms to elapse before testing the code inside of our setTimeout).
One of the nice things about Javascript testing is you can mock whatever you want fairly easily, including methods on the service you’re testing itself, like so:
However, this doesn’t work. The test fails, saying that reconnect was only called once (instead of the expected twice).
The weird part is I put a console.log on the top of my reconnect method and it shows up the expected number of times (twice). As far as I can tell, the event loop had not quite properly processed the fact that the method was called a second time. I’m not sure, but I’m assuming it has something to do with connect being asynchronous.
Solution … almost?
The fix is to wrap the assertion part of the test in a setTimeout to give the event loop the chance to process the rest of the previous callback before checking our assertions. You can accomplish this by providing a parameter to your method (called done in my example) and invoking it to let Jasmine know that the asynchronous function has finished:
However, this still fails. This time, the problem is with jasmine.clock(). When you install() the clock, it overwrites the behavior of setTimeout, including the version we’re trying to use in our test…
Solution for reals
The for reals fix is to uninstall() the clock before the setTimeout in our test. Its kind of cumbersome, but it does make sense when you understand how all of the bits work, and it does successfully test our code: