TDD and JavaScript with Jasmine

Written on

Tonight I had an hour-long slot at Trondheim XP and Agile Meetup. The subject was using TDD and JavaScript. I used the JasmineBDD framework to write my unit tests.

Demo on using TDD on JavaScript

I purposefully kept the slideshow to a minimum. I wanted it to be demo-heavy. I felt it would be easier to explain how to use TDD to unit test your JavaScript by showing it being done instead of talking about it.

All of the code I wrote is in my Samples github repository, specifically the code is right here.

I wanted to make certain points clear:

  • It isn't hard.
  • Once you get into the swing of it, using TDD on your JavaScript feels very smooth - the workflow is great. To prove this, I even did the first demo without using my mouse.
  • Our JavaScript today is doing increasingly complex stuff. Node.js even gives us the ability to run JavaScript on the server. Due to the complexity and importance of our JavaScript code nowadays, the importance to unit-test that code is also increasingly important.
  • Unit testing is made harder by code that has dependencies. On server-side code, dependencies might be databases, file systems or network communications. With (client-side) JavaScript, dependencies are typically two things: 1) the HTML/DOM, and 2) AJAX calls. My second and third demo's showed that those dependencies can be controlled and even faked. Firstly, you can set-up the DOM prior to running your tests with HTML test fixtures, and AJAX calls can be mocked with JavaScript spy objects.

Demo 1: implement the FizzBuzz kata

Using TDD from the word go, I implemented FizzBuzz. I created a standalone JavaScript module, and unit tested it until it was finished. This was an intro to Jasmine and TDDing your JavaScript.

Demo 2: writing a jQuery plugin

This demo showed the important technique of setting up the HTML / DOM prior to running a test, then running code that manipulates that HTML, and finally making assertions against the resulting HTML. Both the production code, and unit tests, made use of jQuery.

Demo 3: faking AJAX calls

The third and final demo showed the importance of JavaScript test spies. In JavaScript testing frameworks, spies assist us in all areas related to mocking/faking behaviours. True unit tests don't communicate over networks, but ideally we would like to test all of our own JavaScript code just before making an AJAX call, and the code that runs after the AJAX call is complete. This was achieved with Jasmine's test spies.

I've had nothing but fun while doing TDD and JavaScript over the last few months, and Jasmine has been a big part of that. Hopefully I spread the word tonight and encouraged others to check it out.


Comments

Great to see you making an impact in Trondheim mate. Keep living up to your potential! I'm certainly really interested now to follow this up, even without seeing you live! Good work :o)

Pete

Thanks Pete :-) Here is a gist of a JavaScript unit test with Jasmine, if you want a sneak-peak: http://gist.github.com/571551

Alex

Interesting stuff! I didn't get to see your presentation, but I'm excited that more and more people are talking about testing and TDD with JavaScript! I have another stub/mock tool for you as well, Sinon.JS: http://cjohansen.no/sinon/ which is test framework agnostic. The reason I bring it up is that it offers a slightly different way to work with XHR, which can give you less implementation specific tests. I copied one of your test cases as a gist to show the difference: http://gist.github.com/611155

Christian Johansen

@Christian: Thanks for your input. Your gist was very interesting. I can see that in the test setup (beforeEach) you just do this: server = sinon.useFakeServer(); I was doing this with Jasmine: spyOn(jQuery, "ajax"); The benefit with sinon, like you pointed out, is that sinon gives me less implementation-specific tests, i.e. my tests would need to be refactored if I used some other AJAX library instead of jQuery at a later date, whereas sinon (I guess) mocks XHR directly, meaning the tests could remain unchanged regardless of what AJAX wrapper I was using. Thanks for pointing that out. BTW - I can't wait to read your new book (Test Driven JavaScript Development). It is next on my reading list right after I finish JavaScript, The Good Parts.

Alex

@Alex - regarding your concern about coupling your implementation to a specific Ajax library - have you considered wrapping the call in an application specific facade/adapter? I am asking because I am thinking about doing this for the very reason that @Christian has brought up. Doing some reduces the surface area taken up by jquery, but I can't help thinking that I am solving a problem that I don't really have. Thoughts?

Brian Chiasson

@Brian: wrapping all AJAX calls sounds like a good idea to me, if that means that the AJAX library you are using could be swapped-out in the future with greater ease. You got a code snippet with an example? There is always a balance between thinking-ahead and designing your system well, and at the other end of the spectrum over-engineering it (i.e. YAGNI: You Ain't Gonna Need It!).

Alex

@Alex, I have nothing concrete. It just seems like a bad idea to have $.ajax({...}) throughout the application. I have started to think about trying some convention based stuff, but it turns out that many of our pages have multiple calls, so I couldn't do anything around URL conventions. Nor have we settled on a GET-only versus POST-only convention. And data type is a per call basis as well, so there really aren't gains there. I haven't really thought long and hard about this. The only thing we discussed was creating an app specific ajax call. It would look identical to jquery's but wrap it. I guess we get the benefit of being able to swap out any library and doing application specific extensions, but it feels like YAGNI. Maybe we get a testing benefit, but we would be spying on our library instead of jquery's. Then there's the trouble of selectors and wiring events. If we are going to wrap the Ajax stuff, why not the rest? It's a tough one, and probably why we haven't made a decision either way.

Brian Chiasson