Testing Third Party HTTP APIs - Richard Livsey and Bartosz Blimke
Richard started by explaining that if you've got an app that communicates with a third party API such as Twitter or Spreedly, you really need to be able to how your code interacts with that site. You also want to be able to test your app without sending any data to a real web site. Why not just test against the real site? Possible problems include:
- Limits on the number of times you can call an external API.
- Flakiness; consecutive test runs may not receive the same input data each time you run them.
So what are the options for replacing a third party app?
Build a dummy app
There's nothing to stop you from making your own application that behaves just like the real thing. There's no stubbing required; you just need to switch the URL of the remote app during testing. But now you have two apps that you need to maintain and test. And what happens when the remote API changes?
You can write test code that gets called during your tests that override how your application behaves in testing. So if your code would normally make an HTTP request to create a
monkey object you could prevent the API call from being made with something like this (in RSpec):
monkey = mock(:monkey, :get_bananas => ) Monkey.stub!(:new).and_return(monkey)
Or you can stub the HTTP request, but you then find yourself mocking the implementation rather than the behaviour of the application:
it "should ..." do stub_http_with__fixuture("no_subscribers.xml", 200) RSpreedly::Subscriber.all.should ==  end
The problem with this approach is that every HTTP request you make will come back with the same response. Ouch.
Faking it with WebMock
An alternative approach is to return the right data for every request made during your tests, by cheekily grabbing suitable data values for each request your tests make and squirreling them away locally. You then just need to arrange for the right data to be returned in response to the relevant HTTP requests, and you're done.
You can use Net::HTTP Spy to dump all the HTTP traffic that your application generates, and Richard has had some success with that approach.
Richard then handed over to Bartosz, who talked about his WebMock project. He started by showing some example tests that mocked calls to the Net::HTTP library, and how his tests broke when he refactored his code to use RestClient instead of Net::HTTP.
Having run into this problem several times, Bartosz wanted a way of testing HTTP connections that:
- Doesn't depend on usage of a specific API or HTTP library.
- Supports stubbing based on request method, uri, body and headers.
- Allows you to assert that a request was actually made.
- Supports multiple HTTP libraries (e.g. Net::HTTP and RestClient).
So Bartosz wrote WebMock, that allows you to write your specs like this:
stub_request(:get, "http://my.geoservice.org?zip=WC1H 9EF"). to_return(:body => "... return data ..."} request(:get, "http://my.geoservice.org?zip=WC1H 9EF"). should have_been_made
That snippet of code is RSpec, but WebMock also supports Test::Unit (hurray!) in case you're not an RSpec fan (three cheers for Shoulda).
You can ask WebMock to stub requests that have specific URLs, HTTP methods (e.g. POST), body content and headers. WebMock also supports basic auth.
Both Net::HTTP and HTTPClient are supported, which menas that HTTParty, RestClient, RightScale:HttpConnection and open-uri are all supported (as they're based on Net::HTTP).
If you want to know more you can find Richard and Bartosz online
More talks from Ruby Manor
- Browser Level Testing - Martin Kleppmann
- Data Visualisation with Ruby - Chris Lowis
- Denormalising Your Rails Application - Dan Lucraft
- gem that - James Adam
- Introduction to Rango - Jakub Šťastný
- Ruby and Cocoa - James Mead
- RubyGoLightly - Eleanor McHugh
- Secrets of the Standard Library - Paul Battley
- Short Order Ruby - Ben Griffiths
- The Joy of Painting with Ruby - Jason Cale
- Ruby Manor 2008 morning session and afternoon session