Skip to main content

Testing

The helpers temporarily replace definitions on the already configured global client and restore them when the block exits, including exceptions. Always pass a block. Do not run different overrides concurrently against the same client; prefer process-isolated test workers.

The Rails view/request/system examples below assume the named application routes, templates and fixtures exist. Helper tests exercise your enabled/disabled branches; overriding a flag does not prove its targeting rules.

RSpec Integration​

Setup​

First select the offline test initializer so a global client exists before any helper runs.

spec/rails_helper.rb
# rails_helper normally loads Rails before this point.
require 'rspec'
require 'rspec/expectations' # Define matchers before loading SDK helpers.
require 'toggly/rails/testing'

RSpec.configure do |config|
config.include Toggly::Rails::Testing::RSpecHelpers
end

Stubbing Features​

Single Feature​

describe 'Dashboard' do
it 'shows new UI when feature enabled' do
with_feature(:new_dashboard, enabled: true) do
visit dashboard_path
expect(page).to have_content('New Dashboard')
end
end

it 'shows old UI when feature disabled' do
with_feature(:new_dashboard, enabled: false) do
visit dashboard_path
expect(page).not_to have_content('New Dashboard')
end
end
end

Multiple Features​

it 'shows full experience when all features enabled' do
with_features(
new_dashboard: true,
dark_mode: true,
premium_features: true
) do
visit dashboard_path
expect(page).to have_css('.new-dashboard')
expect(page).to have_css('.dark-mode')
expect(page).to have_css('.premium')
end
end

Convenience Methods​

it 'shows promo when enabled' do
enable_feature(:promo_banner) do
visit home_path
expect(page).to have_css('.promo')
end
end

it 'hides promo when disabled' do
disable_feature(:promo_banner) do
visit home_path
expect(page).not_to have_css('.promo')
end
end

Custom Matchers​

RSpec.describe 'Feature flags' do
it 'scopes and restores the definition' do
original = Toggly.enabled?(:my_feature)
with_feature(:my_feature, enabled: true) do
expect(:my_feature).to be_feature_enabled
end
expect(Toggly.enabled?(:my_feature)).to eq(original)
with_feature(:my_feature, enabled: false) do
expect(:my_feature).not_to be_feature_enabled
end
end
end

To scope a whole RSpec example, use around { |example| with_feature(:my_feature, enabled: true) { example.run } }, not a blockless before hook.

Minitest Integration​

Setup​

test/test_helper.rb
require 'toggly/rails/testing'

class ActiveSupport::TestCase
include Toggly::Rails::Testing::MinitestHelpers
end

Usage​

test/integration/dashboard_test.rb
class DashboardTest < ActionDispatch::IntegrationTest
test 'shows new UI when feature enabled' do
with_feature(:new_dashboard, enabled: true) do
get dashboard_path
assert_includes response.body, 'New Dashboard'
end
end

test 'hides new UI when feature disabled' do
with_feature(:new_dashboard, enabled: false) do
get dashboard_path
refute_includes response.body, 'New Dashboard'
end
end
end

Assertions​

test 'feature is enabled' do
with_feature(:my_feature, enabled: true) do
assert_feature_enabled(:my_feature)
end
end

test 'feature is disabled' do
with_feature(:my_feature, enabled: false) do
assert_feature_disabled(:my_feature)
end
end

Test Configuration​

Disable Background Refresh​

Disabling polling alone still allows the initial HTTP fetch. Use an explicit test branch in your single initializer instead:

Use Defaults in Tests​

config/initializers/toggly.rb
Toggly::Rails.configure do |config|
config.app_key = Rails.env.test? ? nil : Rails.application.credentials.dig(:toggly, :app_key)
config.environment = Rails.env.production? ? 'Production' : 'Staging'
config.disable_background_refresh = Rails.env.test?
# Nonempty defaults select offline mode when app_key is nil.
config.defaults = { 'my_feature' => false, 'new_dashboard' => false }
end

This omits the key in tests and prevents definitions/telemetry networking. The Railtie owns cleanup. Do not create a second global client in the helper file.

Mock API Responses​

For an HTTP integration test, add webmock to the test Gemfile group. Install the stub before constructing the client, since its constructor performs the initial fetch. If stubbing Rails startup, install the stub before requiring config/environment.

spec/definitions_spec.rb
require 'rspec'
require 'webmock/rspec'
require 'json'
require 'toggly'

RSpec.describe 'Definitions loading' do
it 'loads the initial response' do
endpoint = 'https://definitions.toggly.io/definitions/test-app/Production'
stub_request(:get, endpoint).to_return(
status: 200,
body: { features: [
{ featureKey: 'default-feature', enabled: true }
] }.to_json,
headers: { 'Content-Type' => 'application/json' }
)
client = Toggly::Client.new(
app_key: 'test-app', environment: 'Production',
disable_background_refresh: true,
disable_entity_context_registration: true,
enable_usage_tracking: false, enable_metrics: false
)
begin
expect(client.enabled?('default-feature')).to be true
expect(a_request(:get, endpoint)).to have_been_made.once
ensure
client.close
end
end
end

Testing Patterns​

Testing Feature Variations​

describe ProductsController do
describe 'GET #index' do
context 'with new product list feature' do
it 'renders v2 template' do
with_feature(:new_product_list, enabled: true) do
get :index
expect(response).to render_template(:index_v2)
end
end
end

context 'without new product list feature' do
it 'renders default template' do
with_feature(:new_product_list, enabled: false) do
get :index
expect(response).to render_template(:index)
end
end
end
end
end

Testing with Context​

This proves the application passes a context while the flag is stubbed on. To verify filter behavior, load real filter definitions through a deterministic HTTP fixture instead of with_feature overrides.

describe 'Premium features' do
let(:premium_user) do
Toggly::Context.new(
identity: 'user-123',
groups: ['premium'],
traits: { plan: 'enterprise' }
)
end

it 'is enabled for premium users' do
with_feature(:premium_feature, enabled: true) do
result = Toggly.enabled?(:premium_feature, context: premium_user)
expect(result).to be true
end
end
end

Testing View Helpers​

describe 'Feature helper', type: :view do
it 'shows content when enabled' do
with_feature(:promo, enabled: true) do
render partial: 'shared/promo'
expect(rendered).to have_css('.promo-banner')
end
end

it 'hides content when disabled' do
with_feature(:promo, enabled: false) do
render partial: 'shared/promo'
expect(rendered).not_to have_css('.promo-banner')
end
end
end

System/Integration Tests​

spec/system/checkout_spec.rb
require 'rails_helper'

RSpec.describe 'Checkout', type: :system do
it 'uses new checkout when enabled' do
with_feature(:new_checkout, enabled: true) do
visit products_path
click_button 'Add to Cart'
click_link 'Checkout'

expect(page).to have_css('.new-checkout-form')
end
end
end

Request Specs​

spec/requests/api/features_spec.rb
require 'rails_helper'

RSpec.describe 'Features API', type: :request do
describe 'GET /api/features/:id' do
it 'returns feature status' do
with_feature(:my_feature, enabled: true) do
get '/api/features/my_feature'

expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)).to include(
'feature' => 'my_feature',
'enabled' => true
)
end
end
end
end

Debugging in Tests​

# Print all feature states
it 'debug features' do
puts "Features: #{Toggly.client.feature_keys}"
puts "Enabled: #{Toggly.enabled?(:my_feature)}"
end

# Check evaluation details
it 'check evaluation' do
context = Toggly::Context.with_identity('user-123')
result = Toggly.client.evaluate(:my_feature, context: context)
puts "Result: #{result.to_h}"
end