Thursday 9 May 2013

Test Driven UI Development with Node, Mocha and RequireJs (part 3)

Last time, we completed the scaffolding of our little web app.

This time, we'll make it actually do something - and perform a little refactoring along the way.  I'll also talk a bit about more advanced features of mocha.


As you may remember, we are implementing the Roman Numeral coding kata.  So far, we have an app object with 4 functions.

* init - sets up a click handler on the button to call the action function
* action - currently does nothing
* fetchInput - fetches the input from our input DOM element
* writeOutput - writes an output to the output DOM element

Here's a quick recap of the code:

We have installed the following modules:
* bean
* bonzo
* jsdom
* mocha
* qwery
* requirejs
* sinon

And here are the unit tests:

Let's begin by adding a new function "convertArabicToRoman" which will perform the conversion, and add a unit test for the action function in which it reads the input, passes it to the convert function and writes the result to the output.

So, lets run this for a red test...

 ✖ 3 of 4 tests failed

Wait just a minute - that's not just the new test failing (since we haven't even changed action to call convertArabicToRoman yet), but also 2 of the other tests.

So what caused those other failures?

This is down to the way that we have set up our stubs.  You'll notice that we reassign app to the module under test in a beforeEach block.  This actually has no effect whatsoever.  If you include a file with require more than once, it does not create a new object - it refers to the same object every time.

Ok, but that doesn't explain why the other tests failed.

If I had placed the test for action at the end of the test file, they wouldn't.  The tests are interacting because we are overriding the methods with sinon stubs.   The tests in this case are failing because they are no longer calling the real versions of fetchInput and writeOutput - they are calling the stubs.

So how do we solve this, do we need another test file?

No, not quite so drastic.  Fortunately, sinon will "wrap" methods with its stub.. allowing them later to be unwrapped and restored to their original versions.

We need to replace all occurrences this:

app.method = sinon.stub()

with this

sinon.stub( app,'method')

We can ignore the special case of bind now too.

I did that, and ran the tests again, but I've still got 3 tests failing!

We didn't add a step to unwrap those stubs and restore them.

One last problem.  Bean isn't called as a method, so we can't easily wrap and restore it.

This is not really an issue, since we are never going to want to use the real bean in our unit tests.  There is one little niggle though, the call to bind now returns a real function - so we need to drop the expected third parameter from our init test.

Now we just have a single red test as expected (sinon is erroring because convertArabicToRoman does not exist), lets get it green.


4 tests complete

That was pretty trivial since action is really just a simple function that glues the rest of our app together. Now we can get to the meat of our application - the conversion of roman numerals.

Since my aim isn't to walk through TDD of this kata here, I'm summarizing quite broadly.  I've started out with a test for conversion of single digits.

Ok, so a simple implementation works for that.  Quickly expanding to cover tens, hundreds and thousands.

I'm using a utility method (as part of my refactoring step) - whilst this is important to my implementation, I'm not exposing it or testing it directly;  I should really do so in strict TDD, but that would require exposing the method in order to test it - and if I refactored to perform the conversion a different way I would have to rewrite the tests too.

Now we just need to handle the error conditions to provide some feedback

Some thrown and caught errors to handle those conditions and we're done.

We've created DOM manipulation and business logic functions separately, and glueing them together with structural functions.  This maintains separation between the parts, enabling them to be tested in isolation - and it should enable us to change the structure of the page without requiring massive surgery to big parts of the javascript code.  This separation of responsibility is an excellent principle to follow.

Add a little CSS to make it look pretty (with Twitter Bootstrap), and....

A little refactoring of how we handle errors allows me to highlight them on the front end too.


To examine the final code, visit my github repo of this mini-project.  The conversion absolutely perfect (try a decimal fraction).

That concludes my mini-series on TDD of frontend code.   Hopefully I have enlightened you as to how you can use TDD in an environment which at first is not friendly without the need to test via headless browsers or some other convoluted sequence of hoops.

Feel free to comment or link back to me - I'm sure I'll come up with a new series to work through some more examples soon.

First post in the series: here
Second post in the series: here

No comments:

Post a Comment