JavaScript All the Way Down

Listing 3. Using promises produces far more legible code.


nurseryRhyme(...).
  .then(function eeny(...) {...})
  .then(function meeny(...) {...})
  .then(function miny(...) {...})
  .then(function moe(...) {...});

You also can build promises out of more promises—for example, a service might need the results of three different callbacks before producing an answer. In this case, you could build a new single promise out of the three individual promises and specify that the new one will be fulfilled only when the other three have been fulfilled. There also are other constructs that let you fulfill a promise when a given number (possibly just one) of "sub-promises" have been fulfilled. See the Resources section for several possible libraries you might want to try.

I have commented on several tools you might use to write your application, so now let's consider the final steps: building the application, testing it and eventually deploying it for operation.

Testing Your Application

No matter whether you program on your own or as a part of a large development group, testing your code is a basic need, and doing it in an automated way is a must. Several frameworks can help you with this, such as Intern, Jasmine or Mocha (see Resources). In essence, they are really similar. You define "suites", each of which runs one or more "test cases", which test that your code does some specific function. To test results and see if they satisfy your expectations, you write "assertions", which basically are conditions that must be satisfied (see Listing 4 for a simple example). You can run test suites as part of the build process (which I explain below) to see if anything was broken before attempting to deploy the newer version of your code.

Listing 4. Suites usually include several test cases.


describe("Prime numbers tests", function() {
  it("Test prime numbers", function() {
    expect(isPrime(2)).to.be.true();
    expect(isPrime(5)).to.be.true();
  });
  
  it("Test non-prime numbers", function() {
    expect(isPrime(1).to.be.false();
    expect(isPrime(4).to.be.not.true();	// just for variety!
    expect(isPrime(NaN)).to.throw(err);  
  });
});

Tests can be written in "fluent" style, using many matchers (see Listing 5 for some examples). Several libraries provide different ways to write your tests, including Chai, Unit.js, Should.js and Expect.js; check them out to decide which one suits you best.

Listing 5. Some examples of the many available matchers you can use to write assertions.


expect(someFunction(...)).to.be.false();  // or .true(), .null(), 
                                          // .undefined(), .empty()
expect(someFunction(...)).to.not.equal(33);  // also .above(33), 
                                             // .below(33)
expect(someFunction(...)).to.be.within(30,40);
expect(someObject(...)).to.be.an.instanceOf(someClass);
expect(someObject(...)).to.have.property("key", 22);
expect(someResult(...)).to.have.length.above(2);
expect(someString(...)).to.match(/^aRegularExpression/);
expect(failedCall(...)).to.throw(err);

If you want to run tests that involve a browser, PhantomJS and Zombie provide a fake Web environment, so you can run tests with greater speed than using tools like Selenium, which would be more appropriate for final acceptance tests.

Listing 6. A sample test with Zombie (using promises, by the way) requires no actual browser.


var browser = require("zombie").create();
browser.localhost("your.own.url.com", 3000);
browser.visit("/")
  .then(function() { // when loaded, enter data and click
    return browser
      .fill("User", "fkereki")
      .fill("Password", ¡")
      .pressButton("Log in");
  })
  .done(function() { // page loaded
    browser.assert.success();
    browser.assert.text("#greeting", "Hi, fkereki!");
  });
______________________