You are previewing Node: Up and Running.

Node: Up and Running

Cover of Node: Up and Running by Tom Hughes-Croucher... Published by O'Reilly Media, Inc.
O'Reilly logo

Testing Through assert

assert is a core library that provides the basis for testing code. Node’s assertions works pretty much like the same feature in other languages and environments: they allow you to make claims about objects and function calls and send out messages when the assertions are violated. These methods are really easy to get started with and provide a great way to unit test your code’s features. Node’s own tests are written with assert.

Most assert methods come in pairs: one method providing the positive test and the other providing the negative one. For instance, Example 5-37 shows equal() and notEqual(). The methods take two arguments: the first is the expected value, and the second is the actual value.

Example 5-37. Basic assertions

> var assert = require('assert');
> assert.equal(1, true, 'Truthy');
> assert.notEqual(1, true, 'Truthy');
AssertionError: Truthy
    at [object Context]:1:8
    at Interface.<anonymous> (repl.js:171:22)
    at Interface.emit (events.js:64:17)
    at Interface._onLine (readline.js:153:10)
    at Interface._line (readline.js:408:8)
    at Interface._ttyWrite (readline.js:585:14)
    at ReadStream.<anonymous> (readline.js:73:12)
    at ReadStream.emit (events.js:81:20)
    at ReadStream._emitKey (tty_posix.js:307:10)
    at ReadStream.onData (tty_posix.js:70:12)
>

The most obvious thing here is that when an assert method doesn’t pass, it throws an exception. This is a fundamental principle in the test suites. When a test suite runs, it should just run, without throwing an exception. If that is the case, the test is successful.

There are just a few assertions. equal() and notEqual() check for the == equality and != inequality operators. This means they test weakly for truthy and falsy values, as Crockford termed them. In brief, when tested as a Boolean, falsy values consist of false, 0, empty strings (i.e., ""), null, undefined, and NaN. All other values are truthy. A string such as "false" is truthy. A string containing "0" is also truthy. As such, equal() and notEqual() are fine to compare simple values (strings, numbers, etc.) with each other, but you should be careful checking against Booleans to ensure you got the result you wanted.

The stringEqual() and notStrictEqual() methods test equality with === and !==, which will ensure that only actual values of true and false are treated as true and false, respectively. The ok() method, shown in Example 5-38, is a shorthand for testing whether something is truthy, by comparing the value with true using ==.

Example 5-38. Testing whether something is truthy with assert.ok( )

> assert.ok('This is a string', 'Strings that are not empty are truthy');
> assert.ok(0, 'Zero is not truthy');
AssertionError: Zero is not truthy
    at [object Context]:1:8
    at Interface.<anonymous> (repl.js:171:22)
    at Interface.emit (events.js:64:17)
    at Interface._onLine (readline.js:153:10)
    at Interface._line (readline.js:408:8)
    at Interface._ttyWrite (readline.js:585:14)
    at ReadStream.<anonymous> (readline.js:73:12)
    at ReadStream.emit (events.js:81:20)
    at ReadStream._emitKey (tty_posix.js:307:10)
    at ReadStream.onData (tty_posix.js:70:12)
>

Often the things you want to compare aren’t simple values, but objects. JavaScript doesn’t have a way to let objects define equality operators on themselves, and even if it did, people often wouldn’t define the operators. So the deepEqual() and notDeepEqual() methods provide a way of deeply comparing object values. Without going into too many of the gory details, these methods perform a few checks. If any check fails, the test throws an exception. The first test checks whether the values simply match with the === operator. Next, the values are checked to see whether they are Buffers and, if so, they are checked for their length, and then checked byte by byte. Next, if the object types don’t match with the == operator, they can’t be equal. Finally, if the arguments are objects, more extensive tests are done, comparing the prototypes of the two objects and the number of properties, and then recursively performing deepEqual() on each property.

The important point here is that deepEqual() and notDeepEqual() are extremely helpful and thorough, but also potentially expensive. You should try to use them only when needed. Although these methods will attempt to do the most efficient tests first, it can still take a bit longer to find an inequality. If you can provide a more specific reference, such as the property of an object rather than the whole object, you can significantly improve the performance of your tests.

The next assert methods are throws() and doesNotThrow(). These check whether a particular block of code does or doesn’t throw an exception. You can check for a specific exception or just whether any exception is thrown. The methods are pretty straightforward, but have a few options that are worth reviewing.

It might be easy to overlook these tests, but handling exceptions is an essential part of writing robust JavaScript code, so you should use the tests to make sure the code you write throws exceptions in all the correct places. Chapter 3 offers more information on how to deal with exceptions well.

To pass blocks of code to throws() and doesNotThrow(), wrap them in functions that take no arguments (see Example 5-39). The exception being tested for is optional. If one isn’t passed, throws() will just check whether any exception happened, and doesNotThrow() will ensure that an exception hasn’t been thrown. If a specific error is passed, throws() will check that the specified exception and only that exception was thrown. If any other exceptions are thrown or the exception isn’t thrown, the test will not pass. For doesNotThrow(), when an error is specified, it will continue without error if any exception other than the one specified in the argument is thrown. If an exception matching the specified error is thrown, it will cause the test to fail.

Example 5-39. Using assert.throws( ) and assert.doesNotThrow( ) to check for exception handling

> assert.throws(
... function() {
...   throw new Error("Seven Fingers. Ten is too mainstream.");
... });
> assert.doesNotThrow(
... function() {
...   throw new Error("I lived in the ocean way before Nemo");
... });
AssertionError:   "Got unwanted exception (Error).."
    at Object._throws (assert.js:281:5)
    at Object.doesNotThrow (assert.js:299:11)
    at [object Context]:1:8
    at Interface.<anonymous> (repl.js:171:22)
    at Interface.emit (events.js:64:17)
    at Interface._onLine (readline.js:153:10)
    at Interface._line (readline.js:408:8)
    at Interface._ttyWrite (readline.js:585:14)
    at ReadStream.<anonymous> (readline.js:73:12)
    at ReadStream.emit (events.js:81:20)
>

There are four ways to specify the type of error to look for or avoid. Pass one of the following:

Comparison function

The function should take the exception error as its single argument. In the function, compare the exception actually thrown to the one you expect to find out whether there is a match. Return true if there is a match and false otherwise.

Regular expression

The library will compare the regex to the error message to find a match using the regex.test() method in JavaScript.

String

The library will directly compare the string to the error message.

Object constructor

The library will perform a typeof test on the exception. If this test throws an error with the typeof constructor, then the exception matches. This can be used to make throws() and doesNotThrow() very flexible.

The best content for your career. Discover unlimited learning on demand for around $1/day.