Stubbing Class Constants when Testing

Today i ran into a spec that needed to override a class constant in order to assert that its members are called by an iterator and used inside the code. It wasn’t simply an implementation detail as i needed to make sure everything was working as it should in a kind of complex environment.

At first, i thought of performing a class_eval to reset the constant, which did not work. Later on i’ve used const_set and as it worked it would send a warning that looked pretty ridiculous right in the middle of my specs: warning: already initialized constant A

A few hours later, after running into ruby-doc several times, i created a solution that seems to be working like a charm (at least until now!). It was made with the RSpec processing standards in mind but it should work with other testing frameworks as well by using the same logic:

# Stubbing class constants
describe "stub class constants" do
  before(:all) do
    @new_constant = mock('new_constant')
    @old_constant = ClassThatHasTheConstant::Constant  
    # saves the old constant for future resetting
    ClassThatHasTheConstant.__send__(:remove_const,'Constant') 
    # tells the class to remove the constant through __send__ because :remove_const is a private method
    ClassThatHasTheConstant.const_set('Constant', @new_constant)  # set a new constant with a new value
  end
  [ ... ] # your examples here
  after(:all) do
    ClassThatHasTheConstant.__send__(:remove_const,'Constant')
    ClassThatHasTheConstant.const_set('Constant', @old_constant)
  end
end

Is there a better way to do this? Am i violating any principles of good programming? Please comment below! :)

  • http://www.facebook.com/fraga.raphael Raphael Augusto Fraga

    It would be even better if I could understand any of this!!!

    • http://rubynoobie.wordpress.com/ Lucas d’Acampora Prim

      c’mon! it ain’t that hard :P

  • http://njclarke.com Norman Clarke

    A simpler technique is to create a class or instance method that returns the constant and stub/mock that:

    class Bar
    MAX_CUSTOMERS = 150

    def max_customers
    MAX_CUSTOMERS
    end
    end

    Alternatively, you can use a class variable, which is easier to change in your tests.

    Constants are ideal for truly constant things like PI, but as you can see are inconvenient for things that can at least in theory change. In my experience, if you find you need to change them, even in tests, that’s often an indicator that your best bet is to place the value in something other than a constant.

    • http://rubynoobie.wordpress.com/ Lucas d’Acampora Prim

      I really like your approach! It´s simpler and allows more decoupling than my own does!

  • http://pragmatig.wordpress.com grosser

    more readable:

    before do
    @old = Foo::Bar
    silence_warnings{ Foo::Bar = xxx }
    end

    after do
    silence_warnings{ Foo::Bar = @old }
    end

    • http://rubynoobie.wordpress.com/ Lucas d’Acampora Prim

      Really nice! That’s for folks using Rails 3.x, right?
      Now that you’ve opened this gate, i found out that you can also do like this:

      before(:all) do
      @old_verbose, $VERBOSE = $VERBOSE, nil
      @old = Foo::Bar
      Foo::Bar = xxx
      end
      after(:all) do
      $VERBOSE = @old_verbose
      Foo::Bar = @old
      end

      Taken from the Kernel module From Rails 3 :)

  • Avijayr

    Sorry – I dont totally agree. Your implementation is good only when you have to run ALL test methods within the same spec file/describe block with the new set of [constant] values. In my experience, this value-changing is needed only for a small subset of tests for the class/module that you are testing. To accompllish this, I wrote a slightly different method here: .

    • http://rubynoobie.wordpress.com/ Lucas d’Acampora Prim

      Really liked your solution! It is indeed very elegant and i am using it right now on a project i’m running :)

  • Pingback: Vijay R. Aravamudhan: Stubbing constants for tests | Software Secret Weapons

  • http://chesmart.in ches

    For a modern-day update on the problem, RSpec has constant stubbing support since 2.11.