Implementing and Testing the Singleton Pattern in Ruby

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:

 1 class Singleton
 2   class << self
 3     def instance
 4       @instance ||= new
 5     end
 6 
 7     private :new
 8   end
 9 end
10 
11 # access the instance
12 Singleton.instance # => #<Singleton:0x007fd70c889880>
13 
14 # cannot instantiate
15 Singleton.new # ~> -:12:in `<main>': 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.

 1 class Configuration
 2   attr_writer :credentials_file
 3 
 4   def credentials_file
 5     @credentials_file || raise("credentials file not set")
 6   end
 7 end
 8 
 9 MyConfig = Configuration.new # Somewhere in your app
10 
11 describe Configuration do
12   let(:config) { Configuration.new }
13   describe 'credentials_file' do
14     specify 'it can be set' do
15       config.credentials_file = 'abc'
16       config.credentials_file.should == 'abc'
17     end
18 
19     specify 'raises an error if accessed before being initialized' do
20       expect { config.credentials_file }.to raise_error 'credentials file not set'
21     end
22   end
23 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.

 1 class Configuration
 2   def self.instance
 3     @instance ||= new
 4   end
 5 
 6   attr_writer :credentials_file
 7 
 8   def credentials_file
 9     @credentials_file || raise("credentials file not set")
10   end
11 end
12 
13 # to access the singleton
14 Configuration.instance # => #<Configuration:0x007fe2dc08a7e8>
15 
16 describe Configuration do
17   let(:config) { Configuration.new } # test against fresh instance
18 
19   specify '.instance always refers to the same instance' do
20     Configuration.instance.should be_a_kind_of Configuration
21     Configuration.instance.should equal Configuration.instance
22   end
23 
24   describe 'credentials_file' do
25     specify 'it can be set' do
26       config.credentials_file = 'abc'
27       config.credentials_file.should == 'abc'
28     end
29 
30     specify 'raises an error if accessed before being initialized' do
31       expect { config.credentials_file }.to raise_error 'credentials file not set'
32     end
33   end
34 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.

 1 class Configuration
 2   def self.credentials_file=(credentials_file)
 3     @credentials_file = credentials_file
 4   end
 5 
 6   def self.credentials_file
 7     @credentials_file || raise("credentials file not set")
 8   end
 9 end
10 
11 describe Configuration do
12   let(:config) { Class.new Configuration }
13   describe 'credentials_file' do
14     specify 'it can be set' do
15       config.credentials_file = 'abc'
16       config.credentials_file.should == 'abc'
17     end
18 
19     specify 'raises an error if accessed before being initialized' do
20       expect { config.credentials_file }.to raise_error 'credentials file not set'
21     end
22   end
23 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).

 1 class Configuration
 2   def self.reset
 3     @credentials_file = nil
 4   end
 5 
 6   class << self
 7     attr_writer :credentials_file
 8   end
 9 
10   def self.credentials_file
11     @credentials_file || raise("credentials file not set")
12   end
13 end
14 
15 RSpec.configure do |config|
16   config.before { Configuration.reset }
17 end
18 
19 describe Configuration do
20   describe 'credentials_file' do
21     specify 'it can be set' do
22       Configuration.credentials_file = 'abc'
23       Configuration.credentials_file.should == 'abc'
24     end
25 
26     specify 'raises an error if accessed before being initialized' do
27       expect { Configuration.credentials_file }.to raise_error 'credentials file not set'
28     end
29   end
30 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.

 1 class Configuration
 2   def self.credentials_file=(credentials_file)
 3     @credentials_file = credentials_file
 4   end
 5 
 6   def self.credentials_file
 7     @credentials_file || raise("credentials file not set")
 8   end
 9 end
10 
11 describe Configuration do
12   let(:configuration) { Configuration.clone }
13 
14   describe 'credentials_file' do
15     specify 'it can be set' do
16       configuration.credentials_file = 'abc'
17       configuration.credentials_file.should == 'abc'
18     end
19 
20     specify 'raises an error if accessed before being initialized' do
21       expect { configuration.credentials_file }.to raise_error 'credentials file not set'
22     end
23   end
24 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.

 1 module ConfigurationBehaviour
 2   attr_writer :credentials_file
 3   def credentials_file
 4     @credentials_file || raise("credentials file not set")
 5   end
 6 end
 7 
 8 # Somewhere in your app
 9 class Configuration
10   extend ConfigurationBehaviour
11 end
12 
13 describe Configuration do
14   let(:configuration) { Class.new.extend ConfigurationBehaviour }
15 
16   describe 'credentials_file' do
17     specify 'it can be set' do
18       configuration.credentials_file = 'abc'
19       configuration.credentials_file.should == 'abc'
20     end
21 
22     specify 'raises an error if accessed before being initialized' do
23       expect { configuration.credentials_file }.to raise_error 'credentials file not set'
24     end
25   end
26 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.