RailsSettings vs parallel_tests


RailsSetting stubbing problem

I have an area of functionality that is controlled by a Settings class that is a subclass of RailsSettings::CachedSettings (rails-settings-cached gem) and changing that setting for tests was interfering with the parallel_tests gem when I ran my rspec tests.

I tried namespacing the cache, making TTL 0, invalidating the cache, monkeypatching the lookup… Every convoluted solution possible… to try and get tests to play nicely together.

The humbling thing about programming is how simple actual solutions are compared the things the brain comes up with.

The solution? Wrap the access to the Setting in a layer of abstraction (which I had partially done already.)

class ThatFunctionality
  def self.enabled?
    Setting['that_functionality.enabled']
  end
  def self.enabled=(value)
    Setting['that_functionality.enabled']=value
  end

And then in tests:

before do
  allow(ThatFunctionality).to receive(:enabled?).and_return(true) # or false
end

Trying again to stub RailsSettings

The class method approach is explicit, but once a large set of settings is needed, it can get unwieldy. If you haven’t found a better strategy for wrapping settings values, you can go back to stubbing the :[] method. The caveat is that you need to save the original Settings behavior for any settings that you want to leave unchanged. By stubbing the :[] first to passthrough to .and_call_original, you can add additional stubs on top.


# a module included
module StubSettings
def stub_setting(setting_name, setting_value)
allow(Setting).to receive(:[]).with(setting_name).and_return(setting_value)
end
def stub_setting_assignment
allow(Setting).to receive(:[]=) { |setting, value| stub_setting(setting, setting) }
end
end
# in spec_helper.rb
config.before(:each) do
# detect if Settings being set without you knowing about it
allow(Setting).to receive(:[]=)
.and_raise("stub_setting(setting_name, setting_value) to set the value of a Setting instead")
# passthrough read-only settings calls (necessary if you don't want .with to block other settings
allow(Setting).to receive(:[]).and_call_original
end
# in rspec example
describe 'settings will be set in code invoked from here' do
# automatically stubs assigned value to new stub
before { stub_setting_assignment }
end
# stubbing a setting value
describe 'setting to be forced to a value' do
before { stub_setting('some_toggle_setting', true) }
end


%d bloggers like this: