33

How do you go about unit testing javascript that uses and modifies the DOM?

I'll give an easy example. A form validator that checks for blank text fields, written in javascript and that uses JQuery.

      function Validator() {

        this.isBlank = function(id) {
            if ($(id).val() == '') {
                return true;
            } else {
                return false;
          }
        };

        this.validate = function(inputs) {

           var errors = false;

           for (var field in inputs) {
               if (this.isBlank(inputs[field])) {
                   errors = true;
                   break;
               }
           }

           return errors;
       };
    }

Usage:

var validator = new Validator();
var fields = { field_1 : '#username', field_2 : '#email' };

if (!validator.validate(fields)) {
    console.log('validation failed');
} else {
    console.log('validation passed');
}

What is the best practice for attempting to unit test something like this?

BIOS
  • 1,655
  • 5
  • 20
  • 36

3 Answers3

23

Ideally you should split the code. The validation logic does not really require DOM access, so you would put that into its own function, and put the logic that processes the ID into a value that is then validated into another.

By doing this, you can more easily unit test the validation logic, and if necessary do a functional test on the whole thing using some of the tools suggested by Joseph the Dreamer.

Jani Hartikainen
  • 42,745
  • 10
  • 68
  • 86
  • 1
    This is absolutely the way to go. Interacting with the DOM usually means you are performing more of a functional or integration test. Place your validation code into a separate module with it's own unit tests. Then incorporate the module using your favorite module loader such as browserify https://github.com/substack/browserify – Noah Jun 01 '13 at 04:49
10

You can use Qunit for unit tests involving DOM. If you want it automated, you can use Grunt with grunt-contrib-qunit task which launches your pages in a headless WebKit called PhantomJS.

Joseph
  • 117,725
  • 30
  • 181
  • 234
9

In unlikely case when most of your JavaScript code contains logic and rely on small amount of jQuery code (or live DOM elements) you can refactor code to make replacing access to DOM/usage of jQuery easy and write test with mock implementations:

 function Validator(){
    this.getElementValue = function(id){return $(id).val();}

    this.check_blank = function(id){
    if(this.getElementValue(id) == '') // replace direct call to jQuery mock-able call
       return false;
    else
       return true;
    }....
}

And in test provide mock implementation:

 test("Basic valid field", function() {
   var validation = new Validator();

   // replace element accessor with mock implementation:
   validation.getElementValue = function(id){
     equals(id, "#my_field"); // assert that ID is expected one
     return "7";
   }
   var form_fields = {field_1 : '#my_field'};

   ok(validation.validate(form_fields), "non-empty field should be valid");
 }
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179