Spying on the DOM
I’ve always been skeptical about TDD and code testing. I found it hard and time consuming. However, there is something much much harder and more time consuming than testing. You’ve probably guessed it, bugs! As the codebase grows larger and the development team brings in new members, it becomes harder and harder to sleep better at night knowing that the code written a week ago will still survive the changes of tomorrow. I don’t want to discuss the benefits of unit testing because that topic deserves a whole other article on its own, and if you do a quick google search you will surely find quality content on this subject.
So let’s jump straight to the point. I found my self landing on a Backbone project. The framework choice doesn’t really matter at this point. I am more concerned about how the codebase will grow and how stable it will remain. I am not saying that the framework isn’t important. However, one thing is for sure, things can easily go spaghetti if one doesn’t understand the grounds on which the framework/library was built—no matter the stack.
So like I said, I was concerned with the quality of the code. Because I wanted to sleep better at night, I started writing tests. As most of you may know, when working with Backbone you will sooner or later bring jQuery into the equation. More so, most developers tend to abuse the powers of jQuery, no matter what framework they work with. So, to keep the code as testable as possible, I found that a few rules were very helpful to follow:
- keep the DOM manipulation to a minimum (no matter the framework)
- even if you feel the need to use jQuery , apply it locally
- treat the DOM as a presentation layer and NOT a logic layer
The reason I found these rules helpful is because testing the DOM itself brings so much overhead. Also, I don’t really find view testing really helpful. In my opinion, presentation layer testing has nothing to do with code quality.
Because of this, I’ve decided not to use any kind of spawned browser to test the application, this means no Phantomjs, no Chrome no nothing. Instead I’ve only used these three libraries:
These tools are great for unit testing your code. However, if you want to test anything that calls jQuery, you will stumble upon a little error which informs you that jQuery needs a functional window object to work. So… dead-end? Not quite. We can still make do without a browser.
Introducing our secret weapon: jsdom
With this bad boy we can simulate the whole DOM and still execute jQuery code under nodejs. So at this point our nodejQuery (if you will) is only a few lines of code away:
There, now we can safely unit test our jQuery dependent code under our mocha test runner without any errors.
All this is good, but we still have to make sure that our existing code won’t break. For this part, we will have to make a factory function called getjQuery() which will return a singleton nodejQuery object.
Ok good, now lets code something that we can actually test. Let’s assume that we have this simple validator object:
This module simply offers a validate() function which takes three arguments:
- rule ( a pure boolean function for evaluating a simple string )
- inputSelector ( we will use this to attach the .error class on the input we want to validate )
- valueGetter ( a function which will return the current value of the input we want to evaluate )
For testing this module we will need the help of sinonjs , more exactly we will use spy functions . A spy function simply let’s you know when a function was called, or how many times, or with what arguments. In order to properly test our jQuery code we will need to create spies on all jQuery functions such as: hide, show, append, etc.
To achieve this, we will add a new function to our jquery-getter.js module:
With the help of getJqureySpies() we will modify our singleton nodejQuery version of jQuery, and create spies on all public functions that jQuery provides. So now we have our two main players which will help us test the validator module:
However, the validator module still imports the original jQuery library. And we will want to switch that jQuery function with our modified nodejQuery one. To do this we will use this awesome module called proxyquire . With the help of proxyquire we can create a man in the middle kind of situation and when the module will try to import the jQuery function, it will actually be importing our modified nodejQuery version. Sneaky, right?
So let’s see at how the final unit test will look like:
And voilà, we now can unit test jQuery dependent code without spawning Phantomsjs or Chrome or other kind of browsers. By doing this we gain one major benefit: speed . Your unit tests will run super fast. Also, you will be forced not to abuse jQuery so that your code would remain testable.
This might not be the best solution for this problem but it did the job for me. Also you should keep in mind that the more complex the jQuery code, the harder it will be to unit test. So that’s why I mentioned earlier that I try to keep the DOM manipulation to a minimum.
I’ve learnt that testing your code isn’t so hard if you do it from the beginning. And the benefits that brings to your codebase are noticeable on the long run. I’ve also learnt that testing your front-end code is hard, you have to make all sorts of mocks and hacks to make it work. But, in my opinion I think it’s worth it. I for one, will keep trying to find ways to make my code as easy to unit test as possible.