Implementing and Testing the Singleton Pattern in Ruby

Implementing and Testing the Singleton Pattern in Ruby

Josh Cheek

October 20, 2012

What is a Singleton Object?

The GoF identifies a singleton as a way to "ensure a class only has one instance, and provide a global point of access to it".

The implementations I provide below will be lax on both of these points.

Here is a minimal example using the GoF's implementation:


class Singleton
		class << self
				def instance
						@instance ||= new
				end

				private :new
		end
end

# access the instance
Singleton.instance # => #

# cannot instantiate
Singleton.new # ~> -:12:in `': private method `new' called for Singleton:Class (NoMethodError)

Note that Ruby also provides a singleton helper module in the stdlib. I dislike it because it's true to the GoF's implementation and singletons == meh.

Singletons == meh

It sounds like a great idea: you have one logger, and everything logs through it. But can you really say that you shouldn't be able to log somewhere else or in some other way if you wanted to? What if you later decide you want database queries to log in one place and http requests to log in another? It's easy to think you'll only ever have one of something, but I can't think of any example where this doesn't risk breaking down.

You will experience difficulty testing these because if they retain state (e.g. a logger with a file_name) then you will need to figure out how to reset that state between tests. If you wind up needing to reset them or change their state for your tests, then can you really say that you won't ever need this in your production code? Can you really say there will only ever be one Rails application, one database, one print spool, and one logger?

The second reason singletons == meh is the global point of access. That's a hard dependency. Hard dependencies are convenient when you have no infrastructure to support dependency injection, but they couple your code. This causes the code depending on the singleton to have direct access to the world it lives in, giving up its modularity and its independence from its environment. This makes it difficult to use the code in novel way. It effectively turns the singleton into a global variable.

What to do about it?

I've provided a number of implementations which show how to can get around the singleness of the object.

To get around the problems of a global point of access, opt for some form of dependency injection rather than accessing these objects directly. You can pass the singleton in when initializing, or use one of the many gems that address this problem.

Here are six approaches and example tests demonstrating their use:

1. Make the singleton an instance of some other class

This approach frees that class from having to manage the single point of access to its instances. Then you can test the other class in isolation, and don't need to test your singleton explicitly. This one is my favourite, which is why it is first.

class Configuration
		attr_writer :credentials_file

		def credentials_file
				@credentials_file || raise("credentials file not set")
		end
end

MyConfig = Configuration.new # Somewhere in your app

describe Configuration do
		let(:config) { Configuration.new }
		describe 'credentials_file' do
				specify 'it can be set' do
						config.credentials_file = 'abc'
						config.credentials_file.should == 'abc'
				end

				specify 'raises an error if accessed before being initialized' do
						expect { config.credentials_file }.to raise_error 'credentials file not set'
				end
		end
end

2. Provide an instance of the class, but allow the class to be instantiated

This is in line with the way singletons are traditionally presented, except it does not prevent instantiation. Any time you want to refer to the singleton, you talk to the singleton instance, but you can test against other instances.

class Configuration
		def self.instance
				@instance ||= new
		end

		attr_writer :credentials_file

		def credentials_file
				@credentials_file || raise("credentials file not set")
		end
end

# to access the singleton
Configuration.instance # => #

describe Configuration do
		let(:config) { Configuration.new } # test against fresh instance

		specify '.instance always refers to the same instance' do
				Configuration.instance.should be_a_kind_of(Configuration)
				Configuration.instance.should equal(Configuration.instance)
		end

		describe 'credentials_file' do
				specify 'it can be set' do
						config.credentials_file = 'abc'
						config.credentials_file.should == 'abc'
				end

				specify 'raises an error if accessed before being initialized' do
						expect { config.credentials_file }.to raise_error('credentials file not set')
				end
		end
end

3. Subclass the singleton in tests

This allows you to change the state of the subclass without effecting the state of the singleton. This is useful when the object is a class, a pattern which is best to avoid, but is not uncommon.

class Configuration
		def self.credentials_file=(credentials_file)
				@credentials_file = credentials_file
		end

		def self.credentials_file
				@credentials_file || raise("credentials file not set")
		end
end

describe Configuration do
		let(:config) { Class.new Configuration }
		describe 'credentials_file' do
				specify 'it can be set' do
						config.credentials_file = 'abc'
						config.credentials_file.should == 'abc'
				end

				specify 'raises an error if accessed before being initialized' do
						expect { config.credentials_file }.to raise_error 'credentials file not set'
				end
		end
end

4. Ensure that there is a way to reset the singleton.

Make sure any changes to the singlenton's state can be undone. (e.g. if you can register some object with the singleton, then you need to be able to unregister it. In Rails, for example, when you subclass Railtie it records that in an array, but you can access the array and delete the item from it).

class Configuration
		def self.reset
				@credentials_file = nil
		end

		class << self
				attr_writer :credentials_file
		end

		def self.credentials_file
				@credentials_file || raise("credentials file not set")
		end
end

RSpec.configure do |config|
		config.before { Configuration.reset }
end

describe Configuration do
		describe 'credentials_file' do
				specify 'it can be set' do
						Configuration.credentials_file = 'abc'
						Configuration.credentials_file.should == 'abc'
				end

				specify 'raises an error if accessed before being initialized' does
						expect { Configuration.credentials_file }.to raise_error 'credentials file not set'
				end
		end
end

5. Clone the class instead of testing it directly.

This came out of a gist I made. After cloning the class, you edit the clone instead of the original class. This solution works around some of the difficulties of singletons rather than addressing their problems directly. It has caveats which could cause bugs. For example if the class has instance variables, you need to worry about whether what happens to the clone could affect variables that the cloned class points to. There is no real deep copy in Ruby.

class Configuration
		def self.credentials_file=(credentials_file)
				@credentials_file = credentials_file
		end

		def self.credentials_file
				@credentials_file || raise("credentials file not set")
		end
end

describe Configuration do
		let(:configuration) { Configuration.clone }

		describe 'credentials_file' do
				specify 'it can be set' do
						configuration.credentials_file = 'abc'
						configuration.credentials_file.should == 'abc'
				end

				specify 'raises an error if accessed before being initialized' do
						expect { configuration.credentials_file }.to raise_error 'credentials file not set'
				end
		end
end

6. Develop the behaviour in modules, then extend that onto the singleton.

You would probably need to look into the self.included and self.extended methods if you needed to initialize some variables on the object. This creates more code and disassociates the objects from their methods which may force you to traverse several files before you find the code that defines the method you're interested in. It's also an extra stop along the call chain. This gist has a slightly more involved example.

module ConfigurationBehaviour
		attr_writer :credentials_file

		def credentials_file
				@credentials_file || raise("credentials file not set")
		end
end

# Somewhere in your app
class Configuration
		extend ConfigurationBehaviour
end

describe Configuration do
		let(:configuration) { Class.new.extend ConfigurationBehaviour }

		describe 'credentials_file' do
				specify 'it can be set' do
						configuration.credentials_file = 'abc'
						configuration.credentials_file.should == 'abc'
				end

				specify 'raises an error if accessed before being initialized' do
						expect { configuration.credentials_file }.to raise_error 'credentials file not set'
				end
		end
end

The End <3

I think my preferred solution would be the ability to instantiate singleton classes. As this doesn't exist, hopefully one of these solutions will meet your needs. I've used several of them and recommend the first solution, but there are times when the others have been more appropriate. Choose the one that works for you.