EO
code

Dropbox Express 2: Zombie testing.

This is how I built a simple server-side JavaScript app on top of the Dropbox API, using Express.js, ECMAScript 6 (and one thing I hope will be in ES 7), and Zombie.js for testing. It was my first time using any of these things (except JavaScript, natch), so there are probably better ways to do some of it. Let me know!

In part 1, we set up Node, Express, and Babel, some basic tools for writing our application. But we’re going to do test-driven development (somewhat), so let’s start by testing the most fundamental thing: Our app does something.

Mmm, zombie mocha.

I’m a fan of outside-in testing, where you start by writing high-level tests and then write unit tests and application code to support them. For this project, I wanted to be able to test client-side JavaScript, because I thought I would be using OAuth 2 to authenticate users with the Dropbox API (spoiler: I didn’t), but I didn’t want the overhead of a browser-based tool like Selenium, so I settled on headless testing with Zombie.

I also chose Mocha as a testing framework, because it’s popular. Let’s install them!

npm install --save-dev zombie mocha

A test, first.

The first thing we’re going to do is just test that our app renders something when we hit the root URL. Create a folder called test and put acceptance_test.js in it:

const browserContext = require('./browser_context')

describe('Home page', () => {
  browserContext()

  beforeEach(function () {
    return this.browser.visit('/')
  })

  it('says hello', function () {
    this.browser.assert.text('body', /hello/i)
  })
})

In this file, we describe a test for the home page. First, we set up a browser context (see below), which gives us a Zombie browser to work with. Before each test, we tell the browser to visit the home page; and in our only actual test, we assert that the page contains the word “hello.”

Notice a couple of ECMAScript 6 features:

  • the const statement declares a constant; if anything tried to change the value of browserContext later in the program, we’d get an error
  • the arrow function on line 3

In general, arrow functions make the meaning of the this variable more intuitive, but we can’t use them on lines 6 and 12. I think this is because Mocha’s beforeEach and it functions set this for the functions they’re passed, but I haven’t dug into it.

Configuring Mocha.

We need to tell Mocha we’re using ES6. We can do this by setting up a test command in package.json. Edit the “scripts” section:

"scripts": {
    "test": "./node_modules/.bin/mocha --harmony --compilers js:babel/register"
  },

The --harmony flag tells Mocha to use Node’s built-in ES6 support, and the --compilers flag tells it to use Babel. (I don’t know why we need both.)

While we’re in here, note that package.json is where the input from npm init ended up, back in part 1. Also, as we’ve been using npm install, our dependencies have been recorded here.

You should now be able to run npm test in the shell, and it will run the mocha command we specified, but it will fail, because our test requires a file that doesn’t exist.

Browser context.

Our acceptance test relies on a little glue to start up the app and then point a Zombie browser at it. (I adapted this from a post by Victor Arias.) Create test/browser_context.js:

const app = require('../app')
const Browser = require('zombie')

module.exports = () => {
  // Before we run the tests, start up the app, and point a new
  // Zombie browser at it.
  before(function () {
    this.server = app.listen(3030)
    this.browser = new Browser({site: 'http://localhost:3030/'})
  })

  // After we're done, shut down the app.
  after(function (done) {
    this.server.close(done)
  })
}

When you require a file in Node, you’re loading a module, and specifically you’re loading whatever it puts in module.exports. So in this case, browser_context.js exports a function that sets up some Mocha hooks. In acceptance_test.js, we set browserContext to that function, and we run it in our test definition.

At this point, npm test still fails, because browser_context.js requires another file that doesn’t exist yet: the actual app. How frustrating! We’ll write it in part 3.

arts