12/03/2012

Building a Unit Test in Jasmine, Part 3

We created a Jasmine unit test example in the first two parts of this series that started with a statement and then abstracted out to an object.  This time we will expand the object to be more robust.  Most importantly, we are not changing the tests currently in place.  

The JasmineTester is going to be updated to accept a random number of values.  The first step is to create a unit test to describe this.  The number of properties being added is upped to 4.  The new test will check to make sure the new object constructor still returns an object by using the toBeDefined matcher.


    //Check for expanded options object
    it('Increase number of values to 4'function({
        var testObject new JasmineTester({
            a2,
            b2,
            c2,
            d2
        });

        expect(testObject).toBeDefined();
    });

The test passes. testObject should now have 4 properties so let's add a new expect to check the number of properties.
        expect(Object.keys(testObject).length).toEqual(4);

This test fails: 
        Expected 2 to equal 4.

It's time to update the constructor to use a random number of properties.  
        for (in opts{
            if (opts[o]{
                this[oopts[o];
            }
        }

The constructor is changed to copy all properties from the passed object to itself.  This is fairly simple and allows the JasmineTester to accept more than numbers.  We run the tests and our new test pass.  However, our old tests fail.  The old tests should not be changed. It's time to take a look at the total function. 

The total function is looking for number1 and number2.  We can no longer guarantee that those properties will exist.  The total function needs to be updated to use all number properties on the object regardless of name.  The total is changed to a loop that adds all properties to a running total.
 for (num in this{
                total += this[num];
        }

Unfortunately, our tests still fail because properties from the prototype are also getting added in.  We need to make sure the loop only uses this object's own propertes, so the code is changed to:
 for (num in this{
              if (this.hasOwnProperty(num){
                total += this[num];
            }
        }


Now everything passes, so we can move on.  Our new sets of tests are checking for existence and property length, but they are not using the total function yet.  We add one more expect function to our new set of tests:
        expect(testObject.total()).toEqual(8);

The test passes, and everything is looking good.  

A quick look at the code shows a new possibility we did not need to check for before.    The JasmineTester can take any property type now, so what happens if any of the properties are not numbers?  A new test is added to check if the same result is returned when a string is passed in as a property.  
    //Make sure object doesn't include strings in total
    it('Successful total with a string value'function({
        var testObject new JasmineTester({
            a2,
            b'2'
        });

        expect(testObject.total()).toEqual(2);
    });

This test fails.  
        Expected 22 to equal 4.

The total function needs a little more work.  The string '2' is being concatenated to the running total instead of being added to it.  Adding a typeof check will ensure that the property is a valid 'number' that can be used in a computation.  Now the validation test passes.  

Even though the validation passes, it would be good to expand the check.  Let's ensure that the total function won't break if other types are also sent in.  The string unit test can be changed to be a generic validation check. The code can be modified to reuse one of the properties of the testObject multiple times and then send the object back for testing.
//Make sure the total function doesn't include/break with non-numbers 
    it('Successful total with non-number values'function({
        var testObject new JasmineTester({
            a2,
            b'2'
        });
        expect(testObject.total()).toEqual(2);
        
        testObject.function (){};
        expect(testObject.total()).toEqual(2);
        
        testObject.{b:2};
        expect(testObject.total()).toEqual(2);        
    });

We run all of our tests in the suite and they all pass.  Our new changes are complete.

The JasmineTester has now been expanded to be more robust and our total function is smarter than before.  There are two critical points here:
  • The old tests still pass.  Any code that would have been written previously is still working with our new changes.  
  • Our process has also changed.  Changes to our object started by being described in the unit test.  The object was expanded after the test was written.  Our development was being guided towards a demonstrable end point that showed success.

The next post will finish this series by adding a second object to the mix.