How to Test Asynchronous Code with QUnit

Share this article

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.

Frequently Asked Questions (FAQs) about Testing Asynchronous Code with QUnit

What is asynchronous code and why is it important to test it?

Asynchronous code refers to operations that can start, run, and complete in overlapping time periods. It’s a way of programming that allows multiple things to happen at the same time. This is particularly useful in applications that require heavy I/O operations or need to perform tasks like fetching data from a server. Testing asynchronous code is crucial because it helps ensure that all parts of your code execute correctly, even when they’re running concurrently. It helps identify any potential issues that could arise from the overlapping execution of different parts of your code.

How does QUnit help in testing asynchronous code?

QUnit is a powerful, easy-to-use JavaScript unit testing framework. It’s capable of testing any generic JavaScript code, including asynchronous code. QUnit provides an ‘async’ utility function that makes it easy to test asynchronous code. This function pauses the test runner and resumes it when your asynchronous code has finished executing. This ensures that your tests accurately reflect the behavior of your asynchronous code.

How do I use the ‘async’ function in QUnit to test asynchronous code?

The ‘async’ function in QUnit is used by calling it within your test function. This returns a callback function that you should call when your asynchronous operation has completed. Here’s a basic example:

QUnit.test("async test", function(assert) {
var done = assert.async();
setTimeout(function() {
assert.ok(true);
done();
}, 100);
});
In this example, the ‘async’ function is used to pause the test runner until the ‘done’ callback is called.

Can I test multiple asynchronous operations with QUnit?

Yes, QUnit allows you to test multiple asynchronous operations within a single test. You can do this by calling the ‘async’ function multiple times and storing each returned callback in a separate variable. Each callback should be called when its corresponding asynchronous operation has completed.

How do I handle errors in asynchronous code with QUnit?

QUnit provides a ‘throws’ assertion that you can use to test if a function throws an exception. This can be used to test error handling in your asynchronous code. If your asynchronous code uses Promises, you can also use the ‘rejects’ assertion to test if a Promise is rejected.

Can I use QUnit to test asynchronous code that uses Promises?

Yes, QUnit provides a ‘resolve’ assertion that you can use to test if a Promise is resolved with a specific value. You can also use the ‘rejects’ assertion to test if a Promise is rejected.

How do I cancel an asynchronous operation in QUnit?

QUnit itself does not provide a way to cancel asynchronous operations. However, you can implement cancellation in your own code and test this behavior with QUnit. For example, if your asynchronous code uses Promises, you can create a cancellation token and pass it to your asynchronous function. The function can then check this token at various points and stop its execution if the token indicates that the operation should be cancelled.

Can I use QUnit to test asynchronous code in Node.js?

Yes, QUnit can be used to test asynchronous code in Node.js. You can use the ‘async’ function in the same way as in a browser environment. However, you may need to use a library like ‘jsdom’ to provide a browser-like environment for your tests.

How do I debug asynchronous code with QUnit?

Debugging asynchronous code with QUnit can be challenging due to the nature of asynchronous operations. However, you can use techniques like logging and breakpoints to help debug your code. QUnit also provides a ‘dump’ function that you can use to output the state of an object, which can be useful for debugging.

Can I use QUnit with other testing frameworks?

Yes, QUnit can be used alongside other testing frameworks. However, it’s important to ensure that your tests are isolated and do not interfere with each other. You should also ensure that each test framework is configured correctly to avoid any conflicts.

Aurelio De RosaAurelio De Rosa
View Author

I'm a (full-stack) web and app developer with more than 5 years' experience programming for the web using HTML, CSS, Sass, JavaScript, and PHP. I'm an expert of JavaScript and HTML5 APIs but my interests include web security, accessibility, performance, and SEO. I'm also a regular writer for several networks, speaker, and author of the books jQuery in Action, third edition and Instant jQuery Selectors.

asynchronousColinIQUnitTestingunit testing
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week