[ Content | View menu ]

Testing API Integrations In RSpec

Mark Mzyk March 5, 2012

RSpec, a Behavior Driven Development framework, is great for various kinds of testing, such as unit and integration testing. However one area that is hard to test is integrations that cross APIs. You call into a service, then need to wait while it takes some action before proceeding with the test you want. An example of this would be inserting an item into a search service, then querying the service to test the output, ensuring your item is there. The service might have a queue and so doesn’t insert the item immediately. How do you test this?

One way is to call the API, sleep, and then run the test. The problem with this is that you have to wait the entire sleep duration for every test run, even if the results are ready before the sleep is done. RSpec’s and Ruby’s flexibility allow you to avoid this situation by writing a helper function:

def wait(time, increment = 5, elapsed_time = 0, &block)
  begin
    yield
  rescue Exception => e
    if elapsed_time >= time
      raise e
    else
      sleep increment
      wait(time, increment, elapsed_time + increment, &block)
    end
  end
end

To use this function, you pass your test to it as a block. The wait function will immediately try the test. If a failure is reported – which in RSpec is just an exception – the function catches the failure and sleeps. The assumption is that a failure means the API that is called into has not yet completed the action that is being waited upon. The wait function continues to retry the test until the test either passes or until it reaches the timeout that was passed to it, at which point it will raise the last error it caught, which will be the last failure of the test it ran.

The wait function has the benefit of returning successes as soon as they are encountered, so the tests don’t have to sleep for the entire duration specified. However, it still has to wait the entire sleep period for a failure, because it can’t distinguish a legitimate failure from the API service not yet returning the correct results.

Using the wait function in an RSpec test looks like this:

describe "a test" do
  before :each do
    insert_user_remote_service("user")
  end

  after :each do
    delete_user_remote_service("user")
  end

  it "should receive the results it expects from the api" do
    wait 30 do
      search_for_user_in_remote_service("user").should == "user"
    end
  end
end

Programming - 2 Comments


Git Info

Mark Mzyk February 29, 2012

Git info is a bash script I wrote that searches for git repositories with modified files and returns a list of the modified files and current branch name. It searches one level deep in subdirectories of the directory it is invoked in.

It’s useful if you collect a bunch of git repos in one master folder (say ~/src, for instance) and want to be able to scan the current state of each repo easily.

You can also pass the script the -b or –branch option and it will report the current branch of repos that don’t have any modified files, in addition to those that branches that do have modified files.

Tools - 0 Comments


Chef and ActiveSupport

Mark Mzyk February 18, 2012

Upfront disclaimer: While I work for Opscode, maker of Chef, this blog post reflects my personal opinion and is not company position.

Chef has a major pain point in the Ruby community: ActiveSupport.

ActiveSupport is a library of useful functions that was born from Rails, adding into Ruby all sorts of helper functions. It does so by monkey patching core classes. The monkey patching makes it easy to not even realize you’re using ActiveSupport.

ActiveSupport works great in Rails – because Rails was built from the ground up to use ActiveSupport. Where ActiveSupport falls down is in projects not built from the ground up to use it. Chef is one of those projects.

Chef does not use ActiveSupport. If you run Chef there is no need to install ActiveSupport and Chef will not install ActiveSupport. However, one of the great features of Chef is that you can use any Ruby code of your choosing in the cookbooks – including ActiveSupport, or more insidiously, a gem that pulls in ActiveSupport.

When ActiveSupport gets into Chef’s load path, it has the potential for insidious harm, due to the monkey patching it performs. With Chef this particularly strikes around json operations. Chef does a fair amount of serializing to and from json, using the json gem to accomplish this. ActiveSupport has its own opinions on how json should be serialized/deserialized and will override the json gem.

The Rails team is aware that ActiveSupport interferes with the json gem and has provided ways to defer to it. These work great, so long as you have consciously made the decision to load ActiveSupport and to tell it to use the json gem. What if your project is like Chef, which doesn’t need or want ActiveSupport, but might find it loaded? It is difficult to undo monkey patching once it is present, especially if you want to be a good citizen and not interfere with ActiveSupport when something else might be relying on it.

Opscode has tried to devise fixes that work around ActiveSupport when it is present, without changing the behavior of ActiveSupport. These should go out in a future Chef release. My recommendation is, even after the fixes go out, to run Chef without ActiveSupport. If you must use ActiveSupport and you see an error that can be traced to strange json, try setting ActiveSupport to use the json gem for its backend. It’s also possible that you won’t notice anything wrong with ActiveSupport present, as Chef is fairly promiscuous in the json it allows and will often correctly interpret the json.

If all else fails, file a bug with Opscode and we’ll do our best to keep working around ActiveSupport, since it isn’t going away anytime soon.

Technology - 0 Comments