Accessibility

Table of Contents

Test Driven Development with ColdFusion – Part 2: Designing components for easy testability

Decoupling dependencies and using mocks

In unit test parlance, a mock is a test-time stand-in for the real dependency. Mocks can be full components or single functions. Traditionally, mocks are created in two ways:

  • Writing a component that represents the real object; its functions return dummy data.
  • Using a mock framework to create those dummy objects and functions. Excellent options exist for ColdFusion developers, including ColdMock, CFEasyMock, and MXUnit's own MightyMock (which will be released shortly after this article's publication).

In addition, MXUnit provides easy, powerful mocking functionality in the form of two functions: injectMethod() and injectProperty(). This article will use MXUnit's built-in mocking capabilities.

Beginning with the end in mind

The goal is to test your implementation of the Twitter API, without frequently hitting Twitter's servers. You'll do this by going through several iterations of the code, refactoring to get closer to this goal. Remember: baby steps. Understand that Twitter provides dozens of API calls; continuing down the path begun in Part 1 would have ended in dozens of dependencies. By the end of this article, you'll create only a single, mockable dependency, with corresponding unit tests for your execution of Twitter's API. You'll do this by first breaking down the functionality found in the current API methods into several smaller, isolated, single-responsibility methods. Then, in the unit tests, you'll create mock functions that spoof the actual HTTP call to Twitter. You'll learn how to use MXUnit's built-in mock functionality to simulate different conditions, all within the same test case. Along the way, you'll focus on answering the questions that testing should answer posed earlier.

Strategy #1: Extract Method

In Extract Method refactoring, you pull out a chunk of code from one function and create a new function comprising the extracted chunk of code. Its popularity in the Java world is so great that IDEs such as Eclipse offer built-in support for Extract Method refactoring. Although current IDEs for ColdFusion don't offer such functionality, the concept is so simple that you can easily make do with cut-and-paste.

As noted earlier, designing with testing in mind can lead to increased opportunities for code reuse. When looking at the verifyCredentials() and friendsTimeline() functions, you see nearly redundant constructs in the HTTP call and deserialization. Duplicate code is a prime candidate for refactoring. Here's a first stab, in which the redundant code is extracted to a new method named callTwitter():

<cffunction name="callTwitter" access="private" returntype="any">
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfset var response = "" >
     <cfhttp url="#getTwitterUrl()#/#location#.#getFormat()#"              
        method="get"
        username="#getUserName()#"         
        password="#getPassword()#">                             
     <cfset response = deserializeJSON(cfhttp.FileContent)>
                             
     <cfreturn response>
</cffunction>
 
<cffunction name="friendsTimeline" hint="returns the authenticated user's friends timeline">
     <cfreturn callTwitter(location="statuses/friends_timeline")>
</cffunction>

The unit test still looks the same, and it still passes:

<cffunction name="twitterFriendsTimelineShouldReturn20Items">
     <cfset var results = twitter.friendsTimeline()>
     <cfset debug(results)>
     <cfset assertEquals(20, arrayLen(results), "Something other than 20 items were returned.") />
</cffunction>

With that small win under your belt, you can now refactor verifyCredentials() to use the new callTwitter() method:

<cffunction name="verifyCredentials" hint="Tests that the credentials are valid.">
     <cfset var response = callTwitter(location="account/verify_credentials") >
     <cfif not structKeyExists(response,'id')>
          <cfthrow type="TwitterAuthenticationFailure" message="Could not log into Twitter."
          detail="Tried user:#getUserName()# pwd:#getPassword()#">
     </cfif>
                             
     <cfreturn true />
</cffunction>

Run its existing tests:

<cffunction name="theTwitterAccountShouldBeValid">
     <cfset var actual = twitter.verifyCredentials() />
     <cfset assertTrue(actual)>
</cffunction>
 
<cffunction name="invalidCredentialsShouldThrowTwitterAuthenticationFailure" mxunit:expectedException="TwitterAuthenticationFailure">
     <cfset twitter.init('Kwai Chang Caine','Grasshopper')>
     <cfset twitter.verifyCredentials() />
</cffunction>

As expected, they pass.

Extracting out the HTTP call and response deserialization should significantly improve your ability to add new API calls into the client; however, it hasn't yet made testing any easier. Baby steps.

Also, the first implementation of callTwitter() doesn't provide for passing any arguments to be passed to the Twitter service. Most of Twitter's API allows for optional arguments to be passed in the URL, so the implementation must also support that. Allowing for a structure of arguments is a flexible way to approach this. Finally, Twitter provides return types other than JSON (for example, XML), and callTwitter() should at least provide a hook for using them. Here's a second refactoring:

<cffunction name="callTwitter" access="private" returntype="any">
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfargument name="apiArgs" type="struct" required="false" hint="any args to be passed to twitter" default="#StructNew()#"/>
     <cfset var response = "" >
     <cfset var urlString = "?">
     <cfset var key = "">
     <cfif not StructIsEmpty(arguments.apiArgs)>
          <cfloop collection="#apiArgs#" item="key">
               <cfset urlString = listAppend(urlString,"#key#=#apiArgs[key]#","&")>
          </cfloop>                   
     </cfif>
                             
     <cfhttp url="#getTwitterUrl()#/#location#.#getFormat()##urlString#"                       
          method="get"
          username="#getUserName()#"
          password="#getPassword()#">                   
     <cfif getFormat() eq "JSON">
          <cfset response = deserializeJSON(arguments.httpContent)>
     <cfelse>
          <cfthrow message="Currently there is no deserializer for #getFormat()#">
     </cfif>
     <cfreturn response>
</cffunction>

First run the existing tests to ensure these changes didn't break anything. They pass. Excellent. Baby Steps.

Strategy #2: One more abstraction layer

The Extract Method strategy works great for pulling related lines of code into one reusable method. Often, however, the dependency causing the pain might be a single tag or function call. In the case of TwitterClient.cfc, the pain point is cfhttp. When you extract that single tag into a separate function (by abstracting cfhttp), you make it possible to mock it in your tests.

The tests are still communicating with Twitter on every call, and that needs to change. In fact, when you look at the new callTwitter() function, you see that it's doing three separate steps:

  1. Convert the arguments into a query string.
  2. Make the HTTP call.
  3. Convert the response and return it.

As you march toward being able to mock the HTTP call to Twitter, you'll continue to use the Extract Method approach to refactor your code while using the existing unit tests as the barometer of health.

Here's the third refactoring attempt, with each of the three steps broken down into a function with a single responsibility:

<cffunction name="callTwitter" access="private" returntype="any">               
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfargument name="apiArgs" type="struct" required="false" hint="any args to be passed to twitter" default="#StructNew()#"/>
     <cfset var urlString = structToQueryString(apiArgs=arguments.apiArgs)>
     <cfset var httpContent = doHttpCall(location=arguments.location,urlString=urlString)>
     <cfset var response = deserializeResponse(httpContent) >
     <cfreturn response>
</cffunction>
 
<cffunction name="structToQueryString" access="private" hint="converts the struct of api args to a query string" returntype="string">
     <cfargument name="apiArgs" type="struct" required="false" hint="any args to be passed to twitter"/>
     <cfset var urlString = "?">
     <cfset var key = "">
     <cfif not StructIsEmpty(arguments.apiArgs)>
          <cfloop collection="#apiArgs#" item="key">
               <cfset urlString = listAppend(urlString,"#key#=#apiArgs[key]#","&")>
          </cfloop>
     </cfif>     
     <cfreturn urlString>
</cffunction>
 
<cffunction name="doHttpCall" access="private" hint="wrapper around the http call" returntype="any">
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfargument name="urlString" type="string" required="false" hint="the query string to pass" default="">
     <cfset var httpresponse = "">
     <cfhttp url="#getTwitterUrl()#/#location#.#getFormat()##urlString#"                 
        method="get"

        username="#getUserName()#"                                   
        password="#getPassword()#"                        
        result="httpresponse">

     <cfreturn cfhttp.FileContent>
</cffunction>
 
<cffunction name="deserializeResponse" access="private" hint="deserializes the http response into CFML data">
     <cfargument name="httpContent" type="string" required="true" hint="the http content to be deserialized">
     <cfif getFormat() eq "JSON">
          <cfreturn deserializeJSON(arguments.httpContent)>
     <cfelse>
          <cfthrow message="Currently there is no deserializer for #getFormat()#">
     </cfif>
</cffunction>

Just as with the first refactoring, these changes don't provide any advantages with respect to runtime behavior. However, the changes are significant. First, callTwitter() is much easier to read. Second, the three new private functions are now reusable for when you need to do more than simple GET and POST operations (for example, when you want to provide functionality for uploading a new picture to a user's profile, which callTwitter()will not support). Most importantly, this change will greatly simplify component testing. Whereas previously the component would have contained a cfhttp call inside every function accessing the Twitter service, now the component has extracted it down into a single function call that does nothing else. This may seem strange. Why write a wrapper function around a single-line call to cfhttp?

This is strategy #2: one more abstraction layer. This is the critical turning point for transforming your code from being untestable to easily testable. Testability is about making dependencies easier to contend with at test time. And this approach of writing small wrappers around dependency-heavy functionality like HTTP calls greatly increases your ability to mock those behaviors during unit testing.

Prior to this last refactoring, it would have been virtually impossible to mock the call to Twitter because the cfhttp call was buried down in the middle of the code. Now that it's in its own single-responsibility function, you can mock it repeatedly in tests and test just what you need to. Brace yourself; this is where the unit testing magic happens.

First, write a new, private function inside the TwitterClientTest.cfc test case that returns a simple JSON string with an id key:

<!--- mocks! --->
<cffunction name="verifyCredentialsHttpMock" access="private">          
     <cfreturn '{"id":"1"}'>
</cffunction>

Modify the existing theTwitterAccountShouldBeValid() function, like so:

<cffunction name="theTwitterAccountShouldBeValid">
     <cfset injectMethod(twitter,this,"verifyCredentialsHttpMock","doHTTPCall")>
     <cfset assertTrue(twitter.verifyCredentials(), "Credentials should have validated but did not")>
</cffunction>

The function now uses MXUnit's injectMethod() function to override, at test time, TwitterClient.cfc's doHTTPCall() function with a mock function named verifyCredentialsHttpMock(), which exists in the test case itself (that is, the this object). Thus, when TwitterClient's callTwitter() function executes doHTTPCall(), it will actually run the mock function that has been injected! In this case, the mock function returns a JSON string that contains the id key, which is used by the logic in the verifyCredentials()function.

Note: Inside the test, the mock functions use access="private". MXUnit treats all public functions as tests. By making the mock functions private, MXUnit will ignore them when deciding what functions to treat as tests.

This is a good start. It is now time to apply the same approach to the test for invalid credentials. First, add the mock function to return a JSON string that doesn't contain the id key:

<cffunction name="invalidCredentialsHTTPMock" access="private">
     <cfreturn '{"error":"could not authenticate you"}'>
</cffunction>

Then, in the test, override doHTTPCall() with this new mock:

<cffunction name="invalidCredentialsShouldThrowTwitterAuthenticationFailure"
mxunit:expectedException="TwitterAuthenticationFailure">
     <cfset injectMethod(twitter, this, "invalidCredentialsHTTPMock", "doHTTPCall")>
     <cfset twitter.verifyCredentials() />
</cffunction>

Green!

The tests now no longer rely on Twitter availability, yet they still test the important logic. Specifically, when the id key is returned in the HTTP call, then the credentials are valid.

Turning to the test for friendsTimeline(), the simplest way to spoof the HTTP call is to write a mock function that would return a saved version of a real Twitter response. To do this, simply open a browser and go to http://twitter.com/statuses/friends_timeline.json. When prompted, type in your credentials. Save the file as friends_timeline.json in the same directory as your TwitterClientTest.cfc. (An example is included with this article's sample files.) Note that you have to do this only once. Now that you're all set up, you can write your mock:

<cffunction name="friendsTimelineHttpMock" access="private">
     <cfset var filepath = getDirectoryFromPath(getCurrentTemplatePath()) & "/friends_timeline.json">
     <cfreturn fileRead(filePath)>
</cffunction>

And then modify the test to use the mock function:

<cffunction name="twitterFriendsTimelineShouldReturn20Items">
     <cfset injectMethod(twitter,this,"friendsTimelineHttpMock","doHTTPCall")>
     <cfset results = twitter.friendsTimeline()>
     <cfset debug(results)>
     <cfset assertEquals(20, arrayLen(results), "Something other than 20 items were returned.") />
</cffunction>

Green! The deserialization works fine (remember, you're testing against actual Twitter JSON output), and the test no longer hits the Twitter servers.

Still, something feels wrong. In the first two tests, the authenticated-or-not logic is tested; in this last test, the deserialization works. But the first two questions the tests should answer are still unanswered: "Does the function execute the correct URL?" and "Does the function use the correct HTTP request method?" Looking at the current implementation, no obvious method exists for accessing this information. In addition, since functions such as friendsTimeline() return the deserialized data and nothing else, getting at useful error and debugging information is going to prove elusive.

This is test-driven development! This is your tests talking to you, telling you that design problems exist. Imagine someone trying to use your TwitterClient in their production code, in its current implementation. They use friendsTimeline(), and something unexpected happens. They need a way to get at the details, but there isn't a way. If you need this information in your tests, chances are they'll need it in their production application.

Since you've been coding in baby steps, you're in a great position to modify the existing implementation without much rework. Good thing you didn't go wild and write functions for every Twitter API call already! The return from callTwitter() is the problem; instead of returning the deserialized data, it needs to return something more useful. It could return a structure that would include the information needed, but it would be better to return an object with a clear API for retrieving information. Start with a new component, TwitterResponse.cfc. At first, it will contain some generic plumbing code and eventually specific setters and getters for all the data you want to make available:

<cfcomponent hint="A container for all the useful information we want to make available for Twitter API calls">
     <cfset variables.instance = StructNew()>
     <cffunction name="set" access="private" hint="generic setter">
          <cfargument name="name" required="true" hint="data name" type="string">
          <cfargument name="value" required="true" hint="data value" type="any">
          <cfset variables.instance[arguments.name] = arguments.value>
          <cfreturn this>                        
     </cffunction>
                             
     <cffunction name="get" access="private" hint="generic getter" returntype="any">
          <cfargument name="name" required="true" hint="data name" type="string">
          <cfreturn variables.instance[arguments.name]>
     </cffunction>
                                                      
     <cffunction name="setDeserializedData" access="package" hint="sets the deserialized data from the http call to Twitter">
          <cfargument name="data" required="true" type="any">
          <cfreturn set("deserializeddata",data)>                        
     </cffunction>
                             
     <cffunction name="getDeserializedData" access="public" hint="returns the deserialized data from the http call to Twitter" returntype="any">
          <cfreturn get("deserializeddata")>
     </cffunction>
 
</cfcomponent>

Note that the setter functions return this, so that you can chain methods easily.

Now, refactor TwitterClient.cfc's callTwitter() function:

<cffunction name="callTwitter" access="private" returntype="TwitterResponse">
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfargument name="apiArgs" type="struct" required="false" hint="any args to be passed to twitter" default="#StructNew()#"/>
     <cfset var urlString = structToQueryString(apiArgs=arguments.apiArgs)>
     <cfset var httpContent = doHttpCall(location=arguments.location,urlString=urlString)>
     <cfset var response = deserializeResponse(httpContent) >
     <cfreturn createObject("component","TwitterResponse")
     .setDeserializedData(response)>
</cffunction>

Next, refactor TwitterClientTest.cfc's twitterFriendsShouldReturn20Items() function:

<cffunction name="twitterFriendsTimelineShouldReturn20Items">
     <cfset injectMethod(twitter,this,"friendsTimelineHttpMock","doHTTPCall")>
     <cfset twitterresponse = twitter.friendsTimeline()>                             
     <cfset assertEquals(20, arrayLen(twitterresponse.getDeserializedData()),
         "Something other than 20 items were returned.") />
</cffunction>

Green! That was easy. Now, supplement the test to get answers for the first two questions (Does each function execute the correct URL? Does each function perform the correct HTTP request method (GET vs. POST)?) .

<cffunction name="twitterFriendsTimelineShouldReturn20Items">
     <cfset injectMethod(twitter,this,"friendsTimelineHttpMock","doHTTPCall")>
     <cfset twitterresponse = twitter.friendsTimeline()>
                             
     <cfset assertEquals(20, arrayLen(twitterresponse.getDeserializedData()), "Something other than 20 items were returned.") />
                             
     <cfset assertEquals("get",twitterresponse.getHttpRequestMethod())>                             
     <cfset assertEquals("statuses/friends_timeline", twitterresponse.getURL())>
</cffunction>

Run the test.

Red!

You now have a failing test, indicating the getHttpRequestMethod() function was not found. Add getter and setter methods to TwitterResponse.cfc (see the sample file for the final code)

Now, call the setter in TwitterClient.cfc:

<cfreturn createObject("component","TwitterResponse")
.setDeserializedData(response)
.setHttpRequestMethod(???)>

Uh oh. What should be used as the argument for setHttpRequestMethod()? It's obvious now that the code does not provide a way to control the request method. In fact, it's hard coded as get in the cfhttp call in doHttpCall(). Here is how to fix it:

<cffunction name="doHttpCall" access="private" hint="wrapper around the http call" returntype="any">
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfargument name="requestMethod" type="string" required="false" hint="the http request method. use 'get' or 'post'" default="get">
     <cfargument name="urlString" type="string" required="false" hint="the query string to pass" default="">
     <cfset var httpresponse = ""> 
     <cfhttp url="#getTwitterUrl()#/#location#.#getFormat()##urlString#"
          method="#arguments.requestMethod#"
          username="#getUserName()#"
          password="#getPassword()#"
          result="httpresponse">
     <cfreturn httpresponse.fileContent>
</cffunction>

Now, refactor callTwitter():

<cffunction name="callTwitter" access="private" returntype="TwitterResponse">
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfargument name="requestMethod" type="string" required="false" hint="the http request method. use 'get' or 'post'" default="get">
     <cfargument name="apiArgs" type="struct" required="false" hint="any args to be passed to twitter" default="#StructNew()#"/>
     <cfset var urlString = structToQueryString(apiArgs=arguments.apiArgs)>
     <cfset var httpContent = doHttpCall(location=arguments.location,requestMethod=arguments.requestMethod,urlString=urlString)>                             

     <cfset var response = deserializeResponse(httpContent) >
     <cfreturn createObject("component","TwitterResponse")
     .setDeserializedData(response)
     .setHttpRequestMethod(arguments.requestMethod)>
</cffunction>

When you re-run the friends timeline test, it's still Red. This time, the method getURL(), which you used in the final assertEquals(…) call, was not found. This is great! It means your prior refactorings were successful. More baby steps. In TwitterResponse.cfc, add getUrl() and setURL(arguments.location) functions into TwitterClient.cfc (see the sample file for the final code)

Re-run the test… Green! This is shaping up quite nicely. It's becoming easy to add and access information relevant to the Twitter calls. Now is a good time to re-run the other tests.

On theTwitterAccountShouldBeValid() test, Red! Taking a look at verifyCredentials(), it is plain that the code wasn't refactored to deal with the new return of TwitterResponse. It is still trying to work with a struct return in this line:

<cfif not structKeyExists(response,'id')>

To refactor, replace it with:

<cfif not structKeyExists(response.getDeserializedData(),'id')>

Green! Ah, that's better. Red, Green, Refactor.

The first three questions are answered for the existing tests. However, the component won't provide the error/debugging information necessary to help you quickly assess problems when they arise. Basically, you want easy access to the entire cfhttp structure. Adding a test for the case when Twitter is unresponsive seems appropriate now. In TwitterClientTest.cfc, add a new function:

<cffunction name="whenTwitterIsUnresponsiveRawResponseDataShouldBeAvailable">
     <cfset twitterresponse = twitter.friendsTimeline()>
     <cfset rawdata = twitterresponse.getRawResponseData()>
     <cfset assertTrue( StructKeyExists(rawdata,"StatusCode") ,"StatusCode should exist in raw data" )>
     <cfset assertTrue( StructKeyExists(rawdata,"ErrorDetail") ,"ErrorDetail should exist in raw data" )>
     <cfset assertTrue( StructKeyExists(rawdata,"FileContent") ,"FileContent should exist in raw data" )>
     <cfset assertEquals("",twitterresponse.getDeserializedData(),"When an error occurs, there should be no deserialized data")>
</cffunction>

Naturally, this fails since getRawResponseData() hasn't been written yet. Add that into TwitterResponse.cfc.

Looking at TwitterClient.cfc, you see that doHTTPCall() returns just the CFHTTP.FileContent variable, not the entire cfhttp struct. Because doHttpCall() is private, you can change this without affecting any external code by returning cfhttp instead of cfhttp.FileContent. Then, modify callTwitter() accordingly:

<cffunction name="doHttpCall" access="private" hint="wrapper around the http call" returntype="struct">
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfargument name="requestMethod" type="string" required="false" hint="the http request method. use 'get' or 'post'" default="get">
     <cfargument name="urlString" type="string" required="false" hint="the query string to pass" default="">
     <cfset var httpresponse = "">
     <cfhttp url="#getTwitterUrl()#/#location#.#getFormat()##urlString#"
          method="#arguments.requestMethod#"
          username="#getUserName()#"
          password="#getPassword()#"
          result="httpresponse">       
     <cfreturn httpresponse>
</cffunction>
 
<cffunction name="callTwitter" access="private" returntype="TwitterResponse">
     <cfargument name="location" type="string" required="true" hint="the twitter api location, such as 'statuses/friends_timeline'"/>
     <cfargument name="requestMethod" type="string" required="false" hint="the http request method. use 'get' or 'post'" default="get">
     <cfargument name="apiArgs" type="struct" required="false" hint="any args to be passed to twitter" default="#StructNew()#"/>
     <cfset var urlString = structToQueryString(apiArgs=arguments.apiArgs)>
     <cfset var httpResponse = doHttpCall(location=arguments.location,requestMethod=arguments.requestMethod,urlString=urlString)><cfset var response = deserializeResponse(httpResponse.FileContent) >
     <cfreturn createObject("component","TwitterResponse")
     .setDeserializedData(response)
     .setHttpRequestMethod(arguments.requestMethod)
     .setURL(arguments.location)
     .setRawResponseData(httpResponse)>
</cffunction>

Still red! But this time, for a different reason. The cfhttp data structure is now available, but the last assertion indicates what should be obvious: that the test is not actually spoofing an unresponsive or broken URL. This is easy to mock. In TwitterClientTest.cfc, add the following mock function that returns a simple spoof of the cfhttp structure:

<cffunction name="TwitterIsDownMock" access="private">
     <cfset var cfhttp = {filecontent="connection failure",statuscode=" Connection Failure. Status code unavailable.", errordetail="FailWhale!"}>
     <cfreturn cfhttp>
</cffunction>

And now use that mock in the test:

<cffunction name="whenTwitterIsUnresponsiveRawResponseDataShouldBeAvailable">
     <cfset injectMethod(twitter,this,"TwitterIsDownMock","doHTTPCall")>
     <cfset twitterresponse = twitter.friendsTimeline()>
     <cfset rawdata = twitterresponse.getRawResponseData()>
     <cfset assertTrue( StructKeyExists(rawdata,"StatusCode") ,"StatusCode should exist in raw data" )>
     <cfset assertTrue( StructKeyExists(rawdata,"ErrorDetail") ,"ErrorDetail should exist in raw data" )>
     <cfset assertTrue( StructKeyExists(rawdata,"FileContent") ,"FileContent should exist in raw data" )>
     <cfset assertEquals("",twitterresponse.getDeserializedData(),"When an error occurs, there should be no deserialized data")>
</cffunction>

The test results now show: "JSON parsing failure: Unexpected end of JSON string". This reveals a bug in TwitterClient's deserializeResponse() function. Looking at that code, it expects a well-formed JSON string. However, in the event of an error, unresponsive URL, or some other failure, that's not likely to be the case. Refactor deserializeResponse():

<cffunction name="deserializeResponse" access="private" hint="deserializes the http response into CFML data">
     <cfargument name="httpContent" type="string" required="true" hint="the http content to be deserialized">
     <cfif getFormat() eq "JSON">
          <cfif isJSON(arguments.httpContent)>
               <cfreturn deserializeJSON(arguments.httpContent)>
          <cfelse>
               <cfreturn "">
          </cfif>
   <cfelse>
      <cfthrow message="Currently there is no deserializer for #getFormat()#">
   </cfif>
</cffunction>

And now the test is Green. By refactoring and adding a single test, you've made TwitterClient more robust and developer-friendly, and you've fixed a bug.

Now that your new test passes, it's a good time to run the existing tests to ensure nothing else broke. Running them, you get some errors indicating that the doHttpCall() function isn't returning a structure. This is happening in the tests that use mocks, so go back and change those mocks to return a struct (see the sample files for the complete code).

And now all tests pass. By now, you should feel good about the questions raised earlier: The main dependency is simple to mock and the code provides easy access to the information you need for testing and debugging. You know exactly how the code will behave when calls to Twitter's API are unsuccessful. Adding functions to TwitterClient for exposing more of the Twitter API will be trivial, often as little as a single function call will be needed. Finally, you're now testing your code, not Twitter's. Compared with the initial TwitterClient.cfc implementation, this current version is very testable.

In addition, it is easy to use in real applications. For example:

<cfset twitter = createObject("component","TwitterClient").init(uname,pwd,"json")>
<cfset response = twitter.friendsTimeline()>
<cfset data = response.getDeserializedData()>
<cfloop from="1" to="#ArrayLen(data)#" index="tweet">
     <cfoutput>
          <img src="#data[tweet].user.profile_image_url#"> #data[tweet].text#<br>
          #data[tweet].created_at#<br><br>                             
    </cfoutput>
</cfloop>