Writing an API Wrapper in Ruby with TDD

Discussion in 'Design & Development' started by Samuel, Jan 28, 2012.

  1. Samuel

    Samuel
    Expand Collapse
    Admin

    Joined:
    Dec 20, 2011
    Messages:
    5,576
    Likes Received:
    71
    [​IMG]
    Sooner or later, all developers are required to interact with an API. The most difficult part is always related to reliably testing the code we write, and, as we want to make sure that everything works properly, we continuosly run code that queries the API itself. This process is slow and inefficient, as we can experience network issues and data inconsistencies (the API results may change). Let’s review how we can avoid all of this effort with Ruby.
    Our Goal

    “Flow is essential: write the tests, run them and see them fail, then write the minimal implementation code to make them pass. Once they all do, refactor if needed.”​
    Our goal is simple: write a small wrapper around the Dribbble API to retrieve information about a user (called ‘player’ in the Dribbble world).
    As we will be using Ruby, we will also follow a TDD approach: if you’re not familiar with this technique, Nettuts+ has a good primer on RSpec you can read. In a nutshell, we will write tests before writing our code implementation, making it easier to spot bugs and to achieve a high code quality. Flow is essential: write the tests, run them and see them fail, then write the minimal implementation code to make them pass. Once they all do, refactor if needed.
    The API

    The Dribbble API is fairly straightforward. At the time of this it supports only GET requests and doesn’t require authentication: an ideal candidate for our tutorial. Moreover, it offers a 60 calls per minute limit, a restriction that perfectly shows why working with APIs require a smart approach.
    Key Concepts

    This tutorial needs to assume that you have some familiarity with testing concepts: fixtures, mocks, expectations. Testing is an important topic (especially in the Ruby community) and even if you are not a Rubyist, I’d encourage you to dig deeper into the matter and to search for equivalent tools for your everyday language. You may want to read “The RSpec book” by David Chelimsky et al., an excellent primer on Behavior Driven Development.
    To summarize here, here are three key concepts you must know:
    • Mock: also called double, a mock is “an object that stands in for another object in an example”. This means that if we want to test the interaction between an object and another, we can mock the second one. In this tutorial, we will mock the Dribbble API, as to test our code we don’t need the API, itself, but something that behaves like it and exposes the same interface.
    • Fixture: a dataset that recreates a specific state in the system. A fixture can be used to create the needed data to test a piece of logic.
    • Expectation: a test example written the from the point of view of the result we want to achieve.
    Our Tools

    “As a general practice, run tests every time you update them.”​
    WebMock is a Ruby mocking library that is used to mock (or stub) http requests. In other words, it allows you to simulate any HTTP request without actually making one. The primary advantage to this is being able to develop and test against any HTTP service without needing the service itself and without incurring in related issues (like API limits, IP restrictions and such).
    VCR is a complementary tool that records any real http request and creates a fixture, a file that contains all the needed data to replicate that request without performing it again. We will configure it to use WebMock to do that. In other words, our tests will interact with the real Dribbble API just once: after that, WebMock will stub all the requests thanks to the data recorded by VCR. We will have a perfect replica of the Dribbble API responses recorded locally. In addition, WebMock will let us test edge cases (like the request timing out) easily and consistently. A wonderful consequence of our setup is that everything will be extremely fast.
    As for unit testing, we will be using Minitest. It’s a fast and simple unit testing library that also supports expectations in the RSpec fashion. It offers a smaller feature set, but I find that this actually encourages and pushes you to separate your logic into small, testable methods. Minitest is part of Ruby 1.9, so if you’re using it (I hope so) you don’t need to install it. On Ruby 1.8, it’s only a matter of gem install minitest.
    I will be using Ruby 1.9.3: if you don’t, you will probably encounter some issues related to require_relative, but I’ve included fallback code in a comment right below it. As a general practice, you should run tests every time you update them, even if I won’t be mentioning this step explicitly throughout the tutorial.
    Setup

    [​IMG]
    We will use the conventional /lib and /spec folder structure to organize our code. As for the name of our library, we’ll call it Dish, following the Dribbble convention of using basketball related terms.
    The Gemfile will contain all our dependencies, albeit they’re quite small.

    source :rubygems

    gem 'httparty'

    group :test do
    gem 'webmock'
    gem 'vcr'
    gem 'turn'
    gem 'rake'
    end

    Httparty is an easy to use gem to handle HTTP requests; it will be the core of our library. In the test group, we will also add Turn to change the output of our tests to be more descriptive and to support color.
    The /lib and /spec folders have a symmetrical structure: for every file contained in the /lib/dish folder, there should be a file inside /spec/dish with the same name and the ‘_spec’ suffix.
    Let’s start by creating a /lib/dish.rb file and add the following code:

    require "httparty"
    Dir[File.dirname(__FILE__) + '/dish/*.rb'].each do |file|
    require file
    end

    It doesn’t do much: it requires ‘httparty’ and then iterates over every .rb file inside /lib/dish to require it. With this file in place, we will be able to add any functionality inside separate files in /lib/dish and have it automatically loaded just by requiring this single file.
    Let’s move to the /spec folder. Here’s the content of the spec_helper.rb file.

    #we need the actual library file
    require_relative '../lib/dish'
    # For Ruby < 1.9.3, use this instead of require_relative
    # require(File.expand_path('../../lib/dish', __FILE__))

    #dependencies
    require 'minitest/autorun'
    require 'webmock/minitest'
    require 'vcr'
    require 'turn'

    Turn.config do |c|
    # :eek:utline - turn's original case/test outline mode [default]
    c.format = :eek:utline
    # turn on invoke/execute tracing, enable full backtrace
    c.trace = true
    # use humanized test names (works only with :eek:utline format)
    c.natural = true
    end

    #VCR config
    VCR.config do |c|
    c.cassette_library_dir = 'spec/fixtures/dish_cassettes'
    c.stub_with :webmock
    end

    There’s quite a few things here worth noting, so let’s break it piece by piece:
    • At first, we require the main lib file for our app, making the code we want to test available to the test suite. The require_relative statement is a Ruby 1.9.3 addition.
    • We then require all the library dependencies: minitest/autorun includes all the expectations we will be using, webmock/minitest adds the needed bindings between the two libraries, while vcr and turn are pretty self-explanatory.
    • The Turn config block merely needs to tweak our test output. We will use the outline format, where we can see the description of our specs.
    • The VCR config blocks tells VCR to store the requests into a fixture folder (note the relative path) and to use WebMock as a stubbing library (VCR supports some other ones).
    Last, but not least, the Rakefile that contains some support code:

    require 'rake/testtask'

    Rake::TestTask.new do |t|
    t.test_files = FileList['spec/lib/dish/*_spec.rb']
    t.verbose = true
    end

    task :default => test

    The rake/testtask library includes a TestTask class that is useful to set the location of our test files. From now on, to run our specs, we will only type rake from the library root directory.
    As a way to test our configuration, let’s add the following code to /lib/dish/player.rb:

    module Dish
    class Player
    end
    end

    Then /spec/lib/dish/player_spec.rb:

    require_relative '../../spec_helper'
    # For Ruby < 1.9.3, use this instead of require_relative
    # require (File.expand_path('./../../../spec_helper', __FILE__))

    describe Dish::player do

    it "must work" do
    "Yay!".must_be_instance_of String
    end

    end

    Running rake should give you one test passing and no errors. This test is by no means useful for our project, yet it implicitly verifies that our library file structure is in place (the describe block would throw an error if the Dish::player module was not loaded).
    First Specs

    To work properly, Dish requires the Httparty modules and the correct base_uri, i.e. the base url of the Dribbble API. Let’s write the relevant tests for these requirements in player_spec.rb:

    ...
    describe Dish::player do

    describe "default attributes" do

    it "must include httparty methods" do
    Dish::player.must_include HTTParty
    end

    it "must have the base url set to the Dribble API endpoint" do
    Dish::player.base_uri.must_equal 'http://api.dribbble.com'
    end

    end

    end

    As you can see, Minitest expectations are self-explanatory, especially if you are an RSpec user: the biggest difference is wording, where Minitest prefers “must/wont” to “should/should_not”.
    Running these tests will show one error and one failure. To have them pass, let’s add our first lines of implementation code to player.rb:

    module Dish

    class Player

    include HTTParty

    base_uri 'http://api.dribbble.com'

    end

    end

    Running rake again should show the two specs passing. Now our Player class has access to all Httparty class methods, like get or post.
    Recording our First Request

    As we will be working on the Player class, we will need to have API data for a player. The Dribbble API documentation page shows that the endpoint to get data about a specific player is http://api.dribbble.com/players/:id
    As in typical Rails fashion, :id is either the id or the username of a specific player. We will be using simplebits, the username of Dan Cederholm, one of the Dribbble founders.
    To record the request with VCR, let’s update our player_spec.rb file by adding the following describe block to the spec, right after the first one:

    ...

    describe "GET profile" do

    before do
    VCR.insert_cassette 'player', :record => :new_episodes
    end

    after do
    VCR.eject_cassette
    end

    it "records the fixture" do
    Dish::player.get('/players/simplebits')
    end

    end

    end

    After running rake, you can verify that the fixture has been created. From now on, all our tests will be completely network independent.​
    The before block is used to execute a specific portion of code before every expectation: we use it to add the VCR macro used to record a fixture that we will call ‘player’. This will create a player.yml file under spec/fixtures/dish_cassettes. The :record option is set to record all new requests once and replay them on every subsequent, identical request. As a proof of concept, we can add a spec whose only aim is to record a fixture for simplebits’s profile. The after directive tells VCR to remove the cassette after the tests, making sure that everything is properly isolated. The get method on the Player class is made available, thanks to the inclusion of the Httparty module.
    After running rake, you can verify that the fixture has been created. From now on, all our tests will be completely network independent.
    Getting the Player Profile

    [​IMG]
    Every Dribbble user has a profile that contains a pretty extensive amount of data. Let’s think about how we would like our library to be when actually used: this is a useful way to flesh out our DSL will work. Here’s what we want to achieve:

    simplebits = Dish::player.new('simplebits')
    simplebits.profile
    => #returns a hash with all the data from the API
    simplebits.username
    => 'simplebits'
    simplebits.id
    => 1
    simplebits.shots_count
    => 157

    Simple and effective: we want to instantiate a Player by using its username and then get access to its data by calling methods on the instance that map to the attributes returned by the API. We need to be consistent with the API itself.
    Let’s tackle one thing at a time and write some tests related to getting the player data from the API. We can modify our "GET profile" block to have:

    describe "GET profile" do

    let:)player) { Dish::player.new }

    before do
    VCR.insert_cassette 'player', :record => :new_episodes
    end

    after do
    VCR.eject_cassette
    end

    it "must have a profile method" do
    player.must_respond_to :profile
    end

    it "must parse the api response from JSON to Hash" do
    player.profile.must_be_instance_of Hash
    end

    it "must perform the request and get the data" do
    player.profile["username"].must_equal 'simplebits'
    end

    end

    The let directive at the top creates a Dish::player instance available in the expectations. Next, we want to make sure that our player has got a profile method whose value is a hash representing the data from the API. As a last step, we test a sample key (the username) to make sure that we actually perform the request.
    Note that we’re not yet handling how to set the username, as this is a further step. The minimal implementation required is the following:

    ...
    class Player

    include HTTParty

    base_uri 'http://api.dribbble.com'

    def profile
    self.class.get '/players/simplebits'
    end

    end
    ...

    A very little amount of code: we’re just wrapping a get call in the profile method. We then pass the hardcoded path to retrieve simplebits’s data, data that we had already stored thanks to VCR.
    All our tests should be passing.
    Setting the Username

    Now that we have a working profile function, we can take care of the username. Here are the relevant specs:

    describe "default instance attributes" do

    let:)player) { Dish::player.new('simplebits') }

    it "must have an id attribute" do
    player.must_respond_to :username
    end

    it "must have the right id" do
    player.username.must_equal 'simplebits'
    end

    end

    describe "GET profile" do

    let:)player) { Dish::player.new('simplebits') }

    before do
    VCR.insert_cassette 'base', :record => :new_episodes
    end

    after do
    VCR.eject_cassette
    end

    it "must have a profile method" do
    player.must_respond_to :profile
    end

    it "must parse the api response from JSON to Hash" do
    player.profile.must_be_instance_of Hash
    end

    it "must get the right profile" do
    player.profile["username"].must_equal "simplebits"
    end

    end

    We’ve added a new describe block to check the username we’re going to add and simply amended the player initialization in the GET profile block to reflect the DSL we want to have. Running the specs now will reveal many errors, as our Player class doesn’t accept arguments when initialized (for now).
    Implementation is very straightforward:

    ...
    class Player

    attr_accessor :username

    include HTTParty

    base_uri 'http://api.dribbble.com'

    def initialize(username)
    self.username = username
    end

    def profile
    self.class.get "/players/#{self.username}"
    end

    end
    ...

    The initialize method gets a username that gets stored inside the class thanks to the attr_accessor method added above. We then change the profile method to interpolate the username attribute.
    We should get all our tests passing once again.
    Dynamic Attributes

    At a basic level, our lib is in pretty good shape. As profile is a Hash, we could stop here and already use it by passing the key of the attribute we want to get the value for. Our goal, however, is to create an easy to use DSL that has a method for each attribute.
    Let’s think about what we need to achieve. Let’s assume we have a player instance and stub how it would work:

    player.username
    => 'simplebits'
    player.shots_count
    => 157
    player.foo_attribute
    => NoMethodError

    Let’s translate this into specs and add them to the GET profile block:

    ...
    describe "dynamic attributes" do

    before do
    player.profile
    end

    it "must return the attribute value if present in profile" do
    player.id.must_equal 1
    end

    it "must raise method missing if attribute is not present" do
    lambda { player.foo_attribute }.must_raise NoMethodError
    end

    end
    ...

    We already have a spec for username, so we don’t need to add another one. Note a few things:
    • we explicitly call player.profile in a before block, otherwise it will be nil when we try to get the attribute value.
    • to test that foo_attribute raises an exception, we need to wrap it in a lambda and check that it raises the expected error.
    • we test that id equals 1, as we know that that is the expected value (this is a purely data-dependent test).
    Implementation-wise, we could define a series of methods to access the profile hash, yet this would create a lot of duplicated logic. Moreover, the would rely on the API result to always have the same keys.
    “We will rely on method_missing to handle this cases and ‘generate’ all those methods on the fly.”​
    Instead, we will rely on method_missing to handle this cases and ‘generate’ all those methods on the fly. But what does this mean? Without going into too much metaprogramming, we can simply say that every time we call a method not present on the object, Ruby raises a NoMethodError by using method_missing. By redefining this very method inside a class, we can modify its behaviour.
    In our case, we will intercept the method_missing call, verify that the method name that has been called is a key in the profile hash and in case of positive result, return the hash value for that key. If not, we will call super to raise a standard NoMethodError: this is needed to make sure that our library behaves exactly the way any other library would do. In other words, we want to guarantee the least possible surprise.
    Let’s add the following code to the Player class:

    def method_missing(name)
    if profile.has_key?(name.to_s)
    profile[name.to_s]
    else
    super
    end
    end

    The code does exactly what described above. If you now run the specs, you should have them all pass. I’d encorage you to add some more to the spec files for some other attribute, like shots_count.
    This implementation, however, is not really idiomatic Ruby. It works, but it can be streamlined into a ternary operator, a condensed form of an if-else conditional. It can be rewritten as:

    def method_missing(name, *args, &block)
    profile.has_key?(name.to_s) ? profile[name.to_s] : super
    end

    It’s not just a matter of length, but also a matter of consistency and shared conventions between developers. Browsing source code of Ruby gems and libraries is a good way to get accustomed to these conventions.
    Caching

    As a final step, we want to make sure that our library is efficient. It should not make any more requests than needed and possibly cache data internally. Once again, let’s think about how we could use it:

    player.profile
    => performs the request and returns a Hash
    player.profile
    => returns the same hash
    player.profile(true)
    => forces the reload of the http request and then returns the hash (with data changes if necessary)

    How can we test this? We can by using WebMock to enable and disable network connections to the API endpoint. Even if we’re using VCR fixtures, WebMock can simulate a network Timeout or a different response to the server. In our case, we can test caching by getting the profile once and then disabling the network. By calling player.profile again we should see the same data, while by calling player.profile(true) we should get a Timeout::Error, as the library would try to connect to the (disabled) API endpoint.
    Let’s add another block to the player_spec.rb file, right after dynamic attribute generation:

    describe "caching" do

    # we use Webmock to disable the network connection after
    # fetching the profile
    before do
    player.profile
    stub_request:)any, /api.dribbble.com/).to_timeout
    end

    it "must cache the profile" do
    player.profile.must_be_instance_of Hash
    end

    it "must refresh the profile if forced" do
    lambda { player.profile(true) }.must_raise Timeout::Error
    end

    end

    The stub_request method intercepts all calls to the API endpoint and simulates a timeout, raising the expected Timeout::Error. As we did before, we test the presence of this error in a lambda.
    Implementation can be tricky, so we’ll split it into two steps. Firstly, let’s move the actual http request to a private method:

    ...
    def profile
    get_profile
    end

    ...

    private

    def get_profile
    self.class.get("/players/#{self.username}")
    end
    ...

    This will not get our specs passing, as we’re not caching the result of get_profile. To do that, let’s change the profile method:

    ...
    def profile
    @profile ||= get_profile
    end
    ...

    We will store the result hash into an instance variable. Also note the ||= operator, whose presence makes sure that get_profile is run only if @profile returns a falsy value (like nil).
    Next we can add the forced reload directive:

    ...
    def profile(force = false)
    force ? @profile = get_profile : @profile ||= get_profile
    end
    ...

    We’re using a ternary again: if force is false, we perform get_profile and cache it, if not, we use the logic written in the previous version of this method (i.e. performing the request only if we don’t have already an hash).
    Our specs should be green now and this is also the end of our tutorial.
    Wrapping Up

    Our purpose in this tutorial was writing a small and efficient library to interact with the Dribbble API; we’ve laid the foundation for this to happen. Most of the logic we’ve written can be abstracted and requesed to access all the other endpoints. Minitest, WebMock and VCR have proven to be valuable tools to help us shape our code.
    We do, however, need to be aware of a small caveat: VCR can become a double-edged sword, as our tests can become too much data-dependent. If, for any reason, the API we’re building against changes without any visible sign (like a version number), we may risk having our tests perfectly working with a dataset, which is no longer relevant. In that case, removing and recreating the fixture is the best way to make sure that our code still works as expected.
    [​IMG]

    [​IMG]
    [​IMG]
    [​IMG] [​IMG] [​IMG] [​IMG] [​IMG]

    Continue reading...
     

Share This Page