tags:

views:

95

answers:

3

Consider the following, StubFoo is a stub of Foo, which I wish to stub out for some tests.

class Runner

  def run
      Foo = StubFoo
      foo = Foo.new
      # using Foo...
  end

end

This generates the following error message: Dynamic constant assignment

Yet, in RSpec I can do the following, which works and is perfectly legal:

it "should be an example" do
  Foo = StubFoo
  foo = Foo.new
  foo.to_s.should == "I am stubbed!"
end

A few questions regarding this.

  • Why does this work with the RSpec test case, but not the method above?
  • As far as I'm aware, "it" is just a method within RSpec, yet I'm able to redeclare a constant within the "method".

I'm doing this prior to using a mocking framework for purely wanting to know how mocking, stubbing etc... is different in Ruby. I hear that dynamic languages are easier to mock/stub, and there are guides on the Internet in which simple class reassignment is done as above. From my research, in Ruby it's not possible to declare constants within a method, yet I'm confused as mentioned above.

Edit

Right, this is starting to make more sense. I've updated run to be using const_set now.

  def run
      old = Foo
      self.class.const_set(:Foo, StubFoo)
      foo = Foo.new
      puts foo.to_s
      self.class.const_set(:Foo, old)
      foo = Foo.new
      puts foo.to_s
  end

This generates a warning however, is this what/how mocking frameworks work then in Ruby? Obviously much more elegant and feature full, but do they simply repress this warning?

A: 

You are getting the error because if Runner#run is called, Foo will be changed unexpectedly on the caller. For example:

class C; end

def add_two_to(x) # this method is defined externally, and you cannot change it.
  C = Class.new {} # error
  x + 2
end

x = C.new
puts add_two_to(5)
y = C.new

x.is_a? y.class # would be false if the code get here

The caller of add_two_to does not expect C to be redefined. It just makes more sense that the last line of the above code will always be true. In your second example, your "method" is not actually a method, but a block. If you run the block twice (which it will not do), you will get this error:

warning: already initialized constant Q
Adrian
That's not an error. It's a warning. The program will still run perfectly fine.
sepp2k
+2  A: 

You can't reassign constants in method definitions using Constant = value. You can however reassign them using const_set. Basically it's meant to discourage, but not disallow, dynamic constant reassignment.

As to why it works with it: Yes, it is a method, but you're not defining, you're calling it. The reassignment takes place inside the block which you pass as an argument to it. And inside blocks it's perfectly possible to reassign constants like this (which is why you also can reassign constants when defining a method using define_method instead of def).

sepp2k
A: 

Because in your test, you are not defining a method, but rather calling it.

Ruby detects you are defining a constant in a method, that can be run multiple times, hence the warning. If you call the method more than once, you'll get a warning: already initialized constant Foo warning. This is because they're supposed to be constants. (What would happen, for example, if you defined Foo = Time.now?)

Chubas