I have an angular service that does some async stuff (based on timers). One of the things you can do with a timer is define a 'handler' that fires when the timer expires (as in this pseudo-code):
flag = false;
timer = new Timer(1000); // ms
timer.handler = function () { flag = true };
In this trivial case, the timer would set flag to true after 1 second. How do I unit test this with Angular/Karma/Jasmine?
From reading the docs, I would have expected this to work:
...
flag = false;
timer = new Timer(1000);
timer.handler = function () { flag = true };
expect(flag).toBe(false);
sleep(2)
expect(flag).toBe(true);
...
Rather than being morally upright, that test decided to fail with this:
ReferenceError: Can't find variable: sleep
After some reading, apparently I can't use angular-scenario with Jasmine. Ok, I'm cool with that.
UPDATE : Per the comments, I tested my "working" settimeout method. It doesn't ever get called.
So this works:
...
flag = false;
timer = new Timer(1000);
timer.handler = function () { flag = true };
expect(flag).toBe(false);
setTimeout(function () { expect(flag).toBe(true) }, 2000);
...
But feels a little weird.
Question: Is there a better way?
Fun Trivia: Yep, I know about $timeout. I have Very Good Reasons(TM) for doing the things I did deep in the code mines, away from the light of day =)
We wrap our test spec function in another function called async . 2. We place the tests we need to run after the isAuthenticated promise resolves inside this function. This async function executes the code inside its body in a special async test zone. This intercepts and keeps track of all promises created in its body.
If an operation is asynchronous just because it relies on setTimeout or other time-based behavior, a good way to test it is to use Jasmine's mock clock to make it run synchronously. This type of test can be easier to write and will run faster than an asynchronous test that actually waits for time to pass.
Jasmine provides two strategies for testing asynchronous operations: Waiting for certain events to occur until a timeout.
tl;dr. In almost all cases, they can be used interchangeably, but using fakeAsync()/tick() combo is preferred unless you need to make an XHR call, in which case you MUST use async()/whenStable() combo, as fakeAsync() does not support XHR calls. For the most part they can be used interchangeably.
Jasmine has a way to do async testing using waits() or waitsFor() and runs(). Look here.
Code would be something like:
...
flag = false;
timer = new Timer(1000);
timer.handler = function () { flag = true };
expect(flag).toBe(false);
waitsFor( function() {
return flag;
}, "timer ran");
runs( function() {
expect(flag).toBe(true);
});
...
Note from OP
This is the right solution, so I marked it as accepted. I actually ended up implementing a sleep-like method based on this solution, and wanted to share in case it was helpful to others.
In the test file:
function loiter(ms) {
var loiter = true;
setTimeout(function () {loiter = false}, ms);
waitsFor( function () {return !loiter}, "Loitered too long", ms + 50);
}
it("should ...", function () {
flag = false;
timer = new Timer(1000);
timer.handler = function () {flag = true};
setTimeout(function () {expect(flag).toBe(true)}), 1100);
loiter(1200);
})
I hope this is useful! I'll leave it as an exercise for the reader to figure out why I did it this way =)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With