1

Contrary to the documentation on .stubs, it seems that I'm able to stub a method that doesn't exist.

Considering the following code:

class DependencyClass
  def self.method_to_be_stubbed
    'hello'
  end
end

class TestMethodClass
  def self.method_being_tested
    DependencyClass.method_to_be_stubbed
  end
end

class StubbedMissingMethodTest < ActiveSupport::TestCase
  test '#method_being_tested should return value from stub' do
    assert_equal 'hello', TestMethodClass.method_being_tested

    DependencyClass.stubs(:method_to_be_stubbed).returns('goodbye')

    assert_equal 'goodbye', TestMethodClass.method_being_tested
  end
end

In this example, DependencyClass.stubs(:method_to_be_stubbed).returns('goodbye') works as expected because #method_to_be_stubbed exists on DependencyClass. However, if I were to change #method_to_be_stubbed to a class instance method of DependencyClass as follows:

class DependencyClass
  def method_to_be_stubbed
    'hello'
  end
end

class StubbedMissingMethodTest < ActiveSupport::TestCase
  test '#method_being_tested should return value from stub' do
    assert_equal 'hello', TestMethodClass.method_being_tested

    # despite the method not existing on the class,
    # instead on the instance - yet it still works?
    DependencyClass.stubs(:method_to_be_stubbed).returns('goodbye')

    assert_equal 'goodbye', TestMethodClass.method_being_tested
  end
end

My stub for #method_to_be_stubbed maintains the class method on DependencyClass, despite it not existing any longer. Is the expected behaviour not for the .stubs call to fail, since the method being stubbed does not exist?

Xenyal
  • 2,136
  • 2
  • 24
  • 51
  • Is that your actual code? the documentation you linked have a `.stub` method, not a `.stubs` method. I also cant' find that `returns` method. Are you using some extension? – arieljuod Dec 14 '19 at 04:03
  • @arieljuod I actually flagged this question for closure due to duplication from https://stackoverflow.com/q/7211086/3157745. I got confused between `.stubs` which behaves similarly with `.expects`, but shouldn't be confused with `.stub` which is different altogether (and happened to be what I was searching for). – Xenyal Dec 14 '19 at 04:24

1 Answers1

1

Is the expected behaviour not for the .stubs call to fail, since the method being stubbed does not exist?

No, expected behaviour is not to fail. Here's why. You're not stubbing a method. You're stubbing a response to a message. For example, you have this line in your code: user.name. This means you're sending message age to object user. The easiest/most common way to teach user to handle message age is to ensure that it has an instance method called age. But there are other ways too. You could make user respond to age with method_missing. As far as ruby is concerned, this is just as valid.

Therefore, it would be wrong for minitest to check for method's existence here.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • Can you clarify what 'messages are handled by methods of the same name' means? I could validate that the method exists through `DependencyClass.respond_to?(:method_to_be_stubbed)`, but I figure `.stubs` would at least check what I'm overriding before overriding it. – Xenyal Dec 14 '19 at 00:17
  • @Xenyal: Say, you have this line in your code: `user.name`. This means you're sending message `age` to object `user`. The easiest/most common way to teach `user` to handle message `age` is to ensure that it has an instance method called `age`. But, as I mentioned, you could make `user` respond to `age` with `method_missing`. As far as ruby is concerned, this is just as valid. – Sergio Tulentsev Dec 14 '19 at 00:20
  • What if we consider an example that uses dependency injection where `user` is an input, and we rely on a library function of the class to retrieve the `age` attr? (eg. `self.get_user_name(user)`) Also, I believe class instance method stubbing does actually check for existence on the instance. How come class methods behave differently? – Xenyal Dec 14 '19 at 00:30
  • @Xenyal: "I believe class instance method stubbing does actually check for existence on the instance" - does it, though? – Sergio Tulentsev Dec 14 '19 at 00:40
  • I apologize, that was my misconception (It does not). – Xenyal Dec 14 '19 at 00:41
  • Alternatively, it seems that Minitest `SomeClass.stub :method` does in fact check for the method to exist on the class, otherwise it raises a `NameError`. However, it's usage does differ from that of a mock since 1. there isn't an obvious way of validating the `.with` parameters, and 2. there isn't an obvious way for the code to result in a `.raises(SomeError)`. Ref: https://github.com/seattlerb/minitest/blob/master/lib/minitest/mock.rb#L212 – Xenyal Dec 14 '19 at 00:52