IT IS HERE! Get Smashing Node.JS on Amazon Kindle today!
Show Posts
← Back to homepage

I’m writing this blog post to explain why I no longer consider assertion testing through projects like should.js, expect.js or chai ideal.

For the uninitiated, these modules allow you to write assertions in a very clear english-like way:

// my test file
expect(number).to.be.below(5);
someAssertion().should.be.ok;

But the most attractive quality is that they provide helper functions to make a lot of the common testing procedures like type checking straightforward.

Consider the situation where you want to test that a returned value is an object. With expect.js you write:

expect(ret).to.be.an('object');

Whereas, as we all know, in JavaScript you would have to write something much longer, because you need to make sure that typeof returns 'object' but also that the value is not null because we all know typeof null == 'object'

assert(ret && 'object' == typeof ret);

Forgetting to check that the value is not null before making such a test is a fairly common error, which can be very costly to make while writing tests. Even if you’re disciplined or just forego the usage of typeof altogether, you’ll find that Array type checking is just as complicated:

assert(Array.isArray(ret));

as the above line will definitely not work on browsers where Array.isArray is not supported.

Another common requirement is to test the “approximate” equality of two objects. We don’t actually want to test that the objects reference are equal, but to test that the objects are “equivalent”:

expect(obj).to.eql({ a: { b: { c: 'd' } });

If you want to see the usefulness of these utilities, I recommend you look at the tests I wrote for mongo-query. I implemented the entirety of the MongoDB query language for MyDB, and writing the tests for it was really easy and straightforward thanks to expect.js. The tests also need to run in all browsers and Node.JS, and that comes for free.

Beyond expect.js

The main problem with expect.js is that it acts as a framework. It’s a collection of loosely coupled utilities under a common namespace that happen to be useful when it comes to writing tests. This is fine in a world without a module system.

It’s rewarding to use expect.js because it makes things easy, but its design is not elegant nor simple. It’s easy in the way that jQuery makes it easy to do both AJAX and DOM by incorporating the same <script> tag, but that doesn’t mean it’s superior to combining modules that do each task separately, with their own code bases, tests and documentation.

The simple vs easy paradox has been expressed in this great presentation.

It’s not surprising expect.js was designed before the proliferation of excellent client-side module systems like browserify or component. A much better way to accomplish a similar outcome in a module-oriented way would be:

var assert = require('assert');
var type = require('type');
assert('object' == type(obj));
assert('array' == type(obj));

or if you needed to check for nested object equality:

var assert = require('assert');
var eql = require('deep-equal');
assert(eql(obj, { a: { b: { c: 'd' } } } }));

This has numerous advantages:

  • The learning curve is much lower.
    In the previous example it’s clear what the purpose of eql is. For expect, reading the documentation is mandatory, since like most frameworks it creates its own specific language. If someone is new to the codebase, they will only need to look into the modules that pertain a specific assertion they’re curious about, instead of an entire framework.
  • Smaller code footprint
    Tests will run faster, be faster to transport as a codebase to the cloud for automated browser testing.
  • Extensibility is controlled by the user. I receive numerous pull requests to add more functionality to expect.js all the time. The reason I’m writing this blog post is party to explain that it’s not that I should be merging more pull requests or adding new maintainers to expect.js, it’s that frameworks are not good for composing functionality.

The best assert

There’s one last advantage that expect-type modules have over simple old school assertions, in the context of JavaScript: they can provide more information about failures.

   ∴ ~ node
> require('assert')('undefined' != typeof window)
AssertionError: false == true
    at repl:1:19
    at REPLServer.self.eval (repl.js:110:21)
    at Interface. (repl.js:239:12)
    at Interface.EventEmitter.emit (events.js:95:17)
    at Interface._onLine (readline.js:202:10)
    at Interface._line (readline.js:531:8)
    at Interface._ttyWrite (readline.js:760:14)
    at ReadStream.onkeypress (readline.js:99:10)
    at ReadStream.EventEmitter.emit (events.js:98:17)
    at emitKey (readline.js:1095:12)

The error message AssertionError: false == true is not exactly useful. Luckily, there’s a way of solving this problem. In one of my previous articles I told you about the very useful v8 strack trace API.

Luckily, said API can be repurposed to get information about the line where the assertion failed, and produce a much better developer experience. TJ Holowaychuk (usual suspect) has created a little utility called better-assert to solve this problem. Update: another great module that was brought up to my attention that solves this as well is insist

$ test node index.js

/Users/guillermorauch/test/node_modules/better-assert/index.js:37
  throw err;
        ^
AssertionError: 'undefined' != typeof window
    at Object. (/Users/guillermorauch/test/index.js:4:1)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:902:3

There’s an elegant way to make this possible in all browsers however: we can rewrite the AST so that we automatically populate the second argument of assert calls (which corresponds to the message), with the content of the assertion itself.

Adam Reis is working on tackling this as part of our Open Academy mentorship program.

The hope is that when the V8 stack trace APIs are unavailable (such as when we’re performing automatic browser testing with Sauce Labs), code gets rewritten automatically.

assert('addEventListener' in window)

transparently becomes

assert('addEventListener' in window, "'addEventListener' in window")

through for example a browserify transform.

Conclusion

Thinking in a truly modular way for the JavaScript you write has an impact not on just your app’s main codebase, but also in the way you write tests, benchmarks and even documentation.

Whenever you catch yourself combining a lot of different utilities into a single module or namespace, consider if there’s a better alternative by making node_modules/ your namespace, and require() the way of accessing those utilities.

I recently upgraded to OSX 10.9 Developer Preview announced at the WWDC 2013. I highly recommend trying it out if you’re enrolled in the Mac Developer program and want to benefit from lots of substantial speed, security and reliability gains.

The most notable changes are around scrolling performance and the support of multiple displays and fullscreen applications. The focus on productivity improvements over eye-candy is not coincidental: iOS is clearly the operating system for day-to-day consumption, and the Mac for professional production and creation.

I came across a few problems trying to make my existing development environment work, and I’ve documented the solutions here.

Making Vagrant work

OSX now comes with an updated ruby:

∴ ~ /usr/bin/ruby --version
ruby 2.0.0p195 (2013-05-14 revision 40734) [universal.x86_64-darwin13]

To make Vagrant work on 10.9 I had to rollback to Ruby 1.9 after finding out gem install vagrant was failing. The easiest and safest way to do this is with brew, since it will install ruby in an isolated location.

∴ ~ brew install ruby

After running this, we need to make sure that the 1.9 version takes precedence over the system one in your path. Edit your profile like so:

export PATH=/usr/local/Cellar/ruby/1.9.3-p286/bin:/usr/local/sbin:/usr/local/bin:$PATH

Notice that in my case the installed version is 1.9.3-p286, it might be slightly different by the time you install it. Make sure the path is correct when you edit your PATH.

Now you can install your favorite ruby gems again without problems:

∴ ~ gem install vagrant

For the particular case of making Vagrant work on 10.9, you’ll also have to re-install VirtualBox.

Making Divvy work

All applications that required enabling support for assistive devices in the Accesibility preferences pane will stop working when you upgrade. Since this setting could be used in malicious ways by installed applications to gain control over the entire system, it’s been reset and now has to be configured in an app-per-app basis:

Make sure to unlock the pane with your administrator password, and re-enable access to your favorite apps.

The Chrome Dev Channel has recently introduced a revamped home screen that features a search box alongside the top sites. In addition, I noticed that the suggestions offered as you type now became part of the body of the page.

I tried to right click and unsurprisingly, it seems Google has decided to “dogfood” more and more of its browser’s chrome.

Chrome's WebKit rendered URL Suggestions

I headed to the Chromium issue tracker to learn more about the change and its implications[1]. I came across some predictable yet very well-formulated concerns over a potential performance degradation. In the process I learned that Chrome provides a chrome://omnibox URI to query the suggestions manually:

Chrome Omnibox

It’s not certain whether this change will stay, but I favor the idea of the browser using its own rendering engine for most of the UI, something that Chrome has been doing for its Settings, extensions management and even the print dialog. And maybe this will further extend the hacking surface of its rich extension ecosystem.

[1] Aside from a few links that seemed to be exclusive to Google employees, I still find it amazing that I can in a matter of minutes learn so much about the internals of a project of this caliber.

Email you receive on a daily basis falls into two primary categories:

  • A. Mail written by someone on a Mail User Agent (MUA), like iOS Mail or Gmail.
  • B. Mail written by a computer program, normally transactional email.

Almost every web application you use on a regular basis will send you email when different transactions occur. If someone adds you as a friend on Facebook, you’ll likely receive an email about it sent by Facebook’s backend server.

And unlike category A, the information contained in a lot of email from category B also exists in a web application you can access with a web browser. Chances are good you could visit facebook.com and read the notification (and even act on it) before you check your inbox, yet that same message will still be waiting for you in your inbox in an unread state.

You’ve probably found yourself reading chat email digests of messages you’ve already read in the original chat client (ironically, this even happens with Gmail’s own Google Talk). You might have even read emails about “new” pull requests you actually merged on Github hours ago.

The solution is actually a quite simple one: enable applications to dismiss email they’ve sent in the past, once they know that the email is no longer relevant.

A rough implementation could be as follows:

  1. Outgoing transactional email would contain a dismissal id in the form of a proprietary SMTP header: X-Read-ID: <hash>
  2. Once the information is “read” from the website or application, a new email is emitted with a custom format read({user}, {token})@host.com.

For example, if I’m sending Guillermo (rauchg@gmail.com) a notification email I would include a header like this: X-Read-Id: 55765639d46104a0de.

If Guillermo then goes to the website and reads the notification, I would now send a new empty email to read(rauchg, 55765639d46104a0de)@gmail.com. The email provider can then decide what to do with this amazing information. Gmail could archive those emails for you. Non-compliant email servers will simply bounce the email, making this completely “backwards compatible”.

tl;DR: only 2/3 bugs have an impact, one has a trivial workaround (jQuery snippet included below), the other one is UX related. Socket.IO needs no updates.

In the past 48 hours a lot has been written about the introduction of iOS6. With such a major software release it’s not surprise that a few issues have been introduced. Of particular importance to us web developers, three different bugs have been attributed to Mobile Safari:

  • A continuous “spinning loading indicator” reported on TechCrunch
  • Broken long-polling due to a connection limit set to 1 explained by Real Software
  • The introduction of aggressive caching of POST requests, reported on Stack Overflow

As it turns out, a lot of misinformation is being spread as well. In addition, all of these bugs are of very high importance to the development of the Socket.IO and Engine.IO realtime frameworks. Therefore I took the time to document the actual bugs and their workarounds in this post.

Read more

If MongoDB rs.status() displays the following error:

> rs.status()
{
	"startupStatus" : 4,
	"errmsg" : "all members and seeds must be reachable to initiate set",
	"ok" : 0
}

The first course of action is to look at the MongoDB logs. In my Amazon Linux installation, these are at /var/log/mongo/mongod.log.

$ tail -n 100 /var/log/mongo/mongod.log

You might find an entry like this:

(date) [rsStart] getaddrinfo("ip-10-0-1-94") failed: Name or service not known

Under certain configurations, your machine’s hostname might not actually be reachable. An easy solution is to make it so by adding the an entry to /etc/hosts:

127.0.0.1   ip-10-0-1-94

After restarting mongo, you’ll be able to run rs.config without problems with any IPs you want as members of the replica set.

About Guillermo Rauch:

CTO and co-founder of LearnBoost / Cloudup (acquired by Automattic in 2013). Argentine living in SF.