Quite often at work I find myself having to write code (and therefore tests) that use the Net::HTTP class to read XML from some external REST based service. In this brief article I will explain two minor updates I perform on the Net:HTTP and File classes when in my testing environment.
Most of the services I interact with provide QA and Staging environments along with their Production system. Often times though I am performing other actions with other external services based on the data I am reading from the XML document. Because of this I prefer to mock up the XML response with data that will allow me to test all cases.
The following code uses a few Rails only extensions like alias_method_chain. It is quite simple to factor these out, but I will leave that as a challenge to the reader.
module Net
class HTTP
def get_with_file(path, *args)
case File.basename(path)
when /activations/
mock_path = File.join(File.dirname(__FILE__), "activations.xml")
return File.open(mock_path)
else
get_without_file(path, *args)
end
end
alias_method_chain :get, :file
end
end
The above class opens Net::HTTP and adds the get_with_file method. Its processing is fairly simple, it looks at the basename of the path provided and uses that to match inside a case statement. Using this case statement I can now open a file, or proceed to another method (get_without_file in this case). Following this method definition is a call to alias_method_chain that renames Net::HTTP.get to Net::HTTP::get_without_file, and links Net::HTTP.get to Net::HTTP::get_with_file.
When using a file to represent an HTTP response object you would normally use the following code and be done.
class File alias_method :body, :read end
This will provide the desired results and make the File appear to be a simple HTTP response. The problem with the simple rewrite to File is that if you call response.body more than once, you will receive nil after the first try. This is because you have reached the end of the file the first time you read through it. To combat this problem I use the following code instead of the simple code above.
class File
def read_with_memory
@file_contents ||= read_without_memory
return @file_contents
end
alias_method_chain :read, :memory
alias_method :body, :read_with_memory
def header
{
'status' => "201 Created",
'location' => "http://somethingunimportant.com/orders/1"
}
end
def code
"201"
end
end
The header and code methods aren’t at all required, but were needed due to the calling code of mine using them to verify the Net::HTTP response object before starting to process the data. The read_with_memory method, like the other two is the important change. This method reads the file into an instance variable and saves it. Each subsequent call returns the instance variable, and not the end of the File.