Smashinglabs

Sebastian Poręba's blog

Unit testing for jQuery plugins – part 1

Recently I’m writing some QUnit tests for my gMap plugin and I have some thoughts to share. I’m not going to focus on any specific testing framework, though you may read QUnit introduction. Choose your testing tool by yourself if you don’t like QUnit. I will write more about when and how to use tests, so they make any sense. Article will be split into three parts – introduction to testing, CSS tests and actual jQuery plugin testing.

I don’t want to say, that JS is not a good language, but I’m quite sure that a lot of programmers don’t take it too serious. Some of them are not even programmers and work with HTML+CSS only. What is usually expected from jQuery plugin is to be initialized in one line of code, with some config object. The rest should be magic and nobody bothers how plugin actually works. It’s our job to check all possible configurations for any possible errors.

Development cycle of plugin is usually focused on adding new configuration options and improving old ones. My gMap, after growing for few months and having some impact, is so big, that even I can’t fully remember how it works. I often receive emails “hey, I’m using feature X and it breaks Y, could you fix that?” and I usually think “wow, it does that too!?” (please focus on fact, that something is broken, not on my being pleased with myself). So it was about the time to create some tests, so I can be sure that any improvements I work on will not break existing stuff. It’s also very useful in github community, now I can test any incoming pull request.

Do I really need unit tests for front-end?

I’d say you need them especially there! I see few reason for this:

HTML and CSS dies in silence

I can’t even count how many times changing few lines of CSS broke something I thought was completely unrelated. You may say it proves I’m a bad programmer, but I’m not alone in my projects and honestly – there is always some part of CSS marked “magic, do not touch” or some genius who hides “p {maring-left: -1px;}” somewhere. Firebug helps, of course, to track down root of all evil, but first you have to notice that error is there. There are still people who cares about pixel-perfect layout, and I bet your PM is one of them.

Measuring sizes, distances, etc., with unit tests is a quick way to check if everything is fine, even if project lasts for ages. It is also a great way to remove unused styles – just do it and check if tests are still passed. I would actually pay for a magical unit test generator, so I could say “I would like this div and its contents to look like that, forever”.

JavaScript is fun, unless you actually use it

I think we all know the bad parts of JS already. But it’s not only about strange, rare quirks. With dynamic typing your code is more vulnerable for errors, if you don’t check what type your variables are. I’m going to show later a good example for that. Also, if your team is not very familiar with closures and variable scope, you will quickly find yourself in a global variables nightmare.

Browser testing is madness

I counted 77 browsers at browsershots.org and I’m pretty sure that they have missed few. Good luck with testing everything, everywhere after every change, if your project lacks automated testing.

How to NOT write tests

Consider such example: you’ve built innovative and marvelous piece of code, online calculator. It’s still in beta, so it only adds two numbers.


function addTwo(a,b) {
return a + b;
}

Now, here is how you could write your test. I assume that you know basics of QUnit, which may be untrue. Don’t worry, it’s quite self-explanatory, and I’ll use only equality check.


test('addTwo test', function() {
   equal(addTwo(10,20), 30);
}

First reason why it’s wrong is amount of assertions. Testing doesn’t mean “assume everything works fine and provide data to prove that”. Unless you are a student, avoid such thinking.  One test-one assertion approach is like not writing tests at all. Look for edge cases and malicious data. With our great function it could be


test('addTwo test', function() {
   equal(addTwo(10,20), 30);
   equal(addTwo(10,-20), -10); // negatives ok?
   equal(addTwo(0,0), 0); // zeros?
   equal(addTwo(1000000000000000000000000000000000000000000000000000000000000,1),1000000000000000000000000000000000000000000000000000000000001); // superlong?
   equal(addTwo(10,false), 10); // JS funny stuff
}

We have a lot of assertions and it looks like passing tests will actually mean that function is working as intended. This aproach raises an issue which I think is most important with jQuery plugins. We haven’t tested any user interactions and we missed an obvious error. Typical usage of such function would be:

Super Duper Calculator 3000

<form>
   <input type="text" name="a" id="a" />  +  <input type="text" name="b" id="b"/>
</form>

If we fetch data (let’s say, 10 and 20) from these inputs and pass it to our function, it would return “1020”, because input values are strings by default. What I’m trying to say is that correct testing should always simulate actual user actions. With jQuery it’s very simple to fill forms, trigger clicks, focus, hover and all other events that normal user typically create. For our function correct approach would be:

test('addTwo test', function() {
   var a = $('#a'), b = $('#b');
   a.val(10); b.val(20);
   equal(addTwo(a.val(),b.val()), 30);

   a.val(10); b.val(-20);
   equal(addTwo(a.val(),b.val()), -10); // negatives ok?

   a.val(0); b.val(0);
   equal(addTwo(a.val(),b.val()), 0); // zeros?

   a.val(1000000000000000000000000000000000000000000000000000000000000); b.val(1);
   equal(addTwo(a.val(),b.val()),1000000000000000000000000000000000000000000000000000000000001); // superlong?

   a.val(10); b.val(false);
   equal(addTwo(a.val(),b.val()), 10); // JS funny stuff
}

Now we can see, that all tests will fail, unless we turn our function into something like


function addTwo(a,b) {
   var sum = parseInt(a) + parseInt(b);
   if(isNaN(sum)) {throw "add my shiny metal ass"; }
   return sum;
}

Now, we can be quite sure that everything works fine, we can let some intern mess with our code and turn it into full calculator. We did most important work, after all.

It was a quick introduction, I hope you liked it. Please don’t forget, that your tests may not cover everything. Starting your day with writing few original assertions as a warm-up looks like a good idea.

In next part – how to test your CSS and DOM. I’ll also release my gMap tests as soon as I complete covering all features.


  • RSS
  • Facebook
  • Twitter

FAQ about Wordpress

This came as a surprise for me but gMap is ...

gMap 3.3.3 released

It was a looong time since I last visited gMap. ...

Talks for Google Dev

Two new slide decks appeared in lectures tab. This time with ...

Talks and lectures w

Every now and then I spend a weekend watching various ...

3D Tetris with Three

In the fifth part of tutorial we add some final ...

FAQ about Wordpress

This came as a surprise for me but gMap is ...

gMap 3.3.0 released

Christmas came early! New version of gMap is ready!

Lecture for GTUG: Ja

Today I gave a lecture for GTUG Krakow about optimizations in ...

Unit testing for jQu

In part 1 I described basics of unit testing in ...

Unit testing for jQu

In part 1 I described some basic concepts behind unit ...