Tuesday, December 4, 2012

What to test when testing Javascript

Part two in the series Test Driven Development with Javascript.



By now you've probably read and heard a lot warning you off testing through the UI and testing the user interface generally. Those still holds true for Javascript so you may be wondering if there is anything left to test. Time for some code!

Not very testable code

function validate() {
    if(document.simple_form.name.value == "" ||
        document.simple_form.email.value == "")
    {
        alert("Please fill out all fields before clicking submit!");
        return false;
    }
}


In this there is some logic that is not directly tied to the UI. So there is something we can test. But it’ll need a bit of a rework to make it testable

function isFormValid(formElement) {
    return (formElement.name.value != "" && formElement.email.value != "");
}

function validate() {
    if (!isFormValid(document.simple_form)) {
        alert("Please fill out all fields before clicking submit!");
        return false;
    }
}


Now we have a testable function isFormValid. It’s an example of separating the logic from the UI and the DOM interaction. There is still a DOM element being used but that’s fine as it is easy to create or perhaps mock in writing the test.

Code not worth testing

$(‘.button).click(function() {
  $(‘#my_quote’).css(‘background-color’, ‘red’);
}


This code is still testable despite using some jquery magic. It’s easy to mock out jquery or fake events and DOM enough to test it. Do not test this code. There is no real logic in there and it’s heavily tied to the UI interaction. This may mean a lot of the Javascript you’re writing does not have tests and that’s okay. Interactions like this are still best tested manually and if they fail in a harmful way will be obvious to anyone using the system. Logic errors however can be much harder to spot through manual testing.

Testable closures

function passwordCheckClosure(password) {
  return function(comparePassword) {
    return comparePassword === password;
  }
}


I love closures and closures like this are still really easy to test. Just as I would still expect you to create private methods and properties when doing TDD in C++ or Java. In Javascript I would still expect you to write closures to help build your code into clear abstract interfaces. But as with private methods and constructors you need to avoid hiding everything in closures.

Untestable closures

function validate() {
    function isFormValid(formElement) {
        return (formElement.name.value != "" &&
            formElement.email.value != "");
    }

    if (!isFormValid(document.simple_form)) {
        alert("Please fill out all fields before clicking submit!");
        return false;
    }
}


This is just a slight variation on our form valid check above. But now we cannot access isFormValid to test it separate to the validate method as a whole.

 Dependency injection with a closure

// formElenment - jquery reference to the form object
// alertMethod - alert on the browser. something else for console or tests
function attachFormValidator(formElement, alertMethod) {
    function isFormValid() {
        return (formElement.find(‘#name’).val() != "" &&
            formElement.find(‘#email’).val() != "");
    }
    formElement.submit(function(e) {
        if (!isFormValid()) {
          alert("Please fill out all fields before clicking submit!");
          e.preventDefault();
        }
      }
    );
}


This may seem harder to test. We've introduced expections that we’re using jquery and put everything within a function that does not return anything. But it’s actually very testable since we can easily mock out all the objects that we send in and trigger and test those actions through those mocks. Because objects are cheap and easy to construct that means it’s cheap and easy to mock things out.

Next time
Running tests with Jasmine and your web browser

This is part of a series I'm writing for Agile+ on Google+ so you can follow as I post them there