Ruby on Rails, iPhone SDK, .Net, GitHub...

We spend a lot of time thinking about these things. If we have something helpful to share, we'll put it here.

Request a free RaddOnline® proposal.

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

  1. 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'
  2. 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
  3. 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
  4. 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!
  5. 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
    @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>')

    @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
  6. 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.

Post a comment


(required, but not displayed)