Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: rspec/rspec-mocks
...
head fork: rspec/rspec-mocks
  • 16 commits
  • 30 files changed
  • 0 commit comments
  • 7 contributors
Commits on Jul 10, 2012
@ugisozols ugisozols Add missing 'be's to the README. 5288070
@justinko justinko Merge pull request #161 from ugisozols/master
Add missing 'be's to the README.
d9169ca
Commits on Jul 11, 2012
@myronmarston myronmarston Fix ruby warnings.
- lib/rspec/mocks/error_generator.rb:32: warning: assigned but unused variable - expected_args
- lib/rspec/mocks/error_generator.rb:33: warning: assigned but unused variable - actual_args
- lib/rspec/mocks/stub_const.rb:8: warning: shadowing outer local variable - name
- lib/rspec/mocks/stub_const.rb:12: warning: shadowing outer local variable - name
- spec/rspec/mocks/stub_const_spec.rb:60: warning: assigned but unused variable - orig_value
- lib/rspec/mocks/stub_const.rb:233: warning: instance variable @registered_with_mocks_space not initialized


Closes #162.
f36ad4d
Commits on Aug 01, 2012
@dchelimsky dchelimsky Don't modify dup on classes that don't support dup
Fixes #168.
b828604
Commits on Aug 03, 2012
@daneget daneget Fix any_instance to handle methods defined on superclasses.
Previously, the recorder implementation created a SystemStackError.

Closes #152.
1272c8a
@myronmarston myronmarston Cleanup spec a bit. 306ee36
@myronmarston myronmarston Update changelog. f1a2682
Commits on Aug 05, 2012
@dchelimsky dchelimsky align the feature READMEs for stubs and message expectations e2826c6
@dchelimsky dchelimsky dev: update optional dev dependencies 37e095c
Commits on Aug 10, 2012
@jredville jredville fix formatting for relish df607ea
@alindeman alindeman Merge pull request #173 from jredville/master
Clean up formatting for Relish
bb21bc1
Commits on Aug 11, 2012
@myronmarston myronmarston Fix use of const_defined? and const_get so it ignores top-level const…
…ants.

I didn't realize this previously, but these methods can pick up a top-level
constant when you don't intend it (e.g. ::Hash when checking MyGem.const_defined?("Hash")).
deec990
Commits on Aug 12, 2012
@myronmarston myronmarston 2.11.2 release. e7bd234
Commits on Sep 17, 2012
@myronmarston 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
@myronmarston 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
@myronmarston myronmarston 2.11.3 release. d0bcce4
Showing with 461 additions and 132 deletions.
  1. +1 −0  .gitignore
  2. +1 −1  .travis.yml
  3. +27 −1 Changelog.md
  4. +5 −20 Gemfile-custom.sample
  5. +3 −3 README.md
  6. +10 −10 features/argument_matchers/README.md
  7. +3 −1 features/argument_matchers/type_matchers.feature
  8. +24 −13 features/message_expectations/README.md
  9. +8 −4 features/method_stubs/README.md
  10. +1 −1  lib/rspec/mocks/any_instance.rb
  11. +4 −2 lib/rspec/mocks/any_instance/recorder.rb
  12. +1 −1  lib/rspec/mocks/error_generator.rb
  13. +1 −0  lib/rspec/mocks/framework.rb
  14. +22 −54 lib/rspec/mocks/method_double.rb
  15. +1 −1  lib/rspec/mocks/methods.rb
  16. +5 −1 lib/rspec/mocks/proxy.rb
  17. +60 −0 lib/rspec/mocks/stashed_instance_method.rb
  18. +66 −14 lib/rspec/mocks/stub_const.rb
  19. +1 −1  lib/rspec/mocks/version.rb
  20. +7 −0 script/find_path_to_rspec_exe
  21. +28 −0 script/test_all
  22. +52 −0 spec/rspec/mocks/any_instance_spec.rb
  23. +15 −0 spec/rspec/mocks/partial_mock_spec.rb
  24. +3 −3 spec/rspec/mocks/serialization_spec.rb
  25. +53 −0 spec/rspec/mocks/stashed_instance_method_spec.rb
  26. +5 −0 spec/rspec/mocks/stub_chain_spec.rb
  27. +26 −1 spec/rspec/mocks/stub_const_spec.rb
  28. +13 −0 spec/rspec/mocks/stub_implementation_spec.rb
  29. +9 −0 spec/rspec/mocks/stub_spec.rb
  30. +6 −0 spec/spec_helper.rb
View
1  .gitignore
@@ -13,3 +13,4 @@ Gemfile.lock
.yardoc
bin
Gemfile-custom
+bundle
View
2  .travis.yml
@@ -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
View
28 Changelog.md
@@ -1,3 +1,29 @@
+### 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)
+
+Bug fixes
+
+* Don't modify `dup` on classes that don't support `dup` (David Chelimsky)
+* Fix `any_instance` so that it works properly with methods defined on
+ a superclass. (Daniel Eguzkiza)
+* Fix `stub_const` so that it works properly for nested constants that
+ share a name with a top-level constant (e.g. "MyGem::Hash"). (Myron
+ Marston)
+
### 2.11.1 / 2012-07-09
[full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.0...v2.11.1)
@@ -13,7 +39,7 @@ Bug fixes
Enhancements
-* expose ArgumentListMatcher as a formal API
+* Expose ArgumentListMatcher as a formal API
* supports use by 3rd party mock frameworks like Surrogate
* Add `stub_const` API to stub constants for the duration of an
example (Myron Marston).
View
25 Gemfile-custom.sample
@@ -1,34 +1,19 @@
group :development do
gem 'interactive_rspec'
- gem 'relish', '~> 0.5.0'
- gem 'guard-rspec', '0.5.0'
+ gem 'relish', '~> 0.6.0'
+ gem 'guard-rspec', '~> 1.2.1'
gem 'growl', '1.0.3'
gem 'spork', '0.9.0'
platform :mri do
- gem 'rb-fsevent', '~> 0.4.3.1'
+ 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
View
6 README.md
@@ -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
@@ -152,8 +152,8 @@ specify certain kinds of arguments:
```ruby
double.should_receive(:msg).with(no_args())
double.should_receive(:msg).with(any_args())
-double.should_receive(:msg).with(1, kind_of(Numeric), "b") #2nd argument can any kind of Numeric
-double.should_receive(:msg).with(1, boolean(), "b") #2nd argument can true or false
+double.should_receive(:msg).with(1, kind_of(Numeric), "b") #2nd argument can be any kind of Numeric
+double.should_receive(:msg).with(1, boolean(), "b") #2nd argument can be true or false
double.should_receive(:msg).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp
double.should_receive(:msg).with(1, anything(), "b") #2nd argument can be anything at all
double.should_receive(:msg).with(1, duck_type(:abs, :div), "b")
View
20 features/argument_matchers/README.md
@@ -4,20 +4,20 @@ Argument matchers can be used:
* In stubs to constrain the scope of the stubbed method
- obj.stub(:foo).with(:bar) do |arg|
- #do something for :bar
- end
- obj.stub(:foo).with(:baz) do |arg|
- #do something for :baz
- end
+ obj.stub(:foo).with(:bar) do |arg|
+ #do something for :bar
+ end
+ obj.stub(:foo).with(:baz) do |arg|
+ #do something for :baz
+ end
* In expectations to validate the arguments that should be received in a method call
- #create a double
- obj = double()
+ #create a double
+ obj = double()
- #expect a message with given args
- obj.should_receive(:message).with('an argument')
+ #expect a message with given args
+ obj.should_receive(:message).with('an argument')
If more control is needed, one can use a block
View
4 features/argument_matchers/type_matchers.feature
@@ -1,6 +1,8 @@
Feature: stub with argument constraints
- You can further specify the behavior by constraining the type, format and/or number of arguments with the #with() method chained off of #stub()
+ You can further specify the behavior by constraining the type,
+ format and/or number of arguments with the `#with()` method
+ chained off of `#stub()`
Scenario: an_instance_of argument matcher
Given a file named "stub_an_instance_of_args_spec.rb" with:
View
37 features/message_expectations/README.md
@@ -7,7 +7,30 @@
obj.should_receive(:message)
# specify a return value
- obj.should_receive(:message) { 'this is the value to return' }
+ obj.should_receive(:message) { :value }
+ obj.should_receive(:message => :value)
+ obj.should_receive(:message).and_return(:value)
+
+These forms are somewhat interchangeable. The difference is that the
+block contents are evaluated lazily when the `obj` receives the
+`message` message, whereas the others are evaluated as they are read.
+
+### Fake implementation
+
+ obj.should_receive(:message) do |arg1, arg2|
+ # set expectations about the args in this block
+ # and set a return value
+ end
+
+### Raising/Throwing
+
+ obj.should_receive(:message).and_raise("this error")
+ obj.should_receive(:message).and_throw(:this_symbol)
+
+You can also use the block format:
+
+ obj.should_receive(:message) { raise "this error" }
+ obj.should_receive(:message) { throw :this_symbol }
### Argument constraints
@@ -40,19 +63,7 @@
obj.should_receive(:message).at_most(:twice)
obj.should_receive(:message).at_most(n).times
-### Raising/Throwing
-
- obj.should_receive(:message) { raise "this error" }
- obj.should_receive(:message) { throw :this_symbol }
-
### Ordering
obj.should_receive(:one).ordered
obj.should_receive(:two).ordered
-
-### Arbitrary handling
-
- obj.should_receive(:message) do |arg1, arg2|
- # set expectations about the args in this block
- # and set a return value
- end
View
12 features/method_stubs/README.md
@@ -1,12 +1,16 @@
### Stub return values
+ # create a double
+ obj = double()
+
+ # specify a return value
obj.stub(:message) { :value }
obj.stub(:message => :value)
obj.stub(:message).and_return(:value)
-These forms are somewhat interchangeable. The difference is that the block
-contents are evaluated lazily when the `obj` receives the `message` message,
-whereas the others are evaluated as they are read.
+These forms are somewhat interchangeable. The difference is that the
+block contents are evaluated lazily when the `obj` receives the
+`message` message, whereas the others are evaluated as they are read.
### Fake implementation
@@ -20,7 +24,7 @@ whereas the others are evaluated as they are read.
obj.stub(:message).and_raise("this error")
obj.stub(:message).and_throw(:this_symbol)
-You can also use the block format, for consistency with other stubs:
+You can also use the block format:
obj.stub(:message) { raise "this error" }
obj.stub(:message) { throw :this_symbol }
View
2  lib/rspec/mocks/any_instance.rb
@@ -54,7 +54,7 @@ def __recorder
private
def modify_dup_to_remove_mock_proxy_when_invoked
- unless self.method_defined?(:__rspec_original_dup)
+ if self.method_defined?(:dup) and !self.method_defined?(:__rspec_original_dup)
self.class_eval do
def __rspec_dup
__remove_mock_proxy
View
6 lib/rspec/mocks/any_instance/recorder.rb
@@ -175,7 +175,8 @@ def observe!(method_name)
backup_method!(method_name)
@klass.class_eval(<<-EOM, __FILE__, __LINE__)
def #{method_name}(*args, &blk)
- self.class.__recorder.playback!(self, :#{method_name})
+ klass = ::Object.instance_method(:method).bind(self).call(:#{method_name}).owner
+ klass.__recorder.playback!(self, :#{method_name})
self.__send__(:#{method_name}, *args, &blk)
end
EOM
@@ -186,7 +187,8 @@ def mark_invoked!(method_name)
@klass.class_eval(<<-EOM, __FILE__, __LINE__)
def #{method_name}(*args, &blk)
method_name = :#{method_name}
- invoked_instance = self.class.__recorder.instance_that_received(method_name)
+ 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
EOM
View
2  lib/rspec/mocks/error_generator.rb
@@ -31,7 +31,7 @@ def raise_unexpected_message_args_error(expectation, *args)
def raise_missing_default_stub_error(expectation,*args)
expected_args = format_args(*expectation.expected_args)
actual_args = args.collect {|a| format_args(*a)}.join(", ")
- __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n Please stub a default value first if message might be received with other args as well. \n"
+ __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n expected: #{expected_args}\n got: #{actual_args}\n Please stub a default value first if message might be received with other args as well. \n"
end
# @private
View
1  lib/rspec/mocks/framework.rb
@@ -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'
View
76 lib/rspec/mocks/method_double.rb
@@ -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
View
2  lib/rspec/mocks/methods.rb
@@ -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
View
6 lib/rspec/mocks/proxy.rb
@@ -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)
View
60 lib/rspec/mocks/stashed_instance_method.rb
@@ -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
View
80 lib/rspec/mocks/stub_const.rb
@@ -4,15 +4,67 @@ module Mocks
# constant stubbing.
# @api private
module RecursiveConstMethods
- def recursive_const_get(name)
- name.split('::').inject(Object) { |mod, name| mod.const_get name }
+ # We only want to consider constants that are defined directly on a
+ # particular module, and not include top-level/inherited constants.
+ # Unfortunately, the constant API changed between 1.8 and 1.9, so
+ # we need to conditionally define methods to ignore the top-level/inherited
+ # constants.
+ #
+ # Given:
+ # class A; B = 1; end
+ # class C < A; end
+ #
+ # On 1.8:
+ # - 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:
+ # - 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)
+ end
+
+ def get_const_defined_on(mod, const_name)
+ if const_defined_on?(mod, const_name)
+ return mod.const_get(const_name)
+ end
+
+ 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)
+ end
+
+ 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)
+ const_name.split('::').inject(Object) { |mod, name| get_const_defined_on(mod, name) }
end
- def recursive_const_defined?(name)
- name.split('::').inject([Object, '']) do |(mod, full_name), name|
+ def recursive_const_defined?(const_name)
+ const_name.split('::').inject([Object, '']) do |(mod, full_name), name|
yield(full_name, name) if block_given? && !mod.is_a?(Module)
- return false unless mod.const_defined?(name)
- [mod.const_get(name), [mod, name].join('::')]
+ return false unless const_defined_on?(mod, name)
+ [get_const_defined_on(mod, name), [mod, name].join('::')]
end
end
end
@@ -139,7 +191,7 @@ def to_constant
class DefinedConstantReplacer < BaseStubber
def stub
@context = recursive_const_get(@context_parts.join('::'))
- @original_value = @context.const_get(@const_name)
+ @original_value = get_const_defined_on(@context, @const_name)
constants_to_transfer = verify_constants_to_transfer!
@@ -160,7 +212,7 @@ def rspec_reset
def transfer_nested_constants(constants)
constants.each do |const|
- @stubbed_value.const_set(const, original_value.const_get(const))
+ @stubbed_value.const_set(const, get_const_defined_on(original_value, const))
end
end
@@ -178,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 " +
@@ -190,7 +242,7 @@ def verify_constants_to_transfer!
@transfer_nested_constants
else
- @original_value.constants
+ constants_defined_on(@original_value)
end
end
end
@@ -202,9 +254,9 @@ class UndefinedConstantSetter < BaseStubber
def stub
remaining_parts = @context_parts.dup
@deepest_defined_const = @context_parts.inject(Object) do |klass, name|
- break klass unless klass.const_defined?(name)
+ break klass unless const_defined_on?(klass, name)
remaining_parts.shift
- klass.const_get(name)
+ get_const_defined_on(klass, name)
end
context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
@@ -230,7 +282,7 @@ def rspec_reset
#
# @api private
def self.ensure_registered_with_mocks_space
- return if @registered_with_mocks_space
+ return if defined?(@registered_with_mocks_space) && @registered_with_mocks_space
::RSpec::Mocks.space.add(self)
@registered_with_mocks_space = true
end
View
2  lib/rspec/mocks/version.rb
@@ -1,7 +1,7 @@
module RSpec
module Mocks
module Version
- STRING = '2.11.1'
+ STRING = '2.11.3'
end
end
end
View
7 script/find_path_to_rspec_exe
@@ -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)
+
View
28 script/test_all
@@ -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
+
View
52 spec/rspec/mocks/any_instance_spec.rb
@@ -818,8 +818,60 @@ class RSpec::SampleRspecTestClass;end
o.some_method
lambda { o.dup.some_method }.should_not raise_error(SystemStackError)
end
+
+ it "doesn't bomb if the object doesn't support `dup`" do
+ klass = Class.new do
+ undef_method :dup
+ end
+ klass.any_instance
+ end
+ end
+
+ context "when directed at a method defined on a superclass" do
+ let(:sub_klass) { Class.new(klass) }
+
+ it "stubs the method correctly" do
+ klass.any_instance.stub(:existing_method).and_return("foo")
+ sub_klass.new.existing_method.should == "foo"
+ end
+
+ it "mocks the method correctly" do
+ instance_one = sub_klass.new
+ instance_two = sub_klass.new
+ expect do
+ klass.any_instance.should_receive(:existing_method)
+ instance_one.existing_method
+ instance_two.existing_method
+ 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
View
15 spec/rspec/mocks/partial_mock_spec.rb
@@ -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
View
6 spec/rspec/mocks/serialization_spec.rb
@@ -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
View
53 spec/rspec/mocks/stashed_instance_method_spec.rb
@@ -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
View
5 spec/rspec/mocks/stub_chain_spec.rb
@@ -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
View
27 spec/rspec/mocks/stub_const_spec.rb
@@ -12,6 +12,10 @@ class NestedEvenMore
end
end
+class TestSubClass < TestClass
+ P = :p
+end
+
module RSpec
module Mocks
describe "Constant Stubbing" do
@@ -57,7 +61,6 @@ def change_const_value_to(value)
end
it 'returns the stubbed value' do
- orig_value = const
stub_const(const_name, 7).should eq(7)
end
end
@@ -122,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])
@@ -178,6 +199,10 @@ def change_const_value_to(value)
it_behaves_like "loaded constant stubbing", "TestClass::Nested"
end
+ context 'for an unloaded constant with nested name that matches a top-level constant' do
+ it_behaves_like "unloaded constant stubbing", "TestClass::Hash"
+ end
+
context 'for a loaded deeply nested constant' do
it_behaves_like "loaded constant stubbing", "TestClass::Nested::NestedEvenMore"
end
View
13 spec/rspec/mocks/stub_implementation_spec.rb
@@ -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
View
9 spec/rspec/mocks/stub_spec.rb
@@ -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
View
6 spec/spec_helper.rb
@@ -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.