Testing with Retriable
May 26, 2026 · View on GitHub
Retriable.with_override exists primarily for tests. It lets a test force
retry options like tries: 1 or base_interval: 0 so the suite runs quickly
and predictably, regardless of what the application's Retriable.configure
defaults are.
with_override is block-scoped: the override is active inside the block and
restored to its previous value (which is usually "no override") when the block
exits, even if the block raises. It is also thread-local — overrides set in
one thread do not affect other threads — so it is safe for parallel test
runners. See the README for the full API contract.
RSpec
Apply an override to every test
Use around(:each) in RSpec.configure so every test in the suite runs inside
the override. This is the most common pattern:
RSpec.configure do |config|
config.around(:each) do |example|
Retriable.with_override(tries: 1, base_interval: 0) do
example.run
end
end
end
Apply an override to a specific context
describe MyClient do
context "when external calls should not retry" do
around(:each) do |example|
Retriable.with_override(tries: 1) { example.run }
end
it "fails fast" do
# `with_override(tries: 1)` is active here
end
end
end
Apply an override to a single test
Wrap the test body directly:
it "does the thing without waiting" do
Retriable.with_override(tries: 1, base_interval: 0) do
# test body
end
end
Reusable helper
Wrap a common configuration in a helper to keep tests readable:
module RetriableHelpers
def with_fast_retries(&block)
Retriable.with_override(tries: 1, base_interval: 0, &block)
end
end
RSpec.configure do |config|
config.include RetriableHelpers
end
# In a spec:
it "does the thing" do
with_fast_retries do
# test body
end
end
Minitest
class MyClientTest < Minitest::Test
def around
Retriable.with_override(tries: 1, base_interval: 0) { yield }
end
def test_fails_fast
# `with_override(tries: 1)` is active here
end
end
Older Minitest versions without around can wrap the test body directly:
def test_fails_fast
Retriable.with_override(tries: 1) do
# test body
end
end
Short-Circuiting Retriable in Your Test Suite
When you are running tests for your app, the default retry behavior (3 tries
with exponential backoff) makes failing blocks take a long time. To short-circuit
retries — including calls that pass local options — set tries: 1 and disable
backoff using with_override.
Under Rails
Keep shared defaults in Retriable.configure and apply test-only overrides via
RSpec's around hook (or your test framework's equivalent):
# config/initializers/retriable.rb
Retriable.configure do |c|
c.tries = 3
c.base_interval = 0.5
c.rand_factor = 0.5
end
# spec/spec_helper.rb (or equivalent)
RSpec.configure do |config|
config.around(:each) do |example|
Retriable.with_override(tries: 1, base_interval: 0, rand_factor: 0) do
example.run
end
end
end
If a specific test needs normal retry behavior, opt out by running outside the
around hook. The cleanest way is to tag the example and skip the hook for
tagged examples:
config.around(:each, retriable: :real) { |example| example.run }
config.around(:each) do |example|
next example.run if example.metadata[:retriable] == :real
Retriable.with_override(tries: 1, base_interval: 0, rand_factor: 0) do
example.run
end
end
it "exercises the real retry behavior", retriable: :real do
# `with_override` is not applied here
end
Overriding Configured Contexts in Tests
If you have configured contexts, top-level override values (such as tries: 1)
already take precedence over context-specific values. To override
context-specific options as well (for example, clearing a context's
:intervals array or shrinking its :on exception list), pass :contexts to
with_override.
Given a configured google_api context:
# config/initializers/retriable.rb
Retriable.configure do |c|
c.contexts[:google_api] = {
tries: 5,
base_interval: 3,
on: [
Net::ReadTimeout,
Signet::AuthorizationError,
Errno::ECONNRESET,
OpenSSL::SSL::SSLError,
],
}
end
You can override both top-level defaults and per-context options in your test setup:
RSpec.configure do |config|
config.around(:each) do |example|
context_overrides = Retriable.config.contexts.each_key.with_object({}) do |key, h|
h[key] = { tries: 1, base_interval: 0 }
end
Retriable.with_override(
multiplier: 1.0,
rand_factor: 0.0,
base_interval: 0,
contexts: context_overrides,
) do
example.run
end
end
end
Notes
- The override is automatically cleared when the block exits, including when the block raises. You do not need to clean up after the block.
with_overridecalls nest: an inner block temporarily replaces the active override, and the outer override is restored when the inner block exits.- Overrides are thread-local. Child threads spawned inside the block do not
inherit it. If a test spawns background threads that themselves call
Retriable.retriable, wrap each background thread's body in its ownwith_overridecall.