Bill Shelton
Marc Esher

 

Created

4 May 2009

Requirements
 
Prerequisite knowledge
Working knowledge of ColdFusion Components, some experience writing unit tests for ColdFusion, and knowledge of the concepts covered in Part 1 of this series.
 
User level: Intermediate
 
 
 
Required products
Adobe ColdFusion Enterprise Edition (2016 release) (Download trial)
 
 
Sample files
 
Programmers who incorporate unit testing into their development process inevitably hit what we call the testing wall; this is the point where your code becomes too hard to test in isolation. You've hit the testing wall if you've ever thought:
 
  • "If my code only did simple stuff, unit testing would be easy."
  • "I often write tests, but when the test is too hard to write, I skip it."
  • "I know my code is hard to test, but I don't know how to change it to make it easier to write unit tests."
  • "Forget this! I'm applying for a job at the coffee shop."
In Part 1 of this series, you wrote code and tests for the initial implementation of a Twitter client. The code worked; the tests passed. However, you took a brute force approach when writing the unit tests: Most of the tests depended on the availability of the Twitter service. This resulted in slow tests, and slow tests mean productivity loss. In addition, the testing ignored error paths because the component design didn't lend itself to easily testing what happens when calls to the Twitter service fail.
 
In Part 2 of this series, you'll refactor the Twitter client code and its tests to create a more robust and easily testable API. You'll do this by applying strategies for decoupling external dependencies from business logic so that those dependencies can be swapped with test-friendly stand-ins (known as mocks) during test execution. In addition, you'll see how components that are easier to test are often easier to use in the context of an application.
 

 
What makes components hard to test?

Picture a modern bicycle wheel. The spokes slide through the hub and connect to the rim via threaded nipples. When the rim bends, truing the spokes will straighten it. When a spoke breaks, it is easily fixed. Flat tires are inconvenient but cheaply remedied with minimal effort.
 
As a system, this wheel comprises many simple components that perform single, specific functions. Because this wheel is designed for maintenance, the components are made to be easy to replace. Now, imagine if the spokes were welded to the hub, and the tire a solid hunk of rounded rubber chemically fused to the rim. If constructed this way, wheels would be virtually unmaintainable because their components couldn't be swapped out for new ones when they failed.
 
In modern software, the code you write is rarely self-contained. Often, it interacts with external systems and uses components that you or others have written to do its work. These external systems and components are often known as dependencies, and these dependencies make your components difficult to test.
 
Dependencies include, but are not limited to:
 
  • Databases
  • Network services (web services, REST, email, SMS, and so on)
  • File systems
  • Clock time (for time-sensitive operations)
  • Other components whose behavior is undesirable or difficult to control at time of test
In our experience, most nontrivial applications require such conditions. Furthermore, since these dependencies are precisely what lead to difficulty constructing meaningful tests, this critical functionality often goes untested. This results in anemic tests that exercise less critical functionality, thereby reducing test coverage and simultaneously creating the conditions for a false sense of security. People start to think, "I have a unit test for component X, and the tests are all Green. Component X is tested. My work is done here!"
 
Eventually, as team members and managers realize that the existing tests aren't helping much—and in fact may be hindering the effort due to the false sense of security they impart—the case for spending time on programmatic testing begins to fall apart. As the return on investment for unit testing approaches zero, the encouragement for testing dwindles, and you start writing fewer tests, and eventually no tests.
 
This feedback loop demoralizes developers and managers alike.
 
You can turn this around, and through the use of some simple strategies and freely available tools take testing to a place where you achieve thorough coverage, confidence in your tests, and better object-oriented code. By applying these strategies, designing for easy testability can lead to:
 
  • Smaller, more focused functions
  • Isolation of external dependencies from application logic
  • Increased opportunities for code reuse
  • Components that are easier to debug and maintain

 
The problem with the current Twitter client code and tests

The current implementation suffers from a test-time dependency on an available connection to Twitter. This means the tests will run slower, they could pass or fail inconsistently, and testing error paths is too hard (how do you simulate Twitter being down?). Also, because the HTTP calls are nested inline within the functions, it's difficult to get to the information you need for debugging when things go wrong.
 
You might be thinking, "But I'm writing a Twitter client… don't I want to talk to Twitter during my tests?" Good question. The answer is, "As little as possible." You are not testing the Twitter API. If they expose a function for returning replies, for example, then you have to trust their implementation. What you don't yet trust—and this is the purpose of the unit tests—is your execution of their APIs and your handling of the results of those function calls. You want to test your code, not theirs.
 
To refresh your memory, the full code for the current TwitterClient.cfc and test code is here.
 
The following code illustrates the current implementation's dependency on Twitter availability:
 
<cffunction name="verifyCredentials" hint="Tests that the credentials are valid."> <cfset var response = {} > <cfhttp url="#getTwitterUrl()#/account/verify_credentials.#getFormat()#" method="get" username="#getUserName()#" password="#getPassword()#"> <cfset response = deserializeJSON(cfhttp.FileContent)> <cfif not structKeyExists(response,'id')> <cfthrow type="TwitterAuthenticationFailure" message="Could not log into Twitter." detail="Tried user:#getUserName()# pwd:#getPassword()#"> </cfif> <cfreturn true /></cffunction> <cffunction name="friendsTimeline" hint="returns the authenticated user's friends timeline"> <cfset var response = {} > <cfhttp url="#getTwitterUrl()#/statuses/friends_timeline.#getFormat()#" method="get" username="#getUserName()#" password="#getPassword()#"> <cfset response = deserializeJSON(cfhttp.FileContent)> <cfreturn response> </cffunction>
 
Questions that testing should answer
If the tests shouldn't test the execution of the Twitter HTTP calls, what should they test? At a minimum, they should answer these questions:
 
  • Does each function execute the correct URL?
  • Does each function perform the correct HTTP request method (GET vs. POST)?
  • Does each function get correctly deserialized data?
Because you're writing code in incremental steps, the TDD approach should be your ally. To that end, the tests should also answer these questions:
 
  • While developing the component, do I have easy access to the information I need for debugging?
  • When errors occur, is it easy to access error information?
So how do you test your code, not theirs, while also answering all of these questions? Simply, this means removing the dependency on the external service that causes the code to be difficult to test. In practice, this means:
 
  1. Substituting realistic spoof or mock stand-ins for Twitter API calls to be used during testing (that is, the actual HTTP call).
  2. Refactoring code to take advantage of these mocks during tests while keeping it simple to use the real API during normal application execution.

 
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>

 
What next?

An important piece remains untested. Although the code provides for passing in additional URL parameters to send in Twitter API calls, none of the functions currently uses them and consequently no tests exist. Writing tests for that should be as simple as adding another setter/getter into TwitterResponse.cfc, calling the setter in TwitterClient.cfc, and writing appropriate assertions in a new unit test in TwitterClientTest.cfc. In addition, the code hasn't implemented any of Twitter's POST operations (updating status, for example), and it's a good idea to start there before implementing additional GET operations. When testing the POST operations, you'll be writing a simple mock that returns a spoof cfhttp struct, ensuring you don't actually post junk to your Twitter account every time you run the test.
 
In addition, seeing the TwitterResponse.cfc component in action, you might wonder if the deserializeResponse() function would make more sense in that component instead of TwitterClient.cfc? Does the current object design make this refactoring trivial? Would the existing tests prove the refactoring was successful?
 
Finally, since the Twitter API is rate-limited, you'll only be able to make so many requests per hour before those requests begin to fail. While this wouldn't pose a problem in your unit tests after you've completed development, it could be problematic in the real applications in which your component will be used. TwitterClient needs some kind of caching mechanism. As a thought experiment, sketch out the desired behavior of the cache and its interaction with TwitterClient. Ask yourself, "How should this behave, and how would I test it?"
 
 
Lingering questions
It's perfectly appropriate to have several lingering questions right now. These might include:
 
  • Why is this design superior to the original?
  • Since the current tests don't hit Twitter, won't they be useless in catching changes in the Twitter API?
  • Is this design an example of overthinking a component?
  • When should I use mocks and when shouldn't I?
To answer the first question, this design is better because it contains very little redundant code—the original implementation would have repeated the cfhttp/deserialize process repeatedly; also, it is easier to add new API calls.
 
As for catching changes to the Twitter API, it is fine to add unit tests that do not use mocks but instead contact Twitter directly. These would be more appropriate in separate test cases, also referred to as integration tests.
 
Is this an example of overthinking? Some might say these components are underdesigned! Putting that aside, answer this question: Which of the following would you rather maintain and extend?
 
The new friendsTimeline():
 
<cffunction name="friendsTimeline" hint="returns the authenticated user's friends timeline"> <cfreturn callTwitter(location="statuses/friends_timeline")> </cffunction>
Or this version:
 
<cffunction name="friendsTimeline" hint="returns the authenticated user's friends timeline"> <cfset var response = "" > <cfhttp url="http://www.twitter.com/statuses/friends_timeline.json" method="get" username="#getUserName()#" password="#getPassword()#"> <cfif isJson(cfhttp.FileContent)> <cfset response = deserializeJSON(cfhttp.FileContent)> <cfelse> <cfset response = deserializeXML(cfhttp.FileContent)> </cfif> <cfreturn response> </cffunction>
For the final question, when to use mocks: Use them when you wish to provide alternate implementations for a function at test time. Mock functions are great for testing multiple scenarios without complicated setup; for neutering undesirable behavior at test time (for example, deleting files or sending emails); and for spoofing hard-to-construct external dependencies. You'll start to get a feel for the kinds of dependencies that make testing hard. As that happens, ensure you encapsulate those behaviors so that you can mock them during tests.
 

 
Applying these lessons to other scenarios

The strategy of Extract and Abstract can be applied to most scenarios where dependencies become problematic. Do you have a function that runs a query and performs logic on the result? Simply extract the query into a separate function, and now you can mock that function in a test. Do you have a function that performs time-dependent operations? Instead of having your functions use now(), write a new function called getTime() that simply returns now(). Then, in your unit tests, you can mock getTime() to return whatever time you need. Do you have code that deletes files? Instead of using cffile directly, abstract it into a separate function, and use that function in your code. Then, in your tests, simply mock your new deleteFile() function to not do anything—dependency problem solved! If you're interested in seeing this in action, see our Adobe Max presentation on the topic and download the accompanying code. Once you begin asking yourself, "How can I make this code easy to test?" you'll get in the habit of automatically writing smaller, single-responsibility functions. These functions may become useful elsewhere, and so you'll extract some of them into libraries that other code can use. This is the promise of object-oriented programming: encapsulated, reusable components that decrease code duplication and increase maintainability.
 

 
Where to go from here

Above all, write tests! Do not be afraid to fail. If you want to land on the moon, you have to get in the spaceship first. If you want to improve your ability to design components to make them easier to test, you must write components and tests for those components. Practice!
 
Online resources abound for those learning programmatic testing. Feel free to post questions to the MXUnit Google Group. In addition, the Yahoo Test-Driven Development list is a good place for language-independent questions related to unit testing and test-driven development.