My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
AsyncTestCase  
Demonstrates usage of the asynchronous testing API.
Updated Jan 14, 2011 by robertsd...@gmail.com

A traditional JsTestDriver test case looks like this:

var MyTest = TestCase('MyTest');

MyTest.prototype.testSomething = function() {
  // make some assertions, etc.
};

We've extended the test cases to allow their test methods to accept a parameter. The parameter is a queue that accepts inline functions that represent sequential steps of the test. The test runner executes these steps in sequence, ending the test if at any point an assertion fails.

Here's an example that uses the queue but does not use any asynchronous operations.

var QueueTest = AsyncTestCase('QueueTest');

QueueTest.prototype.testSomething = function(queue) {
  var state = 0;

  queue.call('Step 1: assert the starting condition holds', function() {
    assertEquals(0, state);
  });

  queue.call('Step 2: increment our variable', function() {
    ++state;
  });

  queue.call('Step 3: assert the variable\'s value changed', function() {
    assertEquals(1, state);
  });
};

Note we are using AsyncTestCase() now instead of TestCase(). Also, the queue has a method call() that accepts an optional string to identify the step of the test, and an operation. You could also omit the strings and just call queue.call(function() {}). The functions passed to the queue's call() method usually have more than one line, but the above example is minimal.

Finally, in order to support testing asynchronous operations, as the test runner executes each step in the queue it passes a parameter to that step. The parameter is an empty pool of callback functions. You add your callback functions to the pool so the test runner can track that they are outstanding. The pool wraps your callback functions in a way that preserves their behavior but that notifies the test runner when an asynchronous system calls them. You call var myTweakedCallback = callbacks.add(myOriginalCallback) which returns the tweaked version. The test runner will not execute any subsequent step in the queue until all outstanding callbacks of the current step are complete. If the callbacks are not a called for an egregious amount of time, currently set to 30 seconds, the test fails.

Here's an example:

var AsynchronousTest = AsyncTestCase('AsynchronousTest');

AsynchronousTest.prototype.testSomethingComplicated = function(queue) {
  var state = 0;

  queue.call('Step 1: schedule the window to increment our variable 5 seconds from now.', function(callbacks) {
    var myCallback = callbacks.add(function() {
      ++state;
    });
    window.setTimeout(myCallback, 5000);
  });

  queue.call('Step 2: then assert our state variable changed', function() {
    assertEquals(1, state);
  });
};

Here's a more realistic example that communicates with a hypothetical HTTP server:

var XhrTest = AsyncTestCase('XhrTest');

XhrTest.prototype.testRequest = function(queue) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', '/some/path');

  var responseBody;
  
  queue.call('Step 1: send a request to the server and save the response body', function(callbacks) {
    var onStatusReceived = callbacks.add(function(status) {
      assertEquals(200, status);
    });
    
    var onBodyReceived = callbacks.add(function(body) {
      responseBody = body;
    });

    xhr.onreadystatechange = function() {
      if (xhr.readyState == 2) { // headers and status received
        onStatusReceived(xhr.status);
      } else if (xhr.readyState == 4) { // full body received
        onBodyReceived(xhr.responseText);
      }
    };

    xhr.send(null);
  });

  queue.call('Step 2: assert the response body matches what we expect', function() {
    assertEquals('hello', responseBody);
  });
};

Advanced CallbackPool Features

Noop Callbacks

Sometimes, you may want to do your assertions in the next test step, rather than within the callback you pass to the asynchronous system. CallbackPool has a method called noop() that returns a noop function that blocks the current step until it is called.

Example:

var NoopTest = AsyncTestCase('NoopTest');

NoopTest.prototype.testNoop = function(queue) {
  var asynchronousSystem = ...;
  assertFalse(asynchronousSystem.wasTriggered());
  queue.call('Trigger the system', function(callbacks) {
    asynchronousSystem.triggerLater(callbacks.noop());
  });
  queue.call('Assert about the system', function() {
    assertTrue(asynchronousSystem.wasTriggered());
  });
};

Errbacks (Unexpected Callbacks)

Sometimes, you may want to fail the test immediately if your asynchronous system calls one callback function instead of another. For instance, imagine you pass two callback functions to your asynchronous system, one for a successful outcome, and another for an unsuccessful outcome. When one callback is executed, the other will never execute. They are mutually exclusive.

CallbackPool has a method called addErrback() precisely for this situation. Use addErrback() to return a function() that will fail the test if your asynchronous system calls it. AddErrback() accepts a string to identify which errback the asynchronous system called so you may easily identify it from the test failure message.

Example:

var ErrbackTest = AsyncTestCase('ErrbackTest');

ErrbackTest.prototype.testErrback = function(queue) {
  var asynchronousSystem = ...;
  assertFalse(asynchronousSystem.wasTriggered());
  queue.call('Trigger the system', function(callbacks) {
    asynchronousSystem.triggerLater(
        callbacks.noop(),
        callbacks.addErrback('Failed to trigger'));
  });
  queue.call('Assert about the system', function() {
    assertTrue(asynchronousSystem.wasTriggered());
  });
};

Expect Multiple Invocations

Sometimes, your asynchronous system needs to call a single callback multiple times within one step of your test. Both add() and noop() accept an optional count argument that specifies how many times you expect your asynchronous system to call its callback.

Example:

var MultipleTest = AsyncTestCase('MultipleTest');

MultipleTest.prototype.testMultipleInvocations = function(queue) {
  queue.call('Expect three invocations', function(callbacks) {
    var count = 0;
    var intervalHandle;
    var callback = callbacks.add(function() {
      ++count;
      if (count >= 3) {
        window.clearInterval(intervalHandle);
      }
    }, 3); // expect callback to be called no less than 3 times
    intervalHandle = window.setInterval(callback, 1000);
  });
};
Comment by olegelif...@gmail.com, Sep 28, 2010

Is where any downloadable compiled binary which supports asynchronous testing?

Comment by project member robertsd...@gmail.com, Oct 4, 2010

Version 1.2.2 has the asynchronous test API, but it was released back in May 2010. We've fixed several critical bugs since then. I'll let Cory know we should release an updated version.

Comment by olegelif...@gmail.com, Oct 15, 2010

What version of testing API are in IDEA Plugin? Does version 1.2.2a of plugin contain asyncronous api?

Comment by crys...@bleext.com, Nov 19, 2010

Hi there! I really like this project, it looks really awesome!

I'm having some issues with the "AsyncTestCase", when I try to run one of the above examples i get an error telling me that the "defer" method is not a function! I've inspected the "queue" object and is true, there's not a "defer" method! I'm using the JsTestDriver?-1.2.2.jar

Any suggestions?

Best regards

Comment by eric.eh9@gmail.com, Dec 7, 2010

As of this writing, the current released version on the downloads page is 1.2.2. The "defer" method is defined in DeferredQueue?.js, created 2010 Jun 17, which is after the release date of 1.2.2, 2010 Jun 05 (according to the download page). In other words, this documentation is for an unreleased version, available only if you're building from svn trunk.

From what I can tell, the actual interface in 1.2.2 is to pass a 'herd' object (the old name for pool) instead of a queue, as shown above. This should suffice for a single asynchronous call. It'll mean rewriting all your async tests when the new version comes out. As for me, I think that's less work than dealing with another build environment.

Comment by eric.eh9@gmail.com, Dec 13, 2010

Another piece of the 1.2.2 documentation should be that you can have exactly one AsyncTestCase object, at least insofar as I've been able to get it to work; all others after the first fail. The code that fails has been modified in the trunk.

Comment by rdio...@google.com, Jan 14, 2011

I've created tag 1.2.3 which includes the 1.2.2 server-side implementation, augmented with the latest AsyncTestCase and ConditionalTestCase? functionality from trunk/HEAD. Multiple AsyncTestCases? should now be supported, and you should receive the DeferredQueue? as the first argument to your test method, rather than the CallbackPool?.

Comment by siunmaru, Jan 17, 2011

FYI anyone having issues with these examples -  Issue 171 : AsyncTestCase not working in 1.2.2, based on example in Wiki (http://code.google.com/p/js-test-driver/issues/detail?id=171)

Comment by abre...@gmail.com, Jan 28, 2011

In the Expect Multiple Invocations section, the text says "Both add() and noop() accept an optional count argument", even though in the example below count is just a variable within the scope of the callback, not an argument.

Comment by lemieuxs...@gmail.com, Jan 31, 2011

We have noticed that to get AysnTestCase? to work with the IntelliJ plugin (1.2.2b) using, presumably, the latest version of JSTestDriver that to get the case to run as expected you have to do the following.

TestName.prototype.testToRun = function(pool) {
   expectAsserts(1);
   var tF = pool.add(function() {
                      assertTrue(true); //or your more realistic assert
                  });

   window.setTimeout(tF, 500); //or your async call of some kind
};

Not sure if this is due to the version in the plugin or if the documentation is out of date.

Comment by sachs.paul, Jul 8, 2011

Is there a way to customize the "egregious" timeout period when waiting for callbacks with configuration change? I was hoping for something more like 10 seconds.

Comment by hamme...@gmail.com, Jul 13, 2011

Is there any plan to update for supporting QUnitAdapter? It would be great. :)

Comment by philb...@gmail.com, Sep 23, 2011

I'm running v1.3.2 of js test driver, and the documentation still doesn't correspond to what happens in this version. The object passed to an async test case has add, count, maybeComplete, onError, onHerdComplete, remove and setTimeout methods. Simply using the add method seems to add methods to be tested, but there's nothing obvious to actually run these once they've all been added.

Comment by machineg...@gmail.com, Dec 3, 2011

Just FYI, when using AsyncTestCase with window.setTimeout, be sure to set a decent (1000 worked for me) timeout length. If you try to use a timeout of 1 (as I often do), you may see strange behaviour.


Sign in to add a comment
Powered by Google Project Hosting