Write a post

Simple & Understandable Architecture For Test Driven JavaScript

Published Feb 03, 2017
Simple & Understandable Architecture For Test Driven JavaScript

Test driven development (TDD) is the process of testing code before it is written. The developer finds a problem, comes up with a solution, and then writes a test to test that solution. This test will of course, fail, so the developer will then write code that is able to pass that test. This process is continued until the software is complete. Let’s say, for example, that we need to write a function that iterates over an array. If no items in the array are greater than a value “x” then the function returns false. Otherwise, it returns true.

TDD JavaScript test driven development

Following TDD principles we start with an empty function:

function arrayValueFunction(x, array) {
  return null;
}

To test the function, we would write the following test:

describe("The arrayValueFunction ", function() {

   // test that it returns true
   it("returns true if any value in the Array is greater than X", function() {
     var array = [1, 2, 3, 4];
     var result = arrayValueFunction(2, array);
     expect(result).toBe(true);
   }

   // test that it returns false
   it("returns false if no value in the Array is greater than X", function() {
      var array = [1, 2, 3, 4];
      var result = arrayValueFunction(5, array);
      expect(result).toBe(false);
   }

}

Both of these tests fail because our function is empty and returns “null.” Therefore, we need to write the function to pass the tests.

function arrayValueFunction(x, array) {
  var current_value = 0;
  for(var i=0; i<array.length; i++){
     if(array[i] > x){
       return true;
     }
  }
  return false;
}

This is the basic foundation behind TDD. However, many developers don’t follow this process. Instead, they write tests after they write their code in order to satisfy the project requirements. Some of them never even write tests.

TDD is designed to help you think about your program’s structure. We design tests around how we would like our software to be developed. TDD forces you into a paradigm of iteration and refactoring, helping you make your code better every time you write a test. It also helps you think about the best way to write a test for the code you intend to write. These are all benefits that are lost if you write your code before you write your tests. By writing tests first, they will not written wit the intention to pass the code that is already there. Tests can be biased towards the code if the code already exists. So please, write your tests first. It will make you a better programmer and allow you to build better software.

Here’s a great exercise: think of a piece of software you want to build. Now write a list of features and requirements you would like it to have. Now, one by one, write a test for each of these features. Finally, write code that will pass these tests.

A little structure is all you need!

Let’s say we need to build an Internet connected “DoggyDoor”. When my dog’s Bluetooth LE (iBeacon) collar connects to the door, the door will open after 30 seconds. When the collar disconnects, the door closes. For the purpose of this example, we will only look at the logic for managing the opening and closing of the doggy door.

The Code

// JavaScript Code

var connected = false;
var doorOpen = false;
var timer = null;

// assume these are real things - NOT REAL! :)
var door = new DoggyDoor();
var collar = new CollarListener();

var app = {
   init:function(){

      collar.onConnect = function(data){ 
          connected = true;         

          setInterval(function(evt){
       
             if( connected && !doorOpen){
               door.open();
               doorOpen = true;
               clearInterval(timer);
             }     

          }, 30000);

      }

      collar.onDisconnect = function(data){

          clearInterval(timer);

          connected = false;
     
          if(doorOpen) {
             door.close();
             doorOpen = false;

          }

      }
  }
}

// start the program
app.init();

Note: I'm assuming “DoogyDoor” and “CollarListener” are actual programs in the real world. THEY ARE NOT! They're fake examples ☺

This example code is not very test friendly due to the fact that there is no separation of responsibilities in the code. Developers would fix this with object orientation and software design principles. However, that is not how hackers think. Hackers implement the code and hope (sometimes know) it will work. Once it’s coded, they run it. If it breaks, they make needed changes and run it again. The problem is that this hacker approach doesn’t work for team based development or enterprise software development. The solution is to think about the tasks that your code needs to perform and isolate each task in a function. This is the fastest way to write testable code. Let’s rework the above example to make it “testable.”

The Structured Code


// JavaScript Code
var connected = false;
var doorOpen = false;
var timer = false;

// assume these are real things - NOT REAL! :)
var door = new DoggyDoor();
var collar = new CollarListener();

var app = {
   init:function(){
      collar.onConnect = app.handleCollarConnect;
      collar.onDisconnect = app.handleCollarDisconnect;
   },
   handleCollarConnect:function(data){
      connected = true;
      manageTimer();
   },
   handleCollarDisconnect:function() {
      connected = false;
      openCloseDoor();
      clearInterval(timer); // manually clear timer
      timer = false;
   },
   onTimerTick:function(evt) {  
      openCloseDoor();
      manageTimer();
   },
   manageTimer:function() {
      if(!timer){
           timer = setInterval(onTimerTick, 30000);
      }else{
           clearInterval(timer);
      }
   },
   openCloseDoor:function(){
      if(connected && !doorOpen){
         door.open();
         doorOpen = true;
      }else{
         door.close();
         doorOpen = false;
      }
   }
}

// start the program
app.init();

Now that we have this well structured code (which could be improved), it is easy for us to write tests for it! It’s also a lot easier to read.

describe("The app", function() {

   // test that it returns true
   it(" connects to a collar and waits 30 seconds to open the door.", function() {
     app.handleCollarConnect();
     setInterval(function(){
        expect(app.connected).toBe(true);
        expect(app.doorOpen).toBe(true);
     }, 30000)
   }

   // test that it returns false
   it(" clears the timer and closes the door on collar disconnect", function() {
      app.handleCollarDisconnect();
      expect(app.connected).toBe(false)
      expect(app.doorOpen).toBe(false);
   }
   
   it(" can close the open door", function() {
      app.door.open();
      app.doorOpen = true;
      app.connected = false;
      app.openCloseDoor()  
      expect(app.doorOpen).toBe(false);
   }
   
   it(" can open the closed door", function() {
      app.door.close();
      app.doorOpen = false;
      app.connected = true;
      app.openCloseDoor();
      expect(app.doorOpen).toBe(true);
   }

}

The tests thoroughly describe our program and the program is easy to read. This code would be easy to build on or refactor into a more robust framework. It is a great start to building better software.

Why Test Driven Development?

Code written by junior developers usually isn’t team friendly or suitable for enterprise grade software unless it’s structured and tested.
In its most basic form the concept is simple:

Separate your code into functions that are responsible for single tasks and try not to repeat yourself.

You should also try to write tests before writing code or, at the very least, deliver tested code.

Conclusion

As the industry grows, we are seeing more and more hacker-type developers from other industries. If these developers adopt test driven development, they will have more opportunities and create better products.

If you would like to learn more about TDD in other languages, read How to (Finally) Learn Test-Driven Development for Ruby on Rails and Unit Testing and TDD in Node.js - Part 1 and Unit Testing and TDD in Node.js - Part 2 for Node.js.

You can also check out a live example on Github!

The live example is built as a Node.js module and the DoggyDoor and Collar objects are simple stubs. It uses Nodeunit framework for test execution. It is a completely different syntax than the above example, but the structure is the same and all the tests pass.

Discover and read more posts from Aaron Gerard Franco
get started
Enjoy this post?

Leave a like and comment for Aaron

7
Understanding Hoisting in JavaScript
Getting Started With React Redux: An Intro
The JS ecosystem is exploding