Blog

08
January 2014

Gavin Pickin

Unit Testing 04 - Testing with Dependencies

Unit Testing

Welcome back to our Getting Started with Unit Testing - Unit Testing for Dummies you might say, and we're onto our fourth post. We have covered setting up our ColdFusion project (with files on Github) and added TestBox as our Testing Framework. We wrote a couple of TDD tests for our objects, and then wrote the objects to pass them.  Then we did some more TDD by writing a more complex test with 10+ assertions, and walked through building our object function to pass the test

Today we're going to look at methods / functions which have dependencies on other objects. Up to this point we have passed information into our methods / functions, which means it is easy to control the test of those methods / functions. Remember that one of the keys to Unit Testing is that we want to isolate each UNIT of code so it is independent of others Units. This obviously becomes more difficult when the code itself is obviously dependent on other components / objects, so we'll look at how to deal with that.

Step 1 - Create a test for a New Dependent Function 

In our application, our Website object is going to have several functions that rely on other functions within itself, these are dependencies. One function will be addDomainName(website_id). This will add a Domain Name to a Website… but it will validate it first. We will use our isValidDomainName() function we used before in this Object, and another, WebsiteDAO.addDomainName(website_id) which will perform the actual Database Activity. 

So, first lets make our new test.

function testSaveDomainNameSuccess() {
    $assert.assert( Website.saveDomainName(domainName) == true, "True was expected, but False was returned - Means there was an Invalid Domain Name Given");
}

This is going to test that our function saveDomainName exists, and that it accepts our domainName object, and when it saves, it returns true.
So lets create our domainName struct we're going to pass.

var domainName = {};
domainName.domainid = 3;
domainName.url = "www.mydomainname.com";
domainName.websiteid = 5;

 

We create a struct, and give it a domainid, url, and a websiteid. And then we pass that into our function.
Lets save, and run our tests, and make sure it fails… and it does as expected… as the saveDomainName function is not created yet.

Step 2 - Create our saveDomainName function to satisfy the test

Lets create our function, saveDomainName, and we'll build it to pass the test. We'll start small, and we'll iterate to make it do all we need it to do. To start, we'll just return true or false, based on if the domainName.url is valid or not, by calling our method in our previous post.

blog04/Step2/model/Website.cfc

function saveDomainName(domainName) {
    return isValidDomainName(domainName.url);
}

 

Now, if we run the test, the test passes, because the domainName struct we passed through had a valid domain. 
Lets update our test to pass an invalid Domain name

blog04/Step2/test/unit/Website.cfc

function testSaveDomainNameSuccess() {
    var domainName = {};
    domainName.domainid = 3;
    domainName.url = "www.2mydomainname.com";
    domainName.websiteid = 5;
    $assert.assert( Website.saveDomainName(domainName) == true, "True was expected, but False was returned - Means there was an Invalid Domain Name Given");
}

 

Now, if we run our tests, our tests fail… which is as expected… although, this works, this defeats the purpose of Unit Testing, because we want to test a Unit of code, which is just our saveDomainName() code, we don't want to retest our isValidDomainName() function, over and over again. 

Step 3 - How do we Resolve our Dependencies

So, how do we solve this?

This is where Mocking comes in. Mocking is where we create fake / mocks of our methods, and we trick our CUT or component under test, to call our fake method, and not the real method. This allows us to control our tests, and remove those dependencies.

To use Mocking, while using TestBox, is really easy. TestBox ships with MockBox included, because these two *Box products go hand in hand. First, in our beforetests() function, we need to create an Instance of MockBox to use in our tests, so lets update our beforetests() function in the blog04/Step3/test/unit/Website.cfc file

function beforeTests() {
    variables.mockbox = new testbox.system.testing.Mockbox();
    variables.Website = new blog04.Step3.model.Website().init();
}

 

As you can see, we added a variables.mockbox which is a new object, and the path is text box.system.testing.Mockbox. If you are using Coldbox it is coldbox.system.testing.Mockbox. I also noticed my bad form, and added variables. to the Website variable, to be consistent.

Now, before all the tests, we create a MockBox instance, and a Website Instance, and we're good to go. Next step is, update our testSaveDomainNameSuccess() test to make our CUT component under test to call our Mocked method isValidDomainName() instead of the real one.

First thing we have to do, is turn our Website component into a Website component with all the magic Mockbox functions we need, to Mock what we need to Mock. You could do it without this step, but you would have to inject methods, and properties into the component yourself, but that is what Mockbox does so well, so we'll just use Mockbox the way it should be used (as far as I am aware, I'm not an expect, so correct me if I'm wrong of course).

variables.mockbox.prepareMock(Website);

 

This calls our Mockbox instance, and passes our instance of Website to the prepareMock(). Now our Website instance has all of the magic methods, including the one we will be using, the .$() method. 
We're going to call the $() method, and pass a couple of params. 

method - the name of the method we are going to mock, in this case, isValidDomainName
returns - the result of calling this method, in this case, true because we want our test to pass, the domain name itself is valid for all we care

That looks like this.

Website.$(method="isValidDomainName", returns=true);

 

2 lines, and we have MockBox'ed our Website, and added the mocked method, and no matter what our CUT component under test passes that function, it will return true.

Lets save it, and run our tests, and see if our testSaveDomainNameSuccess test passes.

Yes, it passed… but if you look, you might have noticed, another test now fails.
The testValidDomainName fails, because one of our assertions, which should return false actually returns true, why? 

Our Mocked Method is still being used, instead of the real method for that test.

So we can use the setup() and teardown() methods, if we want to create a new copy of the Website for each and every test, but, since we're not always going to be mocking it, I'm going to just reset the variables.Website instance, by creating a new instance and overwriting it.

// Reset variables.Website so there are no mocked methods for other tests
variables.Website = new blog04.Step3.model.Website().init();

 

This means when we're done with this test, at the end, we save a new instance in the Website variable, and all future tests will run like they should. 
Save, and run tests, and they all pass, as expected.

Today we have dealt with internal dependencies, and to be honest, I think they are the hardest, since you have to worry about resetting the instance because as you mock methods, you overwrite the originals, with external dependencies, you can mock them, and they're done.

We'll build on our saveDomainName() function more in the next post, as we build out the method to actually deal with checking for an existing Domain Name, and inserting or updating based on that.

Thanks for reading, hope this helps, and as usual, feedback is appreciated, because we are always learning.

Gavin

Blog Search