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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |