<flame>
Javascript seems to gets left in the dust when we consider code quality. It’s been rigged, scattered, and smeared every which way from Sunday. Most sites of significant complexity have a severe amount of legacy, rotting code buried deep in a countless number of files. There is little separation of concerns in places, where event handlers written alongside presentation markup are validating data and controlling flow simultaneously. At times, the code gets hacked up at the end of a project to deal with browser flaws and edge cases, and quickly mutates from something not-so-bad to an ugly beast.
</flame>
Lo and behold, there’s a way out! There’s very few valid excuses for not writing cleaner, prettier, more maintainable Javascript. Many of the same principles preached in Python hold true.
There are some prevalent concepts that can help us write better code. Understanding and applying unobtrusive Javascript can help alleviate everything from cluttered namespaces to browser differences and performance issues — I suggest reading the 7 rules. Following directly from that, progressive enhancement principles are fantastic to allow for graceful degradation and accessibility. Do with Javascript what we know we should do with CSS. Here’s some food for thought about progressive enhancement. Additionally, writing loosely coupled code (i.e. methods not tied directly to the DOM unless necessary) provides a separation of concerns that helps with debugging and testability. Finally, automating the testing of your code to ensure it’s doing what you think it should do (prior to sending it to QA for them to check) is powerful in and of itself.
If you’ve been bitten by the testing bug and find yourself wanting to test your Javascript code, you’ve come to the right place. The first question you should ask yourself is, “what should I be testing?”. I will focus on unit testing in this article. You should also be aware that there are powerful tools (e.g. Selenium) for integration and browser testing. But first things first.
So, what are common things done in Javascript that might need to be unit-tested? Here are a few examples:
- Client-side validation. Let’s ensure all bases are covered with validators that are written to check email addresses, birthdays, maximum lengths, etc.
- Methods that compute window sizes or element positions. There’s often lots of math, and edge cases can be killer.
- Basic things ingrained in the Dojo framework, such as class inheritance. You’ll want to ensure that your class behaves as you expect.
Let’s take the example of a email address validator and see how we might write it and test it. Here’s one method, which happens to validate a string expected to be a valid email address, with a length less than the specified maximum. This would be located at $JS/dojo1.2/src/app/validators.js
.
dojo.provide('app.validators'); app.validators.isValidEmail = function(email, maxLength) { if (email.length > maxLength) return false; var pattern = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; return pattern.test(email); }
There are several things we might want to test. For example, we should test that the maximum length functionality works. Additionally, we should test that it doesn’t allow emails without a domain to pass through. And we don’t want to leave out testing that a valid email can pass through. In an ideal world, we would test every reasonable branch through the function, and through the regular expression itself.
Here are 3 test cases, presented in a format acceptable for the D.O.H. framework
Case 1: Too long of an email is invalid
runTest: function(){ doh.assertFalse(app.validators.isValidEmail('123456', 5)); }
Case 2: Email without a domain is invalid
runTest: function(){ doh.assertFalse(app.validators.isValidEmail('foo@.', 50)); }
Case 3: A valid email should validate as such
runTest: function(){ doh.assertTrue(app.validators.isValidEmail('foo@bar.baz', 50)); }
We can then insert these into a D.O.H.-compatible test package. Open an editor to $JS/dojo1.2/src/app/tests/test_validation.js
, and register the tests from above as shown below.
dojo.require('app.validators'); doh.register("app.tests.test_validation", [ { name: 'test_validate_email_too_long', runTest: function(){ doh.assertFalse(app.validators.isValidEmail('123456', 5)); } }, { name: 'test_validate_email_nodomain', runTest: function(){ doh.assertFalse(app.validators.isValidEmail('foo@.', 50)); } }, { name: 'test_validate_email_ok', runTest: function(){ doh.assertTrue(app.validators.isValidEmail('foo@bar.baz', 50)); } }, ]);
The last steps are to plug it into a test “module” (so it can be discovered by the D.O.H. runner), and run it to ensure things are working as desired. Point an editor to $JS/dojo1.2/src/app/tests/module.js
, and add the following line:
dojo.require("app.tests.test_validation");
D.O.H. provides a browser-based test runner for executing tests. Assuming you have Dojo 1.2 available on your server, modify the following path to suite your installation: http://servername/js/dojo1.2/src/util/doh/runner.html?testModule=app.tests.module
.
Hold tight for the next installment, which will include mocking in Javascript and introduce the Rhino runner. Happy coding!