Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular + Karma: Testing async functions

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 =)

like image 469
Sir Robert Avatar asked Jan 15 '14 19:01

Sir Robert


People also ask

What is async in Angular testing?

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.

How do you test async on Jasmine?

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.

Does Jasmine support asynchronous operations?

Jasmine provides two strategies for testing asynchronous operations: Waiting for certain events to occur until a timeout.

What is the difference between the async and fakeAsync utilities in Angular?

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.


1 Answers

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 =)

like image 102
Alan Yackel Avatar answered Nov 15 '22 21:11

Alan Yackel