Consider stubs on superclass receivers if none exist on primary receiver. #175

Closed
wants to merge 1 commit into from

2 participants

@floehopper
Go Free Range member

Note: This may break existing tests which rely on the old behaviour!

Stubbing a superclass method and then invoking that method on a child
class would previously cause an unexpected invocation error. This was
because the superclass and child class delegated to different mock
objects i.e. the superclass has its own @mocha class instance variable
which is different from the child class' @mocha class instance
variable.

By searching up through the inheritance hierarchy for each of the delegate
mock objects, we can provide more intuitive behaviour. Instead of
an unexpected invocation error (see above), invoking the method on the
child class will cause the stubbed method on the superclass to be used.

class Parent
  def self.my_class_method
    :original_value
  end
end

class Child < Parent
end

Parent.stubs(:my_class_method).returns(:stubbed_value)

# old behaviour
Child.my_class_method # => unexpected invocation error

# new behaviour
Child.my_class_method # => :stubbed_value

For consistency, I have also implemented a similar change for the
corresponding any_instance scenario:

class Parent
  def my_instance_method
    :original_value
  end
end

class Child < Parent
end

Parent.any_instance.stubs(:my_instance_method).returns(:stubbed_value)

# old behaviour
Child.new.my_instance_method # => unexpected invocation error

# new behaviour
Child.new.my_instance_method # => :stubbed_value

These changes were largely based on changes suggested by @ccutrer in #145.

@floehopper floehopper Consider stubs on superclasses if none exist on primary receiver.
Note: This may break existing tests which rely on the old behaviour!

Stubbing a superclass method and then invoking that method on a child
class would previously cause an unexpected invocation error. This was
because the superclass and child class delegated to different mock
objects i.e. the superclass has its own `@mocha` class instance variable
which is different from the child class' `@mocha` class instance
variable.

By searching up through the inheritance hierarchy for each of the delegate
mock objects, we can provide more intuitive behaviour. Instead of
an unexpected invocation error (see above), invoking the method on the
child class will cause the stubbed method on the superclass to be used.

    class Parent
      def self.my_class_method
        :original_value
      end
    end

    class Child < Parent
    end

    Parent.stubs(:my_class_method).returns(:stubbed_value)

    # old behaviour
    Child.my_class_method # => unexpected invocation error

    # new behaviour
    Child.my_class_method # => :stubbed_value

For consistency, I have also implemented a similar change for the
corresponding `any_instance` scenario:

    class Parent
      def my_instance_method
        :original_value
      end
    end

    class Child < Parent
    end

    Parent.any_instance.stubs(:my_instance_method).returns(:stubbed_value)

    # old behaviour
    Child.new.my_instance_method # => unexpected invocation error

    # new behaviour
    Child.new.my_instance_method # => :stubbed_value

These changes were largely based on changes suggested by @ccutrer in #145.
c72d5a7
@floehopper floehopper was assigned Dec 30, 2013
@ccutrer

This appears to work for me at first glance; I'm running our full test suite now.

@floehopper
Go Free Range member

@ccutrer Great - thanks!

@ccutrer

The full test suite passed as well. Thanks!

@floehopper
Go Free Range member

Good news. I hope to get it released soon. Thanks.

@floehopper
Go Free Range member

This has been released in v1.0.0.alpha. Closing for now.

@floehopper floehopper closed this Dec 31, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment