Wait for conditions in RSpec
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.
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.
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(account_path)
expect(page).to have_content("Welcome back!")
end
end
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.
By default, RSpec::Wait will wait up to 10 seconds for an assertion to pass. That timeout value is configurable in three ways:
RSpec.configure do |config|
config.wait_timeout = 3 # seconds
end
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(account_path)
expect(page).to have_content("Welcome back!")
end
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(account_path)
expect(page).to have_content("Welcome back!")
end
To enable RSpec::Wait in your Cucumber step definitions, add the following to
features/support/env.rb
:
require "rspec/wait"
World(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!
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.