With Timeout

Posted on: June 11, 2014

A few days ago, I was writing some integration tests. The site’s workflow was something like this:

  1. POST to a URL like ‘/create_a_widget’ with some data
  2. Get a token saying “your widget will be A29G”
  3. GET the finished widget from a URL like ‘/widgets/A29G’.

At step 3, either the widget is ready or you have to try again.

Now I know that it shouldn’t take more than, say, 3 seconds before the widget is ready. So in my test, I could say “do step 2, sleep 3 then do step 3 and check the results.” But that’s slow, and maybe the widget will actually be ready much sooner.

Alternately, I could poll until it’s done. That could get results quickly, but would hang if it never finishes.

What I’d really like to do is say “poll for up to 3 seconds. If it finishes sooner, great! If time runs out, give up.”

So I wrote a small helper method to do just that. It looks like this:

def with_timeout_of(duration, failure_message = "Time up")
  start   = Time.now
  finish  = start + duration
  answer  = nil
  while Time.now < finish
    answer = yield
    break if answer
    sleep 0.1 # simple rate limit
  end
  answer ? answer : fail(failure_message)
end

Here’s how I use it:

it "can make and fetch a widget" do
  # 1. do the post
  # 2. get the token
  widget = with_timeout_of(3) do
    response = get_widget('A29G')
    # If there's a widget in the response,
    # return it; we're done.
    # If not, return false and we'll poll
    # again if time isn't up.
    response.fetch("widget", false) 
  end
  expect(widget).to be_shiny # or whatever
end

If you like this, feel free to adapt it for your own needs. Happy coding!