rspec / rspec-mocks

RSpec's 'test double' framework, with support for stubbing and mocking
https://rspec.info
MIT License
1.16k stars 357 forks source link

stub_const doesn't work with constant inside `class << self` block #1536

Open s101d1 opened 1 year ago

s101d1 commented 1 year ago

I'm not sure if this is a bug or not, I found out the stub_const doesn't work with constant inside class << self block.

class HelloWorld

    class << self
        FOO = "hello"
    end

end

describe HelloWorld do 
    context "Testing the HelloWorld" do 

        it "testing" do 
            p HelloWorld.singleton_class::FOO   # print "hello"
            stub_const("HelloWorld.singleton_class::FOO", "world")  # error at here
            p HelloWorld.singleton_class::FOO   # expected to print "world" at here if there is no error
        end

   end
end

The stub_const("HelloWorld.singleton_class::FOO", "world") line gives this error:

 Failure/Error: stub_const("HelloWorld.singleton_class::FOO", "world")

 NameError:
   wrong constant name HelloWorld.singleton_class

             mod.const_defined?(const_name, false)

Is there a workaround other than moving the FOO = "hello" outside the class << self block?

JonRowe commented 1 year ago

This isn't a bug so much, more of a lack of feature, HelloWorld.singleton_class returns a class, not a constant name, and stub_const takes a string representing a constant name, so the two apis are incompatible, we don't evaluate the string but use it to find the constant and replace it via the Ruby api's for doing so.

It's probably possible to expand the api to cope with this scenario if you wanted to tackle it, I'm a bit unsure if we'd want to but I'm open to discussing that further...

I'm sort of curious why you even want to define a constant within the singleton class, that seems a bit unusual and very much an edge case?

s101d1 commented 1 year ago

I'm sort of curious why you even want to define a constant within the singleton class, that seems a bit unusual and very much an edge case?

I was creating a module actually (the "HelloWorld" class in above example is actually a module in the real case). So it looks something like this:

module HelloWorld

    class << self
        FOO = ...

    end

    # OtherClass and OtherClass2 don't use the FOO constant at all
    class OtherClass
        ...
    end

    class OtherClass2
        ...
    end
end

Since the FOO constant is only used by the methods inside the class << self block, I feel it more makes sense to define the constant inside the class << self rather than on the module level.

JonRowe commented 1 year ago

If you used module_function to create functions on HelloWorld you'd have access to HelloWorld::FOO