Skip to content
4 changes: 3 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ gem 'rspec', '~> 3.0'
gem 'rack-test'
gem 'rackup'
gem 'sqlite3', "~> #{ENV['SQLITE3_VERSION'] || '1.4.1'}"
gem 'rails', "~> #{ENV['RAILS_VERSION'] || '7.0.4'}"
gem 'rails', "~> #{ENV['RAILS_VERSION'] || '7.1'}"
gem 'minitest', '~> 5.18'
gem 'minitest-documentation'
gem 'webmock'
Expand All @@ -27,6 +27,8 @@ gem 'flamegraph'
gem 'climate_control'
gem 'mysql2'
gem 'pg'
gem 'cuprite'
gem 'puma'

group(:guard) do
gem 'guard'
Expand Down
7 changes: 6 additions & 1 deletion lib/flipper/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def self.default_strict_value
instrumenter: ENV.fetch('FLIPPER_INSTRUMENTER', 'ActiveSupport::Notifications').constantize,
log: ENV.fetch('FLIPPER_LOG', 'true').casecmp('true').zero?,
cloud_path: "_flipper",
strict: default_strict_value
strict: default_strict_value,
test_help: Flipper::Typecast.to_boolean(ENV["FLIPPER_TEST_HELP"] || Rails.env.test?),
)
end

Expand Down Expand Up @@ -86,6 +87,10 @@ def self.default_strict_value
end
end

initializer "flipper.test" do |app|
require "flipper/test_help" if app.config.flipper.test_help
end

def cloud?
!!ENV["FLIPPER_CLOUD_TOKEN"]
end
Expand Down
36 changes: 36 additions & 0 deletions lib/flipper/test_help.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module Flipper
module TestHelp
def flipper_configure
# Create a single shared memory adapter instance for each test
@flipper_adapter = Flipper::Adapters::Memory.new

Flipper.configure do |config|
config.adapter { @flipper_adapter }
config.default { Flipper.new(config.adapter) }
end
end

def flipper_reset
Flipper.instance = nil # Reset previous flipper instance
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got some spec failures after this - you get a new flipper instance but using the same memory adapter, so there's leakage between tests.

I can make everything green again if I add Flipper.instance.import(Flipper::Adapters::Memory.new) to the reset (which was the simplest way I could find to delete all the data from the shared adapter)

Copy link

@rnestler rnestler Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I hit the same. Maybe it would be better to just disable all feature flags in reset? Ah no, for me it just doesn't work in system specs with the memory adapter. The feature flag just doesn't get set.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I hit this. The app thread and spec thread end up using a different flipper instance (fine). The app thread is running for the entire duration of the specs so keeps the same flipper instance.

On the second spec the before each creates a new flipper instance but this only affects the spec thread, the app thread still has the old memory adapter.

To make this work I had to disable the builtin flipper/test_help (with the env var) and then do what I was doing before: create the shared memory adapter once in before(suite) and clear out its contents in before each

end
end
end

if defined?(RSpec)
RSpec.configure do |config|
config.include Flipper::TestHelp
config.before(:all) { flipper_configure }
config.before(:each) { flipper_reset }
end
end

if defined?(ActiveSupport)
ActiveSupport.on_load(:active_support_test_case) do
ActiveSupport::TestCase.class_eval do
include Flipper::TestHelp

setup :flipper_configure
setup :flipper_reset
end
end
end
40 changes: 40 additions & 0 deletions spec/flipper/engine_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,46 @@
if: nil
})
end

context "test_help" do
it "is loaded if RAILS_ENV=test" do
Rails.env = "test"
allow(Flipper::Engine.instance).to receive(:require).and_call_original
expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
subject
expect(config.test_help).to eq(true)
end

it "is loaded if FLIPPER_TEST_HELP=true" do
ENV["FLIPPER_TEST_HELP"] = "true"
allow(Flipper::Engine.instance).to receive(:require).and_call_original
expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
subject
expect(config.test_help).to eq(true)
end

it "is loaded if config.flipper.test_help = true" do
initializer { config.test_help = true }
allow(Flipper::Engine.instance).to receive(:require).and_call_original
expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
subject
end

it "is not loaded if FLIPPER_TEST_HELP=false" do
ENV["FLIPPER_TEST_HELP"] = "false"
allow(Flipper::Engine.instance).to receive(:require).and_call_original
expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help").never
subject
end

it "is not loaded if config.flipper.test_help = false" do
Rails.env = "true"
initializer { config.test_help = false }
allow(Flipper::Engine.instance).to receive(:require).and_call_original
expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help").never
subject
end
end
end

context 'with cloud' do
Expand Down
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
require 'flipper/api'
require 'flipper/spec/shared_adapter_specs'
require 'flipper/ui'
require 'flipper/test_help'

Dir[FlipperRoot.join('spec/support/**/*.rb')].sort.each { |f| require f }

Expand Down
46 changes: 46 additions & 0 deletions test_rails/system/test_help_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require_relative "../helper"

# Not worth trying to test on old Rails versions
return unless Rails::VERSION::MAJOR >= 7

require "capybara/cuprite"
require "flipper"
require "flipper/test_help"

require 'action_dispatch/system_testing/server'
ActionDispatch::SystemTesting::Server.silence_puma = true

class TestApp < Rails::Application
config.load_defaults "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}"
config.eager_load = false
config.logger = ActiveSupport::Logger.new(StringIO.new)
routes.append do
root to: "features#index"
end
end

TestApp.initialize!

class FeaturesController < ActionController::Base
def index
render json: Flipper.enabled?(:test) ? "Enabled" : "Disabled"
end
end

class TestHelpTest < ActionDispatch::SystemTestCase
# Any driver that runs the app in a separate thread will test what we want here.
driven_by :cuprite

# Ensure this test uses this app instance
setup { Rails.application = TestApp.instance }

test "configures a shared adapter between tests and app" do
Flipper.disable(:test)
visit "/"
assert_selector "*", text: "Disabled"

Flipper.enable(:test)
visit "/"
assert_selector "*", text: "Enabled"
end
end