How to test a Singleton class?

2020-08-12 18:49发布

问题:

I am using RSpec and want to test the constructor of a Singleton class more than one time.

How can I do this?

Best regards

回答1:

have a look at http://blog.ardes.com/2006/12/11/testing-singletons-with-ruby:

require 'singleton'

class <<Singleton
  def included_with_reset(klass)
    included_without_reset(klass)
    class <<klass
      def reset_instance
        Singleton.send :__init__, self
        self
      end
    end
  end
  alias_method :included_without_reset, :included
  alias_method :included, :included_with_reset
end


回答2:

Singleton classes essentially do this

def self.instance
  @instance ||= new
end

private_class_method :new

So you can bypass the memoization altogether by calling the private method new using send

let(:instance) { GlobalClass.send(:new) }

A nice benefit of this way is that no global state is modified as a result of your tests running.


Probably a better way, from this answer:

let(:instance) { Class.new(GlobalClass).instance }

This creates an anonymous class which inherits from GlobalClass, which all class-level instance variables are stored in. This is then thrown away after each test, leaving GlobalClass untouched.



回答3:

# singleton_spec.rb
require "singleton"

class Global
  include Singleton

  def initialize
    puts "Initializing"
  end
end

describe Global do
  before do
    Singleton.__init__(Global)
  end

  it "test1" do
    Global.instance
  end

  it "test2" do
    Global.instance
  end
end


% rspec singleton_spec.rb -fd
Global
Initializing
  test1
Initializing
  test2


回答4:

One pattern I've seen is having the singleton be a sub-class of the real class. You use the Singleton version in production code, but the base (non-singleton) class for testing.

Example:

class MyClass
  attr_accessor :some_state

  def initialize
     @some_state = {}
  end
end

class MySingletonClass < MyClass
  include Singleton
end

...but I'm looking for a better way myself.

Part of my problem is that I'm using JRuby and hooking into the Java System Preferences, which are global. The rest I think I can refactor out.



回答5:

One reason people use singletons because "global variables are bad, m'kay?" A singleton is a global variable, sequestered in a name space, and with lazy instantiation. Consider whether a true global variable might simplify things, especially if you don't need lazy instantiation.



回答6:

Refactor it into a class that can be constructed multiple times. This has the side-effect (some would say benefit) of removing the Singleton nature from the class.

Look at it another way: you've found a need to call the constructor more than once. Why should the class only construct one instance? What benefit is Singleton providing?



回答7:

Does RSpec allow you do perform pre-test actions? Is so, you could add another method to you static class that cleared anything done during the constructor. Then just call that prior to each and every test.



回答8:

You can simply make a new it and block for each spec. Break down your spec to a testable unit. Use "before" and "after" to set up and clears up anything you did.

before(:each) and after(:each) are executed for every it block.