module RSpec module Mocks # @api private # Provides methods for enabling and disabling the available syntaxes # provided by rspec-mocks. module Syntax # @api private # # Common stubbing logic for both `stub` and `stub!`. This used to # live in `stub`, and `stub!` delegated to `stub`, but we discovered # that `stub!` was delegating to `RSpec::Mocks::ExampleMethods#stub` # (which declares a test double) when called with an implicit receiver, # which was a regression in 2.14.0. def self.stub_object(object, message_or_hash, opts = {}, &block) if ::Hash === message_or_hash message_or_hash.each {|message, value| stub_object(object, message).and_return value } else opts[:expected_from] = CallerFilter.first_non_rspec_line ::RSpec::Mocks.allow_message(object, message_or_hash, opts, &block) end end # @api private # Enables the should syntax (`dbl.stub`, `dbl.should_receive`, etc). def self.enable_should(syntax_host = default_should_syntax_host) return if should_enabled?(syntax_host) syntax_host.class_eval do def should_receive(message, opts={}, &block) opts[:expected_from] ||= CallerFilter.first_non_rspec_line ::RSpec::Mocks.expect_message(self, message.to_sym, opts, &block) end def should_not_receive(message, &block) opts = { :expected_from => CallerFilter.first_non_rspec_line } ::RSpec::Mocks.expect_message(self, message.to_sym, opts, &block).never end def stub(message_or_hash, opts={}, &block) ::RSpec::Mocks::Syntax.stub_object(self, message_or_hash, opts, &block) end def unstub(message) ::RSpec::Mocks.space.proxy_for(self).remove_stub(message) end def stub!(message_or_hash, opts={}, &block) ::RSpec.deprecate "stub!", :replacement => "stub" ::RSpec::Mocks::Syntax.stub_object(self, message_or_hash, opts, &block) end def unstub!(message) ::RSpec.deprecate "unstub!", :replacement => "unstub" unstub(message) end def stub_chain(*chain, &blk) ::RSpec::Mocks::StubChain.stub_chain_on(self, *chain, &blk) end def as_null_object @_null_object = true ::RSpec::Mocks.proxy_for(self).as_null_object end def null_object? defined?(@_null_object) end def received_message?(message, *args, &block) ::RSpec::Mocks.proxy_for(self).received_message?(message, *args, &block) end unless Class.respond_to? :any_instance Class.class_eval do def any_instance ::RSpec::Mocks.any_instance_recorder_for(self) end end end end end # @api private # Disables the should syntax (`dbl.stub`, `dbl.should_receive`, etc). def self.disable_should(syntax_host = default_should_syntax_host) return unless should_enabled?(syntax_host) syntax_host.class_eval do undef should_receive undef should_not_receive undef stub undef unstub undef stub! undef unstub! undef stub_chain undef as_null_object undef null_object? undef received_message? end Class.class_eval do undef any_instance end end # @api private # Enables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc). def self.enable_expect(syntax_host = ::RSpec::Mocks::ExampleMethods) return if expect_enabled?(syntax_host) syntax_host.class_eval do def receive(method_name, &block) Matchers::Receive.new(method_name, block) end def allow(target) AllowanceTarget.new(target) end def expect_any_instance_of(klass) AnyInstanceExpectationTarget.new(klass) end def allow_any_instance_of(klass) AnyInstanceAllowanceTarget.new(klass) end end RSpec::Mocks::ExampleMethods::ExpectHost.class_eval do def expect(target) ExpectationTarget.new(target) end end end # @api private # Disables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc). def self.disable_expect(syntax_host = ::RSpec::Mocks::ExampleMethods) return unless expect_enabled?(syntax_host) syntax_host.class_eval do undef receive undef allow undef expect_any_instance_of undef allow_any_instance_of end RSpec::Mocks::ExampleMethods::ExpectHost.class_eval do undef expect end end # @api private # Indicates whether or not the should syntax is enabled. def self.should_enabled?(syntax_host = default_should_syntax_host) syntax_host.method_defined?(:should_receive) end # @api private # Indicates whether or not the expect syntax is enabled. def self.expect_enabled?(syntax_host = ::RSpec::Mocks::ExampleMethods) syntax_host.method_defined?(:allow) end # @api private # Determines where the methods like `should_receive`, and `stub` are added. def self.default_should_syntax_host # JRuby 1.7.4 introduces a regression whereby `defined?(::BasicObject) => nil` # yet `BasicObject` still exists and patching onto ::Object breaks things # e.g. SimpleDelegator expectations won't work # # See: https://github.com/jruby/jruby/issues/814 if defined?(JRUBY_VERSION) && JRUBY_VERSION == '1.7.4' && RUBY_VERSION.to_f > 1.8 return ::BasicObject end # On 1.8.7, Object.ancestors.last == Kernel but # things blow up if we include `RSpec::Mocks::Methods` # into Kernel...not sure why. return Object unless defined?(::BasicObject) # MacRuby has BasicObject but it's not the root class. return Object unless Object.ancestors.last == ::BasicObject ::BasicObject end # @method should_receive # Sets an expectation that this object should receive a message before # the end of the example. # # @example # # logger = double('logger') # thing_that_logs = ThingThatLogs.new(logger) # logger.should_receive(:log) # thing_that_logs.do_something_that_logs_a_message # # @note This is only available when you have enabled the `should` syntax. # @method should_not_receive # Sets and expectation that this object should _not_ receive a message # during this example. # @method stub # Tells the object to respond to the message with the specified value. # # @example # # counter.stub(:count).and_return(37) # counter.stub(:count => 37) # counter.stub(:count) { 37 } # # @note This is only available when you have enabled the `should` syntax. # @method unstub # Removes a stub. On a double, the object will no longer respond to # `message`. On a real object, the original method (if it exists) is # restored. # # This is rarely used, but can be useful when a stub is set up during a # shared `before` hook for the common case, but you want to replace it # for a special case. # # @note This is only available when you have enabled the `should` syntax. # @method stub_chain # @overload stub_chain(method1, method2) # @overload stub_chain("method1.method2") # @overload stub_chain(method1, method_to_value_hash) # # Stubs a chain of methods. # # ## Warning: # # Chains can be arbitrarily long, which makes it quite painless to # violate the Law of Demeter in violent ways, so you should consider any # use of `stub_chain` a code smell. Even though not all code smells # indicate real problems (think fluent interfaces), `stub_chain` still # results in brittle examples. For example, if you write # `foo.stub_chain(:bar, :baz => 37)` in a spec and then the # implementation calls `foo.baz.bar`, the stub will not work. # # @example # # double.stub_chain("foo.bar") { :baz } # double.stub_chain(:foo, :bar => :baz) # double.stub_chain(:foo, :bar) { :baz } # # # Given any of ^^ these three forms ^^: # double.foo.bar # => :baz # # # Common use in Rails/ActiveRecord: # Article.stub_chain("recent.published") { [Article.new] } # # @note This is only available when you have enabled the `should` syntax. # @method as_null_object # Tells the object to respond to all messages. If specific stub values # are declared, they'll work as expected. If not, the receiver is # returned. # # @note This is only available when you have enabled the `should` syntax. # @method null_object? # Returns true if this object has received `as_null_object` # # @note This is only available when you have enabled the `should` syntax. # @method any_instance # Used to set stubs and message expectations on any instance of a given # class. Returns a [Recorder](Recorder), which records messages like # `stub` and `should_receive` for later playback on instances of the # class. # # @example # # Car.any_instance.should_receive(:go) # race = Race.new # race.cars << Car.new # race.go # assuming this delegates to all of its cars # # this example would pass # # Account.any_instance.stub(:balance) { Money.new(:USD, 25) } # Account.new.balance # => Money.new(:USD, 25)) # # @return [Recorder] # # @note This is only available when you have enabled the `should` syntax. # @method expect # Used to wrap an object in preparation for setting a mock expectation # on it. # # @example # # expect(obj).to receive(:foo).with(5).and_return(:return_value) # # @note This method is usually provided by rspec-expectations, unless # you are using rspec-mocks w/o rspec-expectations, in which case it # is only made available if you enable the `expect` syntax. # @method allow # Used to wrap an object in preparation for stubbing a method # on it. # # @example # # allow(dbl).to receive(:foo).with(5).and_return(:return_value) # # @note This is only available when you have enabled the `expect` syntax. # @method expect_any_instance_of # Used to wrap a class in preparation for setting a mock expectation # on instances of it. # # @example # # expect_any_instance_of(MyClass).to receive(:foo) # # @note This is only available when you have enabled the `expect` syntax. # @method allow_any_instance_of # Used to wrap a class in preparation for stubbing a method # on instances of it. # # @example # # allow_any_instance_of(MyClass).to receive(:foo) # # @note This is only available when you have enabled the `expect` syntax. # @method receive # Used to specify a message that you expect or allow an object # to receive. The object returned by `receive` supports the same # fluent interface that `should_receive` and `stub` have always # supported, allowing you to constrain the arguments or number of # times, and configure how the object should respond to the message. # # @example # # expect(obj).to receive(:hello).with("world").exactly(3).times # # @note This is only available when you have enabled the `expect` syntax. end end end