RSpec::Wait

Wait for conditions in RSpec

Gem Version Build Status Code Climate Coverage Status Dependency Status

Why does RSpec::Wait exist?

Timing is hard.

Timing problems and race conditions can plague your test suite. As your test suite slowly becomes less reliable, development speed and quality suffer.

RSpec::Wait strives to make it easier to test asynchronous or slow interactions.

How does RSpec::Wait work?

RSpec::Wait allows you to wait for an assertion to pass, using the RSpec syntactic sugar that you already know and love.

RSpec::Wait will keep trying until your assertion passes or times out.

Example

RSpec::Wait's wait_for assertions are nearly drop-in replacements for RSpec's expect assertions. The major difference is that the wait_for method only works with non-block matchers. However, wait_for will still accept a block because it may need to evaluate the content of that block multiple times while waiting.

describe Ticker do
  subject(:ticker) { Ticker.new("foo") }

  describe "#start" do
    before do
      ticker.start
    end

    it "starts a blank tape" do
      expect(ticker.tape).to eq("")
    end

    it "writes the message one letter at a time" do
      wait_for(ticker.tape).to eq("··-·")
      wait_for(ticker.tape).to eq("··-· ---")
      wait_for(ticker.tape).to eq("··-· --- ---")
    end
  end
end

This can be especially useful for testing user interfaces with tricky timing elements like JavaScript interactions or remote requests.

feature "User Login" do
  let!(:user) { create(:user, email: "[email protected]", password: "secret") }

  scenario "A user can log in successfully" do
    visit new_session_path

    fill_in "Email", with: "[email protected]"
    fill_in "Password", with: "secret"
    click_button "Log In"

    wait_for { current_path }.to eq()
    expect(page).to have_content("Welcome back!")
  end
end

Matchers

RSpec::Wait ties into RSpec's internals so it can take full advantage of any non-block matcher that you would use with RSpec's own expect method.

Timeout

By default, RSpec::Wait will wait up to 10 seconds for an assertion to pass. That timeout value is configurable in three ways:

RSpec Configuration

RSpec.configure do |config|
  config.wait_timeout = 3 # seconds
end

RSpec Metadata

The timeout can also be specified via options added to a spec's or context's :wait metadata:

scenario "A user can log in successfully", wait: { timeout: 3 } do
  visit new_session_path

  fill_in "Email", with: "[email protected]"
  fill_in "Password", with: "secret"
  click_button "Log In"

  wait_for { current_path }.to eq()
  expect(page).to have_content("Welcome back!")
end

The wait Method

On a per-assertion basis, the timeout value can be passed to the wait method.

scenario "A user can log in successfully" do
  visit new_session_path

  fill_in "Email", with: "[email protected]"
  fill_in "Password", with: "secret"
  click_button "Log In"

  wait(3.seconds).for { current_path }.to eq()
  expect(page).to have_content("Welcome back!")
end

Use with Cucumber

To enable RSpec::Wait in your Cucumber step definitions, add the following to features/support/env.rb:

require "rspec/wait"

World(RSpec::Wait)

Who wrote RSpec::Wait?

My name is Steve Richert and I wrote RSpec::Wait in April, 2014 with the support of my employer, Collective Idea. RSpec::Wait owes its current and future success entirely to inspiration and contribution from the Ruby community, especially the authors and maintainers of RSpec.

Thank you!

How can I help?

RSpec::Wait is open source and contributions from the community are encouraged! No contribution is too small.

See RSpec::Wait's contribution guidelines for more information.