Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

base fork: rspec/rspec-mocks
...
head fork: rspec/rspec-mocks
  • 3 commits
  • 24 files changed
  • 0 commit comments
  • 1 contributor
Commits on Sep 17, 2012
Myron Marston myronmarston Merge bug-fix commits from master into 2-11-maintenance.
This is in prep for the 2.11.3 release.

For future reference, I used the following steps to do this, starting
from a checkout of master:

* `git checkout -b bug_fixes_from_master`
* `git rebase -i v2.11.0`
* Removed the non-bug fix commits from the list of commits
  during interactive rebase.
* `git checkout 2-11-maintenance`
* `git merge bug_fixes_from_master`

Conflicts:
	Changelog.md
	Gemfile-custom.sample
	lib/rspec/mocks/any_instance/recorder.rb
	lib/rspec/mocks/stub_const.rb
	spec/rspec/mocks/any_instance_spec.rb
76a635c
Commits on Sep 19, 2012
Myron Marston myronmarston Fix confusing error message.
When a class method was mocked and it was called an extra time with the wrong arguments, the error being raised was:

"NoMethodError: undefined method `bar' for Object:Class"

...which was very confusing.
767acd0
Commits on Sep 20, 2012
Myron Marston myronmarston 2.11.3 release. d0bcce4
1  .gitignore
View
@@ -13,3 +13,4 @@ Gemfile.lock
.yardoc
bin
Gemfile-custom
+bundle
2  .travis.yml
View
@@ -1,5 +1,5 @@
bundler_args: "--binstubs"
-script: "bin/rake --trace 2>&1"
+script: "script/test_all && bin/rake --trace 2>&1"
rvm:
- 1.8.7
- 1.9.2
14 Changelog.md
View
@@ -1,3 +1,17 @@
+### 2.11.3 / 2012-09-19
+[full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.2...v2.11.3)
+
+Bug fixes
+
+* Fix `:transfer_nested_constants` option of `stub_const` so that it
+ doesn't blow up when there are inherited constants. (Myron Marston)
+* `any_instance` stubs can be used on classes that override `Object#method`.
+ (Andy Lindeman)
+* Methods stubbed with `any_instance` are unstubbed after the test finishes.
+ (Andy Lindeman)
+* Fix confusing error message when calling a mocked class method an
+ extra time with the wrong arguments (Myron Marston).
+
### 2.11.2 / 2012-08-11
[full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.1...v2.11.2)
23 Gemfile-custom.sample
View
@@ -1,34 +1,19 @@
group :development do
gem 'interactive_rspec'
gem 'relish', '~> 0.6.0'
- gem 'guard-rspec', '0.5.0'
+ gem 'guard-rspec', '~> 1.2.1'
gem 'growl', '1.0.3'
gem 'spork', '0.9.0'
platform :mri do
- gem 'rb-fsevent'
+ gem 'rb-fsevent', '~> 0.9.0'
gem 'ruby-prof', '~> 0.10.0'
case RUBY_VERSION
when /^1.8/
gem 'ruby-debug'
- when '1.9.2'
- gem 'ruby-debug19', '0.11.6'
- gem 'ruby-debug-base19', '0.11.25'
- gem 'linecache19', '0.5.12'
- when '1.9.3'
- gem 'ruby-debug19', '0.11.6'
- # NOTE - as of 2012-03-17 the following two gems have not been released,
- # so if you see either of these errors when trying to install the bundle:
- #
- # Could not find gem 'ruby-debug-base19 (= 0.11.26) ruby' in the gems available on this machine.
- # Could not find gem 'linecache19 (= 0.5.13) ruby' in the gems available on this machine.
- #
- # ... run 'script/download-ruby-debug-19-dependencies' and try again
- #
- # See http://blog.wyeworks.com/2011/11/1/ruby-1-9-3-and-ruby-debug for more info.
- gem 'ruby-debug-base19', '0.11.26'
- gem 'linecache19', '0.5.13'
+ when /^1.9/
+ gem 'debugger'
end
end
end
2  README.md
View
@@ -1,4 +1,4 @@
-# RSpec Mocks [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rspec/rspec-mocks)
+# RSpec Mocks [![Build Status](https://secure.travis-ci.org/rspec/rspec-mocks.png?branch=master)](http://travis-ci.org/rspec/rspec-mocks) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rspec/rspec-mocks)
rspec-mocks is a test-double framework for rspec with support for method stubs,
fakes, and message expectations on generated test-doubles and real objects
4 lib/rspec/mocks/any_instance/recorder.rb
View
@@ -175,7 +175,7 @@ def observe!(method_name)
backup_method!(method_name)
@klass.class_eval(<<-EOM, __FILE__, __LINE__)
def #{method_name}(*args, &blk)
- klass = self.method(:#{method_name}).owner
+ klass = ::Object.instance_method(:method).bind(self).call(:#{method_name}).owner
klass.__recorder.playback!(self, :#{method_name})
self.__send__(:#{method_name}, *args, &blk)
end
@@ -187,7 +187,7 @@ def mark_invoked!(method_name)
@klass.class_eval(<<-EOM, __FILE__, __LINE__)
def #{method_name}(*args, &blk)
method_name = :#{method_name}
- klass = self.method(:#{method_name}).owner
+ klass = ::Object.instance_method(:method).bind(self).call(:#{method_name}).owner
invoked_instance = klass.__recorder.instance_that_received(method_name)
raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by \#{self.inspect} but has already been received by \#{invoked_instance}"
end
1  lib/rspec/mocks/framework.rb
View
@@ -3,6 +3,7 @@
# object in the system.
require 'rspec/mocks/extensions/instance_exec'
+require 'rspec/mocks/stashed_instance_method'
require 'rspec/mocks/method_double'
require 'rspec/mocks/methods'
require 'rspec/mocks/argument_matchers'
76 lib/rspec/mocks/method_double.rb
View
@@ -10,7 +10,9 @@ def initialize(object, method_name, proxy)
@method_name = method_name
@object = object
@proxy = proxy
- @stashed = false
+
+ @stashed_method = StashedInstanceMethod.new(object_singleton_class, @method_name)
+ @method_is_proxied = false
store(:expectations, [])
store(:stubs, [])
end
@@ -44,72 +46,38 @@ class << @object; self; end
end
# @private
- def obfuscate(method_name)
- "obfuscated_by_rspec_mocks__#{method_name}"
- end
-
- # @private
- def stashed_method_name
- obfuscate(method_name)
- end
-
- # @private
- def object_responds_to?(method_name)
- if @proxy.already_proxied_respond_to?
- @object.__send__(obfuscate(:respond_to?), method_name)
- elsif method_name == :respond_to?
- @proxy.already_proxied_respond_to
- else
- @object.respond_to?(method_name, true)
- end
- end
-
- # @private
def configure_method
RSpec::Mocks::space.add(@object) if RSpec::Mocks::space
warn_if_nil_class
- unless @stashed
- stash_original_method
- define_proxy_method
- end
- end
-
- # @private
- def stash_original_method
- stashed = stashed_method_name
- orig = @method_name
- object_singleton_class.class_eval do
- alias_method(stashed, orig) if method_defined?(orig) || private_method_defined?(orig)
- end
- @stashed = true
+ @stashed_method.stash unless @method_is_proxied
+ define_proxy_method
end
# @private
def define_proxy_method
- method_name = @method_name
- visibility_for_method = "#{visibility} :#{method_name}"
- object_singleton_class.class_eval(<<-EOF, __FILE__, __LINE__)
- def #{method_name}(*args, &block)
- __mock_proxy.message_received :#{method_name}, *args, &block
+ return if @method_is_proxied
+
+ object_singleton_class.class_eval <<-EOF, __FILE__, __LINE__ + 1
+ def #{@method_name}(*args, &block)
+ __mock_proxy.message_received :#{@method_name}, *args, &block
end
- #{visibility_for_method}
+ #{visibility_for_method}
EOF
+ @method_is_proxied = true
+ end
+
+ # @private
+ def visibility_for_method
+ "#{visibility} :#{method_name}"
end
# @private
def restore_original_method
- if @stashed
- method_name = @method_name
- stashed_method_name = self.stashed_method_name
- object_singleton_class.instance_eval do
- remove_method method_name
- if method_defined?(stashed_method_name) || private_method_defined?(stashed_method_name)
- alias_method method_name, stashed_method_name
- remove_method stashed_method_name
- end
- end
- @stashed = false
- end
+ return unless @method_is_proxied
+
+ object_singleton_class.__send__(:remove_method, @method_name)
+ @stashed_method.restore
+ @method_is_proxied = false
end
# @private
2  lib/rspec/mocks/methods.rb
View
@@ -84,7 +84,7 @@ def stub_chain(*chain, &blk)
chain.shift
matching_stub.invoke.stub_chain(*chain, &blk)
else
- next_in_chain = Object.new
+ next_in_chain = Mock.new
stub(chain.shift) { next_in_chain }
next_in_chain.stub_chain(*chain, &blk)
end
6 lib/rspec/mocks/proxy.rb
View
@@ -175,7 +175,11 @@ def find_matching_expectation(method_name, *args)
end
def find_almost_matching_expectation(method_name, *args)
- method_double[method_name].expectations.find {|expectation| expectation.matches_name_but_not_args(method_name, *args) && !expectation.called_max_times?}
+ method_double[method_name].expectations.find do |expectation|
+ expectation.matches_name_but_not_args(method_name, *args) && !expectation.called_max_times?
+ end || method_double[method_name].expectations.find do |expectation|
+ expectation.matches_name_but_not_args(method_name, *args)
+ end
end
def find_matching_method_stub(method_name, *args)
60 lib/rspec/mocks/stashed_instance_method.rb
View
@@ -0,0 +1,60 @@
+# @private
+class StashedInstanceMethod
+ def initialize(klass, method)
+ @klass = klass
+ @method = method
+
+ @method_is_stashed = false
+ end
+
+ # @private
+ def stash
+ return if !method_defined_directly_on_klass? || @method_is_stashed
+
+ @klass.__send__(:alias_method, stashed_method_name, @method)
+ @method_is_stashed = true
+ end
+
+ private
+
+ # @private
+ def method_defined_directly_on_klass?
+ method_defined_on_klass? && method_owned_by_klass?
+ end
+
+ # @private
+ def method_defined_on_klass?
+ @klass.method_defined?(@method) || @klass.private_method_defined?(@method)
+ end
+
+ if ::UnboundMethod.method_defined?(:owner)
+ # @private
+ def method_owned_by_klass?
+ @klass.instance_method(@method).owner == @klass
+ end
+ else
+ # @private
+ def method_owned_by_klass?
+ # On 1.8.6, which does not support Method#owner, we have no choice but
+ # to assume it's defined on the klass even if it may be defined on
+ # a superclass.
+ true
+ end
+ end
+
+ # @private
+ def stashed_method_name
+ "obfuscated_by_rspec_mocks__#{@method}"
+ end
+
+ public
+
+ # @private
+ def restore
+ return unless @method_is_stashed
+
+ @klass.__send__(:alias_method, @method, stashed_method_name)
+ @klass.__send__(:remove_method, stashed_method_name)
+ @method_is_stashed = false
+ end
+end
35 lib/rspec/mocks/stub_const.rb
View
@@ -10,17 +10,22 @@ module RecursiveConstMethods
# we need to conditionally define methods to ignore the top-level/inherited
# constants.
#
- # Given `class A; end`:
+ # Given:
+ # class A; B = 1; end
+ # class C < A; end
#
# On 1.8:
- # - A.const_get("Hash") # => ::Hash
- # - A.const_defined?("Hash") # => false
- # - Neither method accepts the extra `inherit` argument
+ # - C.const_get("Hash") # => ::Hash
+ # - C.const_defined?("Hash") # => false
+ # - C.constants # => ["A"]
+ # - None of these methods accept the extra `inherit` argument
# On 1.9:
- # - A.const_get("Hash") # => ::Hash
- # - A.const_defined?("Hash") # => true
- # - A.const_get("Hash", false) # => raises NameError
- # - A.const_defined?("Hash", false) # => false
+ # - C.const_get("Hash") # => ::Hash
+ # - C.const_defined?("Hash") # => true
+ # - C.const_get("Hash", false) # => raises NameError
+ # - C.const_defined?("Hash", false) # => false
+ # - C.constants # => [:A]
+ # - C.constants(false) #=> []
if Module.method(:const_defined?).arity == 1
def const_defined_on?(mod, const_name)
mod.const_defined?(const_name)
@@ -33,6 +38,10 @@ def get_const_defined_on(mod, const_name)
raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
end
+
+ def constants_defined_on(mod)
+ mod.constants.select { |c| const_defined_on?(mod, c) }
+ end
else
def const_defined_on?(mod, const_name)
mod.const_defined?(const_name, false)
@@ -41,6 +50,10 @@ def const_defined_on?(mod, const_name)
def get_const_defined_on(mod, const_name)
mod.const_get(const_name, false)
end
+
+ def constants_defined_on(mod)
+ mod.constants(false)
+ end
end
def recursive_const_get(const_name)
@@ -217,10 +230,10 @@ def verify_constants_to_transfer!
if @transfer_nested_constants.is_a?(Array)
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
- undefined_constants = @transfer_nested_constants - @original_value.constants
+ undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
if undefined_constants.any?
- available_constants = @original_value.constants - @transfer_nested_constants
+ available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
raise ArgumentError,
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
"for #{@full_constant_name} since they are not defined. Did you mean " +
@@ -229,7 +242,7 @@ def verify_constants_to_transfer!
@transfer_nested_constants
else
- @original_value.constants
+ constants_defined_on(@original_value)
end
end
end
2  lib/rspec/mocks/version.rb
View
@@ -1,7 +1,7 @@
module RSpec
module Mocks
module Version
- STRING = '2.11.2'
+ STRING = '2.11.3'
end
end
end
7 script/find_path_to_rspec_exe
View
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+require File.expand_path('../../bundle/bundler/setup', __FILE__)
+rspec_core_path = $LOAD_PATH.grep(/rspec-core/).first
+
+puts File.expand_path('../exe/rspec', rspec_core_path)
+
28 script/test_all
View
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+function print_and_run {
+ echo $1
+ ($1)
+}
+
+set -e
+
+echo "Bundling Standalone so we can run the specs w/o bundler loaded"
+
+bundle install --standalone
+path_to_rspec_exe=`script/find_path_to_rspec_exe`
+
+command_prefix="ruby -r./bundle/bundler/setup.rb -S $path_to_rspec_exe"
+
+echo "Running all..."
+
+print_and_run "$command_prefix spec --format progress --profile"
+
+echo
+echo "--------------------------------------------------------------------"
+echo
+
+for file in `find spec -iname '*_spec.rb'`; do
+ print_and_run "$command_prefix $file --format progress"
+done
+
27 spec/rspec/mocks/any_instance_spec.rb
View
@@ -845,6 +845,33 @@ class RSpec::SampleRspecTestClass;end
end.to raise_error(RSpec::Mocks::MockExpectationError, "The message 'existing_method' was received by #{instance_two.inspect} but has already been received by #{instance_one.inspect}")
end
end
+
+ context "when a class overrides Object#method" do
+ let(:http_request_class) { Struct.new(:method, :uri) }
+
+ it "stubs the method correctly" do
+ http_request_class.any_instance.stub(:existing_method).and_return("foo")
+ http_request_class.new.existing_method.should == "foo"
+ end
+
+ it "mocks the method correctly" do
+ http_request_class.any_instance.should_receive(:existing_method).and_return("foo")
+ http_request_class.new.existing_method.should == "foo"
+ end
+ end
+
+ context "when used after the test has finished" do
+ it "restores the original behavior of a stubbed method" do
+ klass.any_instance.stub(:existing_method).and_return(:stubbed_return_value)
+
+ instance = klass.new
+ instance.existing_method.should == :stubbed_return_value
+
+ RSpec::Mocks.verify
+
+ instance.existing_method.should == :existing_method_return_value
+ end
+ end
end
end
end
15 spec/rspec/mocks/partial_mock_spec.rb
View
@@ -93,6 +93,21 @@ module Mocks
%Q|(nil).foobar(any args)\n expected: 1 time\n received: 0 times|
)
end
+
+ it "includes the class name in the error when mocking a class method that is called an extra time with the wrong args" do
+ klass = Class.new do
+ def self.to_s
+ "MyClass"
+ end
+ end
+
+ klass.should_receive(:bar).with(1)
+ klass.bar(1)
+
+ expect {
+ klass.bar(2)
+ }.to raise_error(RSpec::Mocks::MockExpectationError, /MyClass/)
+ end
end
describe "Partially mocking an object that defines ==, after another mock has been defined" do
6 spec/rspec/mocks/serialization_spec.rb
View
@@ -59,7 +59,7 @@ def set_stub
end
it 'serializes to yaml the same with and without stubbing, using YAML.dump' do
- expect { set_stub }.to_not change { YAML.dump(serializable_object) }
+ expect { set_stub }.to_not change { ::YAML.dump(serializable_object) }
end
end
@@ -73,12 +73,12 @@ def set_stub
if compiled_with_psych
context 'using Syck as the YAML engine' do
- before(:each) { YAML::ENGINE.yamler = 'syck' }
+ before(:each) { ::YAML::ENGINE.yamler = 'syck' }
it_behaves_like 'normal YAML serialization'
end
context 'using Psych as the YAML engine' do
- before(:each) { YAML::ENGINE.yamler = 'psych' }
+ before(:each) { ::YAML::ENGINE.yamler = 'psych' }
it_behaves_like 'normal YAML serialization'
end
else
53 spec/rspec/mocks/stashed_instance_method_spec.rb
View
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe StashedInstanceMethod do
+ class ExampleClass
+ def hello
+ :hello_defined_on_class
+ end
+ end
+
+ def singleton_class_for(obj)
+ class << obj; self; end
+ end
+
+ it "stashes the current implementation of an instance method so it can be temporarily replaced" do
+ obj = Object.new
+ def obj.hello; :hello_defined_on_singleton_class; end;
+
+ stashed_method = StashedInstanceMethod.new(singleton_class_for(obj), :hello)
+ stashed_method.stash
+
+ def obj.hello; :overridden_hello; end
+ expect(obj.hello).to eql :overridden_hello
+
+ stashed_method.restore
+ expect(obj.hello).to eql :hello_defined_on_singleton_class
+ end
+
+ it "stashes private instance methods" do
+ obj = Object.new
+ def obj.hello; :hello_defined_on_singleton_class; end;
+ singleton_class_for(obj).__send__(:private, :hello)
+
+ stashed_method = StashedInstanceMethod.new(singleton_class_for(obj), :hello)
+ stashed_method.stash
+
+ def obj.hello; :overridden_hello; end
+ stashed_method.restore
+ expect(obj.send(:hello)).to eql :hello_defined_on_singleton_class
+ end
+
+ it "only stashes methods directly defined on the given class, not its ancestors" do
+ obj = ExampleClass.new
+
+ stashed_method = StashedInstanceMethod.new(singleton_class_for(obj), :hello)
+ stashed_method.stash
+
+ def obj.hello; :overridden_hello; end;
+ expect(obj.hello).to eql :overridden_hello
+
+ stashed_method.restore
+ expect(obj.hello).to eql :overridden_hello
+ end
+end
5 spec/rspec/mocks/stub_chain_spec.rb
View
@@ -144,6 +144,11 @@ module Mocks
object.msg1.msg2.msg3.msg4.should equal(:first)
object.msg1.msg2.msg3.msg5.should equal(:second)
end
+
+ it "handles private instance methods (like Object#select) in the middle of a chain" do
+ object.stub_chain(:msg1, :select, :msg3 => 'answer')
+ expect(object.msg1.select.msg3).to eq 'answer'
+ end
end
end
end
22 spec/rspec/mocks/stub_const_spec.rb
View
@@ -12,6 +12,10 @@ class NestedEvenMore
end
end
+class TestSubClass < TestClass
+ P = :p
+end
+
module RSpec
module Mocks
describe "Constant Stubbing" do
@@ -121,6 +125,24 @@ def change_const_value_to(value)
stub::Nested.should be(tc_nested)
end
+ it 'does not transfer nested constants that are inherited from a superclass' do
+ stub = Module.new
+ stub_const("TestSubClass", stub, :transfer_nested_constants => true)
+ stub::P.should eq(:p)
+ defined?(stub::M).should be_false
+ defined?(stub::N).should be_false
+ end
+
+ it 'raises an error when asked to transfer a nested inherited constant' do
+ original_tsc = TestSubClass
+
+ expect {
+ stub_const("TestSubClass", Module.new, :transfer_nested_constants => [:M])
+ }.to raise_error(ArgumentError)
+
+ TestSubClass.should be(original_tsc)
+ end
+
it 'allows nested constants to be selectively transferred to a stub module' do
stub = Module.new
stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N])
13 spec/rspec/mocks/stub_implementation_spec.rb
View
@@ -56,6 +56,19 @@ def obj.foo; :original; end
obj.unstub(:foo)
obj.foo(3).should eq :three
end
+
+ it "restores the correct implementations when stubbed and unstubbed on a parent and child class" do
+ parent = Class.new
+ child = Class.new(parent)
+
+ parent.stub(:new)
+ child.stub(:new)
+ parent.unstub(:new)
+ child.unstub(:new)
+
+ parent.new.should be_an_instance_of parent
+ child.new.should be_an_instance_of child
+ end
it "raises a MockExpectationError if the method has not been stubbed" do
obj = Object.new
9 spec/rspec/mocks/stub_spec.rb
View
@@ -112,6 +112,15 @@ def existing_private_instance_method
@class.rspec_reset
@class.send(:existing_private_class_method).should eq(:original_value)
end
+
+ it "does not remove existing methods that have been stubbed twice" do
+ @instance.stub(:existing_instance_method)
+ @instance.stub(:existing_instance_method)
+
+ @instance.rspec_reset
+
+ @instance.existing_instance_method.should eq(:original_value)
+ end
end
it "returns values in order to consecutive calls" do
6 spec/spec_helper.rb
View
@@ -1,3 +1,9 @@
+require 'yaml'
+begin
+ require 'psych'
+rescue LoadError
+end
+
RSpec::Matchers.define :include_method do |expected|
match do |actual|
actual.map { |m| m.to_s }.include?(expected.to_s)

No commit comments for this range

Something went wrong with that request. Please try again.