Tuesday 23 April 2013

Test Driven UI Development with Node, Mocha and RequireJs

Test driven development, or TDD, is often held up as an ideal to aim for in unit testing.  Its simple premise of write a test, pass the test, refactor the code - or red, green, refactor - leads to good coding habits.

But testing your front end javascript code is hard... just getting it into a position where you can test it at all is tricky, and following examples on the web will tie you in knots.  Then there's yet another problem... dependency injection.  With so many hurdles to jump, it is little wonder that many projects have a handful of fragile integration tests that leave you wondering whether the trouble was worth it.

I'm going to attempt to lay out a method by which front-end code can be readily unit tested, without needing a headless browser.


In this first post, we will set the basic framework of tests, modules, and frontend markup that will allow you to build a TDD web app.


Start with a computer with a working copy of nodejs.  I'm using my macbook, with node v0.8.12.  You should also have npm working - we'll use that to install more node modules.

Straight on to the node modules, we're just going to use mocha (the testing framework), sinon (a mock framework) and requirejs (a file and module loader).

But hang on a minute, node loads modules itself - right?

Sure, but we're doing frontend development; we going to write our code as a module, and use requirejs to load it an manage dependencies.

Wait a second... requirejs loads modules using the AMD syntax, and node needs its modules in CommonJS form.

Okay, so we're jumping through some hoops here - I'll get to the explanation in a moment.  Lets set up a working directory and get our modules installed.

mkdir tdd-frontendjs
cd tdd-frontendjs
npm install mocha sinon requirejs

Directory layout is important so that you know what goes where.

src - our source code in here
build - libraries and such in here
test - mocha tests
node_modules - npm has already created this and put mocha, sinon and requirejs in there.

mkdir src
mkdir build
mkdir test

Okay, back to the explanation...

We're going to write our code in CommonJS form as a node module, and test it exactly as if it were backend code.  Then we're going to use the requirejs optimizer to wrap it up into AMD form, which should then be usable in the frontend.

Obviously this process won't test if we've got it looking right, or got the correct selectors or DOM structure - but that's not what unit testing or TDD is about.  UX and UAT testing should ensure that the visuals are ok - our unit testing will confirm that the business logic is correct.

So lets crack right on and write our first test:

Ok, so what have we got in this test? - The app must exist as a module, with an init function that executes the callback passed to it.  It may seem like a useless test, but I wanted to write a test that didn't involve any business logic to start with, we can always refactor it and delete the test if we don't like it later.

The binary for mocha is in node_modules/mocha/bin, so lets run it now

node_modules/mocha/bin/mocha

Error: Cannot find module '../src/app'

That's a good TDD start - a red test.  Let's make it green

node_modules/mocha/bin/mocha
.
1 test complete (1ms)

Ok.  We're rolling with TDD.

But how we're going to get it into the web page?

Lets start with some kind of web page structure.


We need access to require.js in the front end, so put require.js in build/lib - I just symlinked it to the one in node_modules.  Now we can access this page directly in our browser with the URI file:///path-to-dir/index.html (or you can set up node, apache, nginx or something to serve the page statically - I'm just being lazy and using file)

Error: Script error

Ok, require.js is rather unhelpful in that it isn't telling us that it just can't find the main javascript file that we specified in data-main.  But that's the problem, so we'll deal with that.

Error: Script error

Ok, so we have app.js in src, but we don't yet have app.js in build.  We need to use the requirejs optimizer.

node node_modules/requirejs/bin/r.js -o baseUrl=src name=app out=build/app.js cjsTranslate=true

This runs the optimizer on src/app.js and builds that (and all its dependencies) into build/app.js.  The final argument tells the optimizer that we're using CommonJs and it should wrap all the CommonJs in an AMD style define statement.

You can add uglify.beautify=true if you'd like the code layed out in a structure that is easier to step through in yout browser debugger.

The result is this code

Now we load the webpage, and the console shows:

Application initialised

So we've got our app module under test (even if the test is pretty simple), and we can compile it into a minimized form and use it in the front end.

We've got our framework set up - next time we'll do some TDD DOM manipulation, and later on build some simple business logic.

Second post in the series: here
Final post in the series: here

No comments:

Post a Comment