Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to check whether a method is stubbed with RSpec?

Tags:

rspec

Mainly for test debugging purposes, I would like to know whether there is a way to tell whether a method is stubbed or not.

For example, in one of my tests, I write:

Model.any_instance.stub(:method)

And when I have a real instance I want to write something like:

an_instance_of_model.stubbed?(:method) # expecting to return true
like image 526
p.matsinopoulos Avatar asked May 23 '13 09:05

p.matsinopoulos


People also ask

What is stubbing in RSpec?

In RSpec, a stub is often called a Method Stub, it's a special type of method that “stands in” for an existing method, or for a method that doesn't even exist yet.

What is the difference between stubs and mocks in Ruby testing?

Stub: A class or object that implements the methods of the class/object to be faked and returns always what you want. Mock: The same of stub, but it adds some logic that "verifies" when a method is called so you can be sure some implementation is calling that method.

What is allow in RSpec?

Use the allow method with the receive matcher on a test double or a real. object to tell the object to return a value (or values) in response to a given. message. Nothing happens if the message is never received.

What is a mock in Ruby?

A mock is an object used for testing. You use mocks to test the interaction between two objects. Instead of testing the output value, like in a regular expectation.


2 Answers

In case you have stubbed the method in class level with the any_instance method you may check this using something like this in your test:

RSpec::Mocks.space.any_instance_recorder_for(YourClass).already_observing?(:method_name)

but if you stubbed a specific instance, you can find this out by using the following:

!(your_instance.method(:method_name).owner >= your_instance.class)

So you may combine these too in a helper module:

module Helpers
  def stubbed?(object, method)
    RSpec::Mocks.space.any_instance_recorder_for(object.class).already_observing?(method) || !(object.method(method).owner >= object.class)
  end
end

and include it in your RSpec.configure:

require 'helpers'
RSpec.configure do |config|
  ...
  config.include Helpers
  ...
end

NB: This has been updated based on @PJSCopeland's comment. If you're using a version of RSpec less than 3.6, remove the .space calls, i.e. RSpec::Mocks.any_instance_recorder_for(YourClass).already_observing?(:method_name)

like image 54
Lazarus Lazaridis Avatar answered Oct 05 '22 02:10

Lazarus Lazaridis


tl;dr

While it doesn't appear to be documented very well, following on from @PJSCopeland's comment, it appears you can use RSpec::Mocks.space.registered? to check if a 'mock proxy' exists:

# eg. allow(Rails.configuration.action_controller).to receive(:allow_forgery_protection).and_return(true)

RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true

RSpec::Mocks.space.registered?(Rails.configuration)
# => false

Going Deeper

In a pry debug session we can see:

ls RSpec::Mocks
# => RSpec::Mocks.methods: allow_message  configuration  error_generator  expect_message  setup  space  teardown  verify  with_temporary_scope

ls RSpec::Mocks.space
# => RSpec::Mocks::Space#methods: any_instance_mutex  any_instance_proxy_for  any_instance_recorder_for  any_instance_recorders  any_instance_recorders_from_ancestry_of  constant_mutator_for  ensure_registered  new_scope  proxies  proxies_of  proxy_for  proxy_mutex  register_constant_mutator  registered?  reset_all  superclass_proxy_for  verify_all

Looking at https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/space.rb#L127-L129 we can see that registered? checks proxies.key?, where proxies is an attr on the RSpec::Space class:

module RSpec
  module Mocks
    # ..snip..
    class Space
      attr_reader :proxies, :any_instance_recorders, :proxy_mutex, :any_instance_mutex
      # ..snip..
      def registered?(object)
        proxies.key?(id_for object)
      end
      # ..snip..
    end
  end
end

In pry, we can see that proxies is a Hash that maps an id number (created by id_for) to the RSpec::Mocks::Proxy instance representing that mock:

RSpec::Mocks.space.proxies.class
# => Hash

RSpec::Mocks.space.proxies.map { |k,v| v.class }
# => [RSpec::Mocks::PartialClassDoubleProxy, RSpec::Mocks::PartialDoubleProxy]

As you can see from the available methods, this would give us pretty deep access to play around with the mocks if we wanted to:

ls RSpec::Mocks.space.proxies.map { |k,v| v }.first
# RSpec::Mocks::Proxy#methods: add_message_expectation  add_stub  as_null_object  build_expectation  check_for_unexpected_arguments  ensure_implemented  has_negative_expectation?  messages_arg_list  method_double_if_exists_for_message  null_object?  object  prepended_modules_of_singleton_class  raise_missing_default_stub_error  raise_unexpected_message_error  received_message?  record_message_received  remove_stub  remove_stub_if_present  replay_received_message_on  verify
# RSpec::Mocks::PartialDoubleProxy#methods: add_simple_expectation  add_simple_stub  message_received  reset  visibility_for
# RSpec::Mocks::PartialClassDoubleProxyMethods#methods: original_method_handle_for

Example: Resetting a mocked value

For example, if we wanted to reset this mock back to it's unmocked default value, we can use the RSpec::Mocks.space.proxy_for helper to find our mock, then reset it:

# when
#   Rails.configuration.action_controller.allow_forgery_protection == false
# and
#   allow(Rails.configuration.action_controller).to receive(:allow_forgery_protection).and_return(true)

RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true

Rails.configuration.action_controller.allow_forgery_protection
# => true

RSpec::Mocks.space.proxy_for(Rails.configuration.action_controller).reset

Rails.configuration.action_controller.allow_forgery_protection
# => false

Notice however that the even though the mock value has been reset, the mock remains registered?:

RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true
like image 44
Glenn 'devalias' Grant Avatar answered Oct 05 '22 01:10

Glenn 'devalias' Grant