How to Test Asynchronous Code with QUnit

Aurelio De Rosa
Share

A few weeks ago, I published an article titled Getting Started with QUnit where I discussed the main concepts of unit testing and how we can test our JavaScript code with QUnit. In that article, I focused on the assertions provided by the framework and how to test code that runs synchronously. However, if we want to discuss real world cases we can’t avoid talking about asynchronous functions.

Just like synchronous functions, asynchronous ones need love, and even more tests. In this article I’ll teach you how to test asynchronous code with QUnit. In case you don’t recall the assertion methods available, or you totally missed my article, I suggest you read Getting Started with QUnit. The material covered in it will be a prerequisite to this article.

Creating Asynchronous Tests with QUnit

Every non-trivial project that is written in JavaScript contains asynchronous functions. They are used to perform a given action after a certain amount of time, to retrieve data from a server, or event to send data to a server. QUnit provides a method, called QUnit.asyncTest(), whose purpose is to test asynchronous code. The signature of the method is:

QUnit.asyncTest(name, testFunction)

The meaning of the parameters is the same as QUnit.test(), but I’m reporting them here for your convenience:

  • name: A string that helps us identify the test created.
  • testFunction: The function containing the assertions that the framework will execute. The framework passes to this function an argument that exposes all of QUnit’s assertion methods.

The fact that this method accepts the same parameters as QUnit.test() might be misleading. You might think that the principle is the same and that all you have to do to test an asynchronous function is to replace the calls to QUnit.test() with QUnit.asyncTest() and you’re done. Not so fast!

In order to do its job, QUnit.asyncTest() needs to be used with two other methods: QUnit.start() and QUnit.stop(). Let’s discover more about them.

QUnit.start() and QUnit.stop()

When QUnit executes a test created using QUnit.asyncTest(), it’ll automatically stop the testrunner. Then, it’ll wait until the function containing the assertions invokes QUnit.start(). The aim of QUnit.start() is to start or resume a running test after it was stopped. This method accepts an integer as its only optional argument to merge multiple QUnit.start() calls into one.

A test can be stopped using the method QUnit.stop(). It increases the number of QUnit.start() calls the testrunner has to wait before continuing. This method accepts an integer as its only optional argument that specifies the number of additional calls to QUnit.start() that the framework has to wait. Its default value is 1.

A bit hard to understand, isn’t it? A definition of a method that involves its counterpart sounds like a complete mess. Unfortunately, this is exactly what they do. The best way I know to clarify these concepts is to give you a concrete example of use.

Putting It All Together

In this section we’ll put into action the methods discussed so far. Hopefully once you read it you’ll have a in-depth understanding of this mechanism.

Let’s start with a simple example that uses one of the functions developed in the article Getting Started with QUnit: max(). This function accepts an arbitrary number of parameters and returns the maximum. The code for the function is reported below:

function max() {
   var max = -Infinity;
   for (var i = 0; i < arguments.length; i++) {
      if (arguments[i] > max) {
         max = arguments[i];
      }
   }

   return max;
}

Now, imagine that this function will usually work on a very large set of parameters. We want to avoid our users’ browsers from being blocked until the result is computed. For this reason we’ll call max() inside a callback passed to window.setTimeout() with a delay value of 0.

The code to test the function asynchronously, that should give you a feeling of the use of QUnit.start(), is shown below:

QUnit.asyncTest('max', function (assert) {
   expect(1);

   window.setTimeout(function() {
      assert.strictEqual(max(3, 1, 2), 3, 'All positive numbers');
      QUnit.start();
   }, 0); 
});

In the code above, I’ve wrapped the call to the max() function as a callback of window.setTimeout(). After the assertion using max() has been executed, we invoke the QUnit.start() method to allow the testrunner to resume its execution. If we avoided the call to this method, the testrunner would be stuck and our test would fail miserably (actually the test pauses and does nothing else, so it’s not a real assertion fail).

The previous example should have been easy to understand because it’s very similar to its synchronous counterpart. But, testing for just one case doesn’t allow us to put trust in our code. In addition, we haven’t had the change to see QUnit.stop() in action. To fix that, we’ll implement all the assertions we saw in the previous article inside the function passed to QUnit.asyncTest().

The full code is reported below:

QUnit.asyncTest('max', function (assert) {
   expect(4);
   QUnit.stop(3);

   window.setTimeout(function() {
      assert.strictEqual(max(), -Infinity, 'No parameters');
      QUnit.start();
   }, 0);

   window.setTimeout(function() {
      assert.strictEqual(max(3, 1, 2), 3, 'All positive numbers');
      QUnit.start();
   }, 0);

   window.setTimeout(function() {
      assert.strictEqual(max(-10, 5, 3, 99), 99, 'Positive and negative numbers');
      QUnit.start();
   }, 0);

   window.setTimeout(function() {
      assert.strictEqual(max(-14, -22, -5), -5, 'All positive numbers');
      QUnit.start();
   }, 0);   
});

Inside the test, we set the number of asserts we expect to run as we discussed in Getting Started with QUnit. Then, the function invokes the QUnit.stop() method. This is necessary because inside the test we perform four asynchronous calls. When we employ QUnit.asyncTest(), the framework only waits for one call to QUnit.start(). If we omit the call to QUnit.stop() specifying the additional three calls to QUnit.start(), the test will fail because the number of assertions expected is different from the number of asserts executed.

A live demo of the code, including the call to expect(), is shown below and available as a JSBin.

Asynchronous tests with QUnit

In this section, we’ve seen examples of asynchronous code that doesn’t perform Ajax operations. However, you often want to load data from or send data to a server. When this happens it’s better not to rely on the actual data or result returned by the server because it may have bugs (you know, nothing is perfect in software). To avoid this issue, you should mock the Ajax requests. To do so, you can employ jQuery Mockjax, Sinon.js, or any other library that fits your needs.

Conclusion

In this tutorial you’ve discovered how to create tests for your asynchronous functions. First, we discussed how to declare a test that involves asynchronous code using the method QUnit.asyncTest(). Then, you learned of the existence of two other methods, QUnit.start() and QUnit.stop(), that should be used when creating a test with QUnit.asyncTest(). Finally, we put the knowledge acquired into action by developing two tests to show how these methods work together.

With the topics covered in this tutorial you have all the power you need to test any code you might write with JavaScript. I’m eager to know your opinion about this framework and whether you’ll consider using it in your projects.