Thumbs up for testing

Introduction

If you’ve been working with a lot of JavaScript or another dynamic language, you probably have come to the conclusion that you should have automated tests for your code. If you haven’t come to the realization yet, I suspect you’d like to make your life easier. Wouldn’t it be nice to know that your latest round of changes didn’t break your application without you having to manually go through every page in your application looking for issues? In this post, we’ll get started writing tests for our JavaScript code. For testing we’ll use the QUnit library. Why QUnit? First, it’s the oldest of the JavaScript testing frameworks. Also, Ember.js relies on it and in future posts I’d like to go into testing Ember code. Having this QUnit primer will help you test any JavaScript code, not just Ember code. Let’s get started!

Test Driven Development (TDD)

The basic idea behind Test Driven Development consists of writing a failing test (red), then writing code to “fix” the test (green), finally refactoring. Let’s look at a quick example. Say our requirements are to write a function that adds up two numbers, we’ll start with a unit test to check that the numbers add up correctly. Since the implementation hasn’t yet been written, we expect that the running the test now will fail (red). That’s expected. Then we’ll write the actual function to add up two numbers, run the test again, and if all went correctly, it will pass (green). Now look at the function you wrote and see if it could be simplified or written in a more elegant manor. That’s the refactoring phase. We can refactor at this point because we have a verified, automated passing test we can check our changes against. Of course this is a very simplified example, but hopefully you get the point.

Personally I like using the TDD approach when coding. Having a failing test first assures that you are writing your code to “spec.” If you do this for all your methods, you can really help yourself in the future when making changes. Tests don’t guarantee error free code, but they definitely help and give you an instant gauge of where your code stands.

Getting Started with QUnit

Getting started with QUnit is easy, simply make an HTML page with the following content:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-1.17.1.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="https://code.jquery.com/qunit/qunit-1.17.1.js"></script>
  <script src="app.js"></script>
  <script src="tests.js"></script>
</body>
</html>

You can find this code on the QUnit Homepage with the exception of the app.js reference (this is where our code will go).

Create this HTML file (name it index.html for simplicity) and two JavaScript files, app.js and tests.js. Now run the page. You can open it up from the file directly or if you have Python 3 installed, you can use the following to spin up a simple server: python -m http.server. Browse to http://127.0.0.1:8000 and you should see the following (Figure 01).

Figure 01: QUnit Template
Figure 01: QUnit Template

Have Python 2.x instead? Use python -m SimpleHTTPServer instead.

Hello World

Now that we have our infrastructure in place, let’s start with a “Hello World” testing example. We’ll write a simple test within tests.js that checks that 1 equals the string “1”.

QUnit.test('hello world', function(assert) {
  assert.ok(1 == '1', "1 equals '1'")
});

Here we’re using QUnit’s ok method which checks for true (which passes) or false (which fails). The second argument here is just a comment that will be displayed in the output. Now running the test we see Figure 02. The numbered sections are as follows:

  1. Our hello world test
  2. We have 1 assertion, with 1 passed and 0 failed
  3. Finally, you can see we are all green, indicating success
Figure 02: Passing
Figure 02: Passing

Now let’s look at a failure. Change the == to the strict equals JavaScript operator ===.

QUnit.test('hello world', function(assert) {
  assert.ok(1 === '1', "1 equals '1'")
});

Boom, failure (Figure 03).

Figure 03: Failure
Figure 03: Failure

Now that you know what success and failures look like, let’s work on test-driving a real-world JavaScript function.

Real World Example

In this section, we’ll TDD a function that tests if an object equals another object by checking it’s properties. Both equal operators (== and ===) won’t check objects by properties, it simply checks references. What we’ll do is add an equals function to the JavaScript Object (by using the prototype), so we can simply call obj1.equals(obj2). Let’s get started with our first test.

First, we’ll make sure there is a function called equals on an object.

QUnit.test('object should have an equals function', function(assert) {
  var obj = { id: 1, name: 'Damien' }
  assert.ok(obj.equals)
});

Run the test, and we get a failure: failed, expected argument to be truthy, was: undefined. Now let’s add the code to “fix” the test. Within app.js we’ll define our function.

Object.prototype.equals = function(b) {}

Refresh your tests, and you should be all green again.

When doing TDD we want to test the simplest cases first then work to harder ones, so let’s make sure equals does a simple check to verify that an object is the same (using ===). Here’s the test:

QUnit.test('an object should equal itself', function(assert) {
  var obj1 = { id: 1, name: 'Damien' }
  assert.ok(obj1.equals(obj1))
});

And the code (building out our function in app.js:

Object.prototype.equals = function(b) {
  var a = this
  if (a === b) return true;
}

I’ve renamed this to a so that we can compare a to b, seems nicer than if (this === b) return true;, don’t you agree? Let’s keep going. What’s the next simplest case? Two objects that aren’t the same. We expect equals to return false.

QUnit.test('equals should return false if the objects don't match', function(assert) {
  var obj1 = { id: 1, name: 'Damien' }
  var obj2 = {name: 'Damien' }
  assert.equals(obj1.equals(obj2), false)
});

Right now if you run the code, it returns undefined instead of false. Let’s correct that.

Object.prototype.equals = function(b) {
  var a = this
  if (a === b) return true;
  return false;
}

Now we’re passing again. What should we test now? I think the next step should make sure that simple properties (not objects or functions) are the same amongst both objects.

QUnit.test('an object should equal another object with the same "simple" properties', function(assert) {
  var obj1 = { id: 1, name: 'Damien' }
  var obj2 = { id: 1, name: 'Damien' }
  assert.ok(obj1.equals(obj2))
});

And the code to make our assertion pass.

Object.prototype.equals = function(b) {
  var a = this
  if (a === b) return true;
  for (p in a) {
    // skip testing functions since they can't be serialized
    if (typeof(a[p]) === 'function') continue;

    if (a[p] !== b[p]) return false;
  }
  return true;
}

I skipped testing functions because we’re messing with the Object prototype (which is sort of evil, but we’re ignoring that in this post), it messes with the codefor (p in a). One of the “properties” ends up being our equals function that we’re creating (and why Object.prototype.function = {} is considered evil). In our case, functions aren’t able to be serialized, so we’ll just simply skip all functions. You of course, can always add that functionality in. This post is about testing, not so much about the code we are creating. Finally, I changed the last line to return true since we need to assume that an object is valid if we get to the end of the function. Now all the tests are passing. Let’s keep going shall we?

Finally, we’ll handle nested objects.

QUnit.test('an object should equal another object with the nested objects', function(assert) {
  var obj1 = { id: 1, name: 'Damien', address: { city: 'Bristol', state: 'CT'} }
  var obj2 = { id: 1, name: 'Damien', address: { city: 'Bristol', state: 'CT'} }
  assert.ok(obj1.equals(obj2))
});

And now the final code, simple recursion.

Object.prototype.equals = function(b) {
  var a = this
  if (a === b) return true;
  for (p in a) {
    // skip testing functions since they can't be serialized
    if (typeof(a[p]) === 'function') continue;
    if (typeof(a[p]) === 'object') return a[p].equals(b[p]);
    if (a[p] !== b[p]) return false;
  }
  return true;
}

Conclusion

Hopefully this gave you a look into testing your JavaScript code. Pretty easy, right? In future posts I’ll write about more testing and using it with Ember.js. I would recommend that you review the API documentation on QUnit, especially the assertions, as I only used two of them, ok and equal. Feel free to take the code here and keep improving upon it.