Ruby on Rails: Testing Net::HTTP With Mocks
Posted by Tim Stephenson, RaddOnline® on Wednesday, January 07, 2009
Problem solving test failures using Net::HTTP in Rails
I had some trouble figuring out how to test Net::HTTP in one of my Ruby on Rails projects, so I thought I'd share my approach to the problem... and my ultimate success.
To make my tests less fragile, and speed them up, I wanted to use Mocha to stub things out. I'm using Rails 2.1 and Test::Unit. On top of that I'm using the Shoulda plugin and Mocha.
In my project, I am using Net::HTTP to retrieve the contents of web pages, parse them and store the data into a model. So I needed to mock the Net::HTTPResponse, and stub out the request.
While I'm not going into the specifics of how to use Shoulda here, it has helped me a great deal. I find my tests more readable and faster to write.
For mocking the objects I'm using Mocha. If you've never used it before, here's how to get started:
Download and Installation
Install the gem with the following command…
$ gem install mocha
Or install the Rails plugin…
$ script/plugin install svn://rubyforge.org/var/svn/mocha/trunk
I used the gem.
Preparing the Tests
- There are a few extra items that are required at the top of the test class, so be sure to require them like so:
require File.dirname(__FILE__) + '/../test_helper'
require 'uri'
require 'net/http'
require 'mocha' - Then mock the Net::HTTPResponse object and stub out the response that is expected. Since I use this for several tests, I included it in the
setup.
def setup
@http_mock = mock('Net::HTTPResponse')
@http_mock .stubs(:code => '200', :message => "OK", :content_type => "text/html", :body => '<title>Test</title><body>Body of the page</body>')
end - Here's how the test looks, using Shoulda and the mock objects.
context "A web site instance" do
should "return a title for a page when the response is successful" do
# I used expects here.
# Since I don't care about how many times get_response is called
# I could have used stubs too...
Net::HTTP.expects(:get_response).returns(@http_mock)
# The model I'll be testing
site = WebSite.new
#The method in my model that I wanted to test
site.get_site_with_url('http://www.raddonline.com')
# It parses html and stores values in the database. I'm confirming that it found the title correctly.
assert_equal "Test", site.title, "The title should have been 'Test'"
end
end - Later I added code to my model to handle the return of compressed data. The above test began to fail with this error: ".[]('Content-Encoding') expected 0 times actual 1." As it turns out, this was because I was calling a method on the response object to determine if the content was compressed with gzip. My call looks like this:
if response['Content-Encoding'] == 'gzip'
Now the test passes again! - Finally, I wanted to test a page that responded with a redirection. First, I had to mock a redirection response. I added it to my setup
method like so:
def setup<title>Test</title><body>Body of the page</body>
@http_mock = mock('Net::HTTPResponse')
@http_mock .stubs(:code => '200', :message => "OK", :content_type => "text/html", :body => '')
@redirect_mock = mock('Net::HTTPResponse')
@redirect_mock.stubs(:code => '302', :message => "Moved Temporarily", :location => "http://www.raddonline.com", :content_type => "text/html", :body => 'Doesn't matter')
end - And the test looks like this:
# This test goes in the same context as the previous test
should "get a status of 200 OK after completing the redirection" do
# I'm using stubs for the call to get_response now because it will be called more than once.
# If I cared how many times I could have used:
# Net::HTTP.expects(:get_response).times(2).returns(@redirect_mock, @http_mock)
# Also, provide both the redirect and the http mock objects for the return objects.
Net::HTTP.stubs(:get_response).returns(@redirect_mock, @http_mock)
# Stub out the call to [] with a check for Location
@redirect_mock.stubs(:[]).with('Location').returns('http://www.itdoesntmatter.com')
@http_mock.stubs(:[]).with('Content-Encoding').returns('NotGZip')
site = WebSite.new
site.get_site_with_url('http://video.google.com')
assert_equal "200, OK", site.server_status
end
That's it. If you're a geek like me, hopefully you'll find this helpful.
