Accessibility

Table of Contents

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

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.