Skip to content
This repository
  • 16 commits
  • 30 files changed
  • 0 comments
  • 7 contributors
Jul 10, 2012
Uģis Ozols Add missing 'be's to the README. 5288070
Justin Ko Merge pull request #161 from ugisozols/master
Add missing 'be's to the README.
d9169ca
Myron Marston 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
Aug 01, 2012
David Chelimsky Don't modify dup on classes that don't support dup
Fixes #168.
b828604
Aug 02, 2012
daneget Fix any_instance to handle methods defined on superclasses.
Previously, the recorder implementation created a SystemStackError.

Closes #152.
1272c8a
Myron Marston Cleanup spec a bit. 306ee36
Myron Marston Update changelog. f1a2682
Aug 05, 2012
David Chelimsky align the feature READMEs for stubs and message expectations e2826c6
David Chelimsky dev: update optional dev dependencies 37e095c
Aug 10, 2012
Jim Deville fix formatting for relish df607ea
Andy Lindeman Merge pull request #173 from jredville/master
Clean up formatting for Relish
bb21bc1
Myron Marston 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
Aug 11, 2012
Myron Marston 2.11.2 release. e7bd234
Sep 16, 2012
Myron Marston 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
Sep 18, 2012
Myron Marston 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
Sep 19, 2012
Myron Marston 2.11.3 release. d0bcce4

Showing 30 changed files with 461 additions and 132 deletions. Show diff stats Hide diff stats

  1. 1  .gitignore
  2. 2  .travis.yml
  3. 28  Changelog.md
  4. 25  Gemfile-custom.sample
  5. 6  README.md
  6. 20  features/argument_matchers/README.md
  7. 4  features/argument_matchers/type_matchers.feature
  8. 37  features/message_expectations/README.md
  9. 12  features/method_stubs/README.md
  10. 2  lib/rspec/mocks/any_instance.rb
  11. 6  lib/rspec/mocks/any_instance/recorder.rb
  12. 2  lib/rspec/mocks/error_generator.rb
  13. 1  lib/rspec/mocks/framework.rb
  14. 76  lib/rspec/mocks/method_double.rb
  15. 2  lib/rspec/mocks/methods.rb
  16. 6  lib/rspec/mocks/proxy.rb
  17. 60  lib/rspec/mocks/stashed_instance_method.rb
  18. 80  lib/rspec/mocks/stub_const.rb
  19. 2  lib/rspec/mocks/version.rb
  20. 7  script/find_path_to_rspec_exe
  21. 28  script/test_all
  22. 52  spec/rspec/mocks/any_instance_spec.rb
  23. 15  spec/rspec/mocks/partial_mock_spec.rb
  24. 6  spec/rspec/mocks/serialization_spec.rb
  25. 53  spec/rspec/mocks/stashed_instance_method_spec.rb
  26. 5  spec/rspec/mocks/stub_chain_spec.rb
  27. 27  spec/rspec/mocks/stub_const_spec.rb
  28. 13  spec/rspec/mocks/stub_implementation_spec.rb
  29. 9  spec/rspec/mocks/stub_spec.rb
  30. 6  spec/spec_helper.rb
1  .gitignore
@@ -13,3 +13,4 @@ Gemfile.lock
13 13
 .yardoc
14 14
 bin
15 15
 Gemfile-custom
  16
+bundle
2  .travis.yml
... ...
@@ -1,5 +1,5 @@
1 1
 bundler_args: "--binstubs"
2  
-script: "bin/rake --trace 2>&1"
  2
+script: "script/test_all && bin/rake --trace 2>&1"
3 3
 rvm:
4 4
   - 1.8.7
5 5
   - 1.9.2
28  Changelog.md
Source Rendered
... ...
@@ -1,3 +1,29 @@
  1
+### 2.11.3 / 2012-09-19
  2
+[full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.2...v2.11.3)
  3
+
  4
+Bug fixes
  5
+
  6
+* Fix `:transfer_nested_constants` option of `stub_const` so that it
  7
+  doesn't blow up when there are inherited constants. (Myron Marston)
  8
+* `any_instance` stubs can be used on classes that override `Object#method`.
  9
+  (Andy Lindeman)
  10
+* Methods stubbed with `any_instance` are unstubbed after the test finishes.
  11
+  (Andy Lindeman)
  12
+* Fix confusing error message when calling a mocked class method an
  13
+  extra time with the wrong arguments (Myron Marston).
  14
+
  15
+### 2.11.2 / 2012-08-11
  16
+[full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.1...v2.11.2)
  17
+
  18
+Bug fixes
  19
+
  20
+* Don't modify `dup` on classes that don't support `dup` (David Chelimsky)
  21
+* Fix `any_instance` so that it works properly with methods defined on
  22
+  a superclass. (Daniel Eguzkiza)
  23
+* Fix `stub_const` so that it works properly for nested constants that
  24
+  share a name with a top-level constant (e.g. "MyGem::Hash"). (Myron
  25
+  Marston)
  26
+
1 27
 ### 2.11.1 / 2012-07-09
2 28
 [full changelog](http://github.com/rspec/rspec-mocks/compare/v2.11.0...v2.11.1)
3 29
 
@@ -13,7 +39,7 @@ Bug fixes
13 39
 
14 40
 Enhancements
15 41
 
16  
-* expose ArgumentListMatcher as a formal API
  42
+* Expose ArgumentListMatcher as a formal API
17 43
     * supports use by 3rd party mock frameworks like Surrogate
18 44
 * Add `stub_const` API to stub constants for the duration of an
19 45
   example (Myron Marston).
25  Gemfile-custom.sample
... ...
@@ -1,34 +1,19 @@
1 1
 group :development do
2 2
   gem 'interactive_rspec'
3  
-  gem 'relish', '~> 0.5.0'
4  
-  gem 'guard-rspec', '0.5.0'
  3
+  gem 'relish', '~> 0.6.0'
  4
+  gem 'guard-rspec', '~> 1.2.1'
5 5
   gem 'growl', '1.0.3'
6 6
   gem 'spork', '0.9.0'
7 7
 
8 8
   platform :mri do
9  
-    gem 'rb-fsevent', '~> 0.4.3.1'
  9
+    gem 'rb-fsevent', '~> 0.9.0'
10 10
     gem 'ruby-prof', '~> 0.10.0'
11 11
 
12 12
     case RUBY_VERSION
13 13
     when /^1.8/
14 14
       gem 'ruby-debug'
15  
-    when '1.9.2'
16  
-      gem 'ruby-debug19',      '0.11.6'
17  
-      gem 'ruby-debug-base19', '0.11.25'
18  
-      gem 'linecache19',       '0.5.12'
19  
-    when '1.9.3'
20  
-      gem 'ruby-debug19',      '0.11.6'
21  
-      # NOTE - as of 2012-03-17 the following two gems have not been released,
22  
-      # so if you see either of these errors when trying to install the bundle:
23  
-      #
24  
-      #   Could not find gem 'ruby-debug-base19 (= 0.11.26) ruby' in the gems available on this machine.
25  
-      #   Could not find gem 'linecache19 (= 0.5.13) ruby' in the gems available on this machine.
26  
-      #
27  
-      # ... run 'script/download-ruby-debug-19-dependencies' and try again
28  
-      #
29  
-      # See http://blog.wyeworks.com/2011/11/1/ruby-1-9-3-and-ruby-debug for more info.
30  
-      gem 'ruby-debug-base19', '0.11.26'
31  
-      gem 'linecache19',       '0.5.13'
  15
+    when /^1.9/
  16
+      gem 'debugger'
32 17
     end
33 18
   end
34 19
 end
6  README.md
Source Rendered
... ...
@@ -1,4 +1,4 @@
1  
-# RSpec Mocks [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rspec/rspec-mocks)
  1
+# 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)
2 2
 
3 3
 rspec-mocks is a test-double framework for rspec with support for method stubs,
4 4
 fakes, and message expectations on generated test-doubles and real objects
@@ -152,8 +152,8 @@ specify certain kinds of arguments:
152 152
 ```ruby
153 153
 double.should_receive(:msg).with(no_args())
154 154
 double.should_receive(:msg).with(any_args())
155  
-double.should_receive(:msg).with(1, kind_of(Numeric), "b") #2nd argument can any kind of Numeric
156  
-double.should_receive(:msg).with(1, boolean(), "b") #2nd argument can true or false
  155
+double.should_receive(:msg).with(1, kind_of(Numeric), "b") #2nd argument can be any kind of Numeric
  156
+double.should_receive(:msg).with(1, boolean(), "b") #2nd argument can be true or false
157 157
 double.should_receive(:msg).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp
158 158
 double.should_receive(:msg).with(1, anything(), "b") #2nd argument can be anything at all
159 159
 double.should_receive(:msg).with(1, duck_type(:abs, :div), "b")
20  features/argument_matchers/README.md
Source Rendered
@@ -4,20 +4,20 @@ Argument matchers can be used:
4 4
 
5 5
 * In stubs to constrain the scope of the stubbed method
6 6
 
7  
-      obj.stub(:foo).with(:bar) do |arg|
8  
-        #do something for :bar
9  
-      end
10  
-      obj.stub(:foo).with(:baz) do |arg|
11  
-        #do something for :baz
12  
-      end
  7
+    obj.stub(:foo).with(:bar) do |arg|
  8
+      #do something for :bar
  9
+    end
  10
+    obj.stub(:foo).with(:baz) do |arg|
  11
+      #do something for :baz
  12
+    end
13 13
 
14 14
 * In expectations to validate the arguments that should be received in a method call
15 15
 
16  
-      #create a double
17  
-      obj = double()
  16
+    #create a double
  17
+    obj = double()
18 18
       
19  
-      #expect a message with given args
20  
-      obj.should_receive(:message).with('an argument')
  19
+    #expect a message with given args
  20
+    obj.should_receive(:message).with('an argument')
21 21
 
22 22
 If more control is needed, one can use a block
23 23
 
4  features/argument_matchers/type_matchers.feature
... ...
@@ -1,6 +1,8 @@
1 1
 Feature: stub with argument constraints
2 2
 
3  
-  You can further specify the behavior by constraining the type, format and/or number of arguments with the #with() method chained off of #stub()
  3
+  You can further specify the behavior by constraining the type,
  4
+  format and/or number of arguments with the `#with()` method 
  5
+  chained off of `#stub()`
4 6
   
5 7
   Scenario: an_instance_of argument matcher
6 8
     Given a file named "stub_an_instance_of_args_spec.rb" with:
37  features/message_expectations/README.md
Source Rendered
@@ -7,7 +7,30 @@
7 7
     obj.should_receive(:message)
8 8
 
9 9
     # specify a return value
10  
-    obj.should_receive(:message) { 'this is the value to return' }
  10
+    obj.should_receive(:message) { :value }
  11
+    obj.should_receive(:message => :value)
  12
+    obj.should_receive(:message).and_return(:value)
  13
+
  14
+These forms are somewhat interchangeable. The difference is that the
  15
+block contents are evaluated lazily when the `obj` receives the
  16
+`message` message, whereas the others are evaluated as they are read.
  17
+
  18
+### Fake implementation
  19
+
  20
+    obj.should_receive(:message) do |arg1, arg2|
  21
+      # set expectations about the args in this block
  22
+      # and set a return value
  23
+    end
  24
+
  25
+### Raising/Throwing
  26
+
  27
+    obj.should_receive(:message).and_raise("this error")
  28
+    obj.should_receive(:message).and_throw(:this_symbol)
  29
+
  30
+You can also use the block format:
  31
+
  32
+    obj.should_receive(:message) { raise "this error" }
  33
+    obj.should_receive(:message) { throw :this_symbol }
11 34
 
12 35
 ### Argument constraints
13 36
    
@@ -40,19 +63,7 @@
40 63
     obj.should_receive(:message).at_most(:twice)
41 64
     obj.should_receive(:message).at_most(n).times
42 65
 
43  
-### Raising/Throwing
44  
-
45  
-    obj.should_receive(:message) { raise "this error" }
46  
-    obj.should_receive(:message) { throw :this_symbol }
47  
-
48 66
 ### Ordering
49 67
 
50 68
     obj.should_receive(:one).ordered
51 69
     obj.should_receive(:two).ordered
52  
-
53  
-### Arbitrary handling
54  
-
55  
-    obj.should_receive(:message) do |arg1, arg2|
56  
-      # set expectations about the args in this block
57  
-      # and set a return value
58  
-    end
12  features/method_stubs/README.md
Source Rendered
... ...
@@ -1,12 +1,16 @@
1 1
 ### Stub return values
2 2
 
  3
+    # create a double
  4
+    obj = double()
  5
+
  6
+    # specify a return value
3 7
     obj.stub(:message) { :value }
4 8
     obj.stub(:message => :value)
5 9
     obj.stub(:message).and_return(:value)
6 10
 
7  
-These forms are somewhat interchangeable. The difference is that the block
8  
-contents are evaluated lazily when the `obj` receives the `message` message,
9  
-whereas the others are evaluated as they are read.
  11
+These forms are somewhat interchangeable. The difference is that the
  12
+block contents are evaluated lazily when the `obj` receives the
  13
+`message` message, whereas the others are evaluated as they are read.
10 14
 
11 15
 ### Fake implementation
12 16
 
@@ -20,7 +24,7 @@ whereas the others are evaluated as they are read.
20 24
     obj.stub(:message).and_raise("this error")
21 25
     obj.stub(:message).and_throw(:this_symbol)
22 26
 
23  
-You can also use the block format, for consistency with other stubs:
  27
+You can also use the block format:
24 28
 
25 29
     obj.stub(:message) { raise "this error" }
26 30
     obj.stub(:message) { throw :this_symbol }
2  lib/rspec/mocks/any_instance.rb
@@ -54,7 +54,7 @@ def __recorder
54 54
       
55 55
       private
56 56
       def modify_dup_to_remove_mock_proxy_when_invoked
57  
-        unless self.method_defined?(:__rspec_original_dup)
  57
+        if self.method_defined?(:dup) and !self.method_defined?(:__rspec_original_dup)
58 58
           self.class_eval do
59 59
             def __rspec_dup
60 60
               __remove_mock_proxy
6  lib/rspec/mocks/any_instance/recorder.rb
@@ -175,7 +175,8 @@ def observe!(method_name)
175 175
           backup_method!(method_name)
176 176
           @klass.class_eval(<<-EOM, __FILE__, __LINE__)
177 177
             def #{method_name}(*args, &blk)
178  
-              self.class.__recorder.playback!(self, :#{method_name})
  178
+              klass = ::Object.instance_method(:method).bind(self).call(:#{method_name}).owner
  179
+              klass.__recorder.playback!(self, :#{method_name})
179 180
               self.__send__(:#{method_name}, *args, &blk)
180 181
             end
181 182
           EOM
@@ -186,7 +187,8 @@ def mark_invoked!(method_name)
186 187
           @klass.class_eval(<<-EOM, __FILE__, __LINE__)
187 188
             def #{method_name}(*args, &blk)
188 189
               method_name = :#{method_name}
189  
-              invoked_instance = self.class.__recorder.instance_that_received(method_name)
  190
+              klass = ::Object.instance_method(:method).bind(self).call(:#{method_name}).owner
  191
+              invoked_instance = klass.__recorder.instance_that_received(method_name)
190 192
               raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by \#{self.inspect} but has already been received by \#{invoked_instance}"
191 193
             end
192 194
           EOM
2  lib/rspec/mocks/error_generator.rb
@@ -31,7 +31,7 @@ def raise_unexpected_message_args_error(expectation, *args)
31 31
       def raise_missing_default_stub_error(expectation,*args)
32 32
         expected_args = format_args(*expectation.expected_args)
33 33
         actual_args = args.collect {|a| format_args(*a)}.join(", ")
34  
-        __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"
  34
+        __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"
35 35
       end
36 36
 
37 37
       # @private
1  lib/rspec/mocks/framework.rb
@@ -3,6 +3,7 @@
3 3
 # object in the system.
4 4
 
5 5
 require 'rspec/mocks/extensions/instance_exec'
  6
+require 'rspec/mocks/stashed_instance_method'
6 7
 require 'rspec/mocks/method_double'
7 8
 require 'rspec/mocks/methods'
8 9
 require 'rspec/mocks/argument_matchers'
76  lib/rspec/mocks/method_double.rb
@@ -10,7 +10,9 @@ def initialize(object, method_name, proxy)
10 10
         @method_name = method_name
11 11
         @object = object
12 12
         @proxy = proxy
13  
-        @stashed = false
  13
+
  14
+        @stashed_method = StashedInstanceMethod.new(object_singleton_class, @method_name)
  15
+        @method_is_proxied = false
14 16
         store(:expectations, [])
15 17
         store(:stubs, [])
16 18
       end
@@ -44,72 +46,38 @@ class << @object; self; end
44 46
       end
45 47
 
46 48
       # @private
47  
-      def obfuscate(method_name)
48  
-        "obfuscated_by_rspec_mocks__#{method_name}"
49  
-      end
50  
-
51  
-      # @private
52  
-      def stashed_method_name
53  
-        obfuscate(method_name)
54  
-      end
55  
-
56  
-      # @private
57  
-      def object_responds_to?(method_name)
58  
-        if @proxy.already_proxied_respond_to?
59  
-          @object.__send__(obfuscate(:respond_to?), method_name)
60  
-        elsif method_name == :respond_to?
61  
-          @proxy.already_proxied_respond_to
62  
-        else
63  
-          @object.respond_to?(method_name, true)
64  
-        end
65  
-      end
66  
-
67  
-      # @private
68 49
       def configure_method
69 50
         RSpec::Mocks::space.add(@object) if RSpec::Mocks::space
70 51
         warn_if_nil_class
71  
-        unless @stashed
72  
-          stash_original_method
73  
-          define_proxy_method
74  
-        end
75  
-      end
76  
-
77  
-      # @private
78  
-      def stash_original_method
79  
-        stashed = stashed_method_name
80  
-        orig = @method_name
81  
-        object_singleton_class.class_eval do
82  
-          alias_method(stashed, orig) if method_defined?(orig) || private_method_defined?(orig)
83  
-        end
84  
-        @stashed = true
  52
+        @stashed_method.stash unless @method_is_proxied
  53
+        define_proxy_method
85 54
       end
86 55
 
87 56
       # @private
88 57
       def define_proxy_method
89  
-        method_name = @method_name
90  
-        visibility_for_method = "#{visibility} :#{method_name}"
91  
-        object_singleton_class.class_eval(<<-EOF, __FILE__, __LINE__)
92  
-          def #{method_name}(*args, &block)
93  
-            __mock_proxy.message_received :#{method_name}, *args, &block
  58
+        return if @method_is_proxied
  59
+
  60
+        object_singleton_class.class_eval <<-EOF, __FILE__, __LINE__ + 1
  61
+          def #{@method_name}(*args, &block)
  62
+            __mock_proxy.message_received :#{@method_name}, *args, &block
94 63
           end
95  
-        #{visibility_for_method}
  64
+          #{visibility_for_method}
96 65
         EOF
  66
+        @method_is_proxied = true
  67
+      end
  68
+
  69
+      # @private
  70
+      def visibility_for_method
  71
+        "#{visibility} :#{method_name}"
97 72
       end
98 73
 
99 74
       # @private
100 75
       def restore_original_method
101  
-        if @stashed
102  
-          method_name = @method_name
103  
-          stashed_method_name = self.stashed_method_name
104  
-          object_singleton_class.instance_eval do
105  
-            remove_method method_name
106  
-            if method_defined?(stashed_method_name) || private_method_defined?(stashed_method_name)
107  
-              alias_method method_name, stashed_method_name
108  
-              remove_method stashed_method_name
109  
-            end
110  
-          end
111  
-          @stashed = false
112  
-        end
  76
+        return unless @method_is_proxied
  77
+
  78
+        object_singleton_class.__send__(:remove_method, @method_name)
  79
+        @stashed_method.restore
  80
+        @method_is_proxied = false
113 81
       end
114 82
 
115 83
       # @private
2  lib/rspec/mocks/methods.rb
@@ -84,7 +84,7 @@ def stub_chain(*chain, &blk)
84 84
             chain.shift
85 85
             matching_stub.invoke.stub_chain(*chain, &blk)
86 86
           else
87  
-            next_in_chain = Object.new
  87
+            next_in_chain = Mock.new
88 88
             stub(chain.shift) { next_in_chain }
89 89
             next_in_chain.stub_chain(*chain, &blk)
90 90
           end
6  lib/rspec/mocks/proxy.rb
@@ -175,7 +175,11 @@ def find_matching_expectation(method_name, *args)
175 175
       end
176 176
 
177 177
       def find_almost_matching_expectation(method_name, *args)
178  
-        method_double[method_name].expectations.find {|expectation| expectation.matches_name_but_not_args(method_name, *args) && !expectation.called_max_times?}
  178
+        method_double[method_name].expectations.find do |expectation|
  179
+          expectation.matches_name_but_not_args(method_name, *args) && !expectation.called_max_times?
  180
+        end || method_double[method_name].expectations.find do |expectation|
  181
+          expectation.matches_name_but_not_args(method_name, *args)
  182
+        end
179 183
       end
180 184
 
181 185
       def find_matching_method_stub(method_name, *args)
60  lib/rspec/mocks/stashed_instance_method.rb
... ...
@@ -0,0 +1,60 @@
  1
+# @private
  2
+class StashedInstanceMethod
  3
+  def initialize(klass, method)
  4
+    @klass = klass
  5
+    @method = method
  6
+
  7
+    @method_is_stashed = false
  8
+  end
  9
+
  10
+  # @private
  11
+  def stash
  12
+    return if !method_defined_directly_on_klass? || @method_is_stashed
  13
+
  14
+    @klass.__send__(:alias_method, stashed_method_name, @method)
  15
+    @method_is_stashed = true
  16
+  end
  17
+
  18
+  private
  19
+
  20
+  # @private
  21
+  def method_defined_directly_on_klass?
  22
+    method_defined_on_klass? && method_owned_by_klass?
  23
+  end
  24
+
  25
+  # @private
  26
+  def method_defined_on_klass?
  27
+    @klass.method_defined?(@method) || @klass.private_method_defined?(@method)
  28
+  end
  29
+
  30
+  if ::UnboundMethod.method_defined?(:owner)
  31
+    # @private
  32
+    def method_owned_by_klass?
  33
+      @klass.instance_method(@method).owner == @klass
  34
+    end
  35
+  else
  36
+    # @private
  37
+    def method_owned_by_klass?
  38
+      # On 1.8.6, which does not support Method#owner, we have no choice but
  39
+      # to assume it's defined on the klass even if it may be defined on
  40
+      # a superclass.
  41
+      true
  42
+    end
  43
+  end
  44
+
  45
+  # @private
  46
+  def stashed_method_name
  47
+    "obfuscated_by_rspec_mocks__#{@method}"
  48
+  end
  49
+
  50
+  public
  51
+
  52
+  # @private
  53
+  def restore
  54
+    return unless @method_is_stashed
  55
+
  56
+    @klass.__send__(:alias_method, @method, stashed_method_name)
  57
+    @klass.__send__(:remove_method, stashed_method_name)
  58
+    @method_is_stashed = false
  59
+  end
  60
+end
80  lib/rspec/mocks/stub_const.rb
@@ -4,15 +4,67 @@ module Mocks
4 4
     # constant stubbing.
5 5
     # @api private
6 6
     module RecursiveConstMethods
7  
-      def recursive_const_get(name)
8  
-        name.split('::').inject(Object) { |mod, name| mod.const_get name }
  7
+      # We only want to consider constants that are defined directly on a
  8
+      # particular module, and not include top-level/inherited constants.
  9
+      # Unfortunately, the constant API changed between 1.8 and 1.9, so
  10
+      # we need to conditionally define methods to ignore the top-level/inherited
  11
+      # constants.
  12
+      #
  13
+      # Given:
  14
+      #   class A; B = 1; end
  15
+      #   class C < A; end
  16
+      #
  17
+      # On 1.8:
  18
+      #   - C.const_get("Hash") # => ::Hash
  19
+      #   - C.const_defined?("Hash") # => false
  20
+      #   - C.constants # => ["A"]
  21
+      #   - None of these methods accept the extra `inherit` argument
  22
+      # On 1.9:
  23
+      #   - C.const_get("Hash") # => ::Hash
  24
+      #   - C.const_defined?("Hash") # => true
  25
+      #   - C.const_get("Hash", false) # => raises NameError
  26
+      #   - C.const_defined?("Hash", false) # => false
  27
+      #   - C.constants # => [:A]
  28
+      #   - C.constants(false) #=> []
  29
+      if Module.method(:const_defined?).arity == 1
  30
+        def const_defined_on?(mod, const_name)
  31
+          mod.const_defined?(const_name)
  32
+        end
  33
+
  34
+        def get_const_defined_on(mod, const_name)
  35
+          if const_defined_on?(mod, const_name)
  36
+            return mod.const_get(const_name)
  37
+          end
  38
+
  39
+          raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
  40
+        end
  41
+
  42
+        def constants_defined_on(mod)
  43
+          mod.constants.select { |c| const_defined_on?(mod, c) }
  44
+        end
  45
+      else
  46
+        def const_defined_on?(mod, const_name)
  47
+          mod.const_defined?(const_name, false)
  48
+        end
  49
+
  50
+        def get_const_defined_on(mod, const_name)
  51
+          mod.const_get(const_name, false)
  52
+        end
  53
+
  54
+        def constants_defined_on(mod)
  55
+          mod.constants(false)
  56
+        end
  57
+      end
  58
+
  59
+      def recursive_const_get(const_name)
  60
+        const_name.split('::').inject(Object) { |mod, name| get_const_defined_on(mod, name) }
9 61
       end
10 62
 
11  
-      def recursive_const_defined?(name)
12  
-        name.split('::').inject([Object, '']) do |(mod, full_name), name|
  63
+      def recursive_const_defined?(const_name)
  64
+        const_name.split('::').inject([Object, '']) do |(mod, full_name), name|
13 65
           yield(full_name, name) if block_given? && !mod.is_a?(Module)
14  
-          return false unless mod.const_defined?(name)
15  
-          [mod.const_get(name), [mod, name].join('::')]
  66
+          return false unless const_defined_on?(mod, name)
  67
+          [get_const_defined_on(mod, name), [mod, name].join('::')]
16 68
         end
17 69
       end
18 70
     end
@@ -139,7 +191,7 @@ def to_constant
139 191
       class DefinedConstantReplacer < BaseStubber
140 192
         def stub
141 193
           @context = recursive_const_get(@context_parts.join('::'))
142  
-          @original_value = @context.const_get(@const_name)
  194
+          @original_value = get_const_defined_on(@context, @const_name)
143 195
 
144 196
           constants_to_transfer = verify_constants_to_transfer!
145 197
 
@@ -160,7 +212,7 @@ def rspec_reset
160 212
 
161 213
         def transfer_nested_constants(constants)
162 214
           constants.each do |const|
163  
-            @stubbed_value.const_set(const, original_value.const_get(const))
  215
+            @stubbed_value.const_set(const, get_const_defined_on(original_value, const))
164 216
           end
165 217
         end
166 218
 
@@ -178,10 +230,10 @@ def verify_constants_to_transfer!
178 230
 
179 231
           if @transfer_nested_constants.is_a?(Array)
180 232
             @transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
181  
-            undefined_constants = @transfer_nested_constants - @original_value.constants
  233
+            undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
182 234
 
183 235
             if undefined_constants.any?
184  
-              available_constants = @original_value.constants - @transfer_nested_constants
  236
+              available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
185 237
               raise ArgumentError,
186 238
                 "Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
187 239
                 "for #{@full_constant_name} since they are not defined. Did you mean " +
@@ -190,7 +242,7 @@ def verify_constants_to_transfer!
190 242
 
191 243
             @transfer_nested_constants
192 244
           else
193  
-            @original_value.constants
  245
+            constants_defined_on(@original_value)
194 246
           end
195 247
         end
196 248
       end
@@ -202,9 +254,9 @@ class UndefinedConstantSetter < BaseStubber
202 254
         def stub
203 255
           remaining_parts = @context_parts.dup
204 256
           @deepest_defined_const = @context_parts.inject(Object) do |klass, name|
205  
-            break klass unless klass.const_defined?(name)
  257
+            break klass unless const_defined_on?(klass, name)
206 258
             remaining_parts.shift
207  
-            klass.const_get(name)
  259
+            get_const_defined_on(klass, name)
208 260
           end
209 261
 
210 262
           context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
@@ -230,7 +282,7 @@ def rspec_reset
230 282
       #
231 283
       # @api private
232 284
       def self.ensure_registered_with_mocks_space
233  
-        return if @registered_with_mocks_space
  285
+        return if defined?(@registered_with_mocks_space) && @registered_with_mocks_space
234 286
         ::RSpec::Mocks.space.add(self)
235 287
         @registered_with_mocks_space = true
236 288
       end
2  lib/rspec/mocks/version.rb
... ...
@@ -1,7 +1,7 @@
1 1
 module RSpec
2 2
   module Mocks
3 3
     module Version
4  
-      STRING = '2.11.1'
  4
+      STRING = '2.11.3'
5 5
     end
6 6
   end
7 7
 end
7  script/find_path_to_rspec_exe
... ...
@@ -0,0 +1,7 @@
  1
+#!/usr/bin/env ruby
  2
+
  3
+require File.expand_path('../../bundle/bundler/setup', __FILE__)
  4
+rspec_core_path = $LOAD_PATH.grep(/rspec-core/).first
  5
+
  6
+puts File.expand_path('../exe/rspec', rspec_core_path)
  7
+
28  script/test_all
... ...
@@ -0,0 +1,28 @@
  1
+#!/bin/bash
  2
+
  3
+function print_and_run {
  4
+  echo $1
  5
+  ($1)
  6
+}
  7
+
  8
+set -e
  9
+
  10
+echo "Bundling Standalone so we can run the specs w/o bundler loaded"
  11
+
  12
+bundle install --standalone
  13
+path_to_rspec_exe=`script/find_path_to_rspec_exe`
  14
+
  15
+command_prefix="ruby -r./bundle/bundler/setup.rb -S $path_to_rspec_exe"
  16
+
  17
+echo "Running all..."
  18
+
  19
+print_and_run "$command_prefix spec --format progress --profile"
  20
+
  21
+echo
  22
+echo "--------------------------------------------------------------------"
  23
+echo
  24
+
  25
+for file in `find spec -iname '*_spec.rb'`; do
  26
+  print_and_run "$command_prefix $file --format progress"
  27
+done
  28
+
52  spec/rspec/mocks/any_instance_spec.rb
@@ -818,8 +818,60 @@ class RSpec::SampleRspecTestClass;end
818 818
           o.some_method
819 819
           lambda { o.dup.some_method }.should_not raise_error(SystemStackError)
820 820
         end
  821
+
  822
+        it "doesn't bomb if the object doesn't support `dup`" do
  823
+          klass = Class.new do
  824
+            undef_method :dup
  825
+          end
  826
+          klass.any_instance
  827
+        end
  828
+      end
  829
+
  830
+      context "when directed at a method defined on a superclass" do
  831
+        let(:sub_klass) { Class.new(klass) }
  832
+
  833
+        it "stubs the method correctly" do
  834
+          klass.any_instance.stub(:existing_method).and_return("foo")
  835
+          sub_klass.new.existing_method.should == "foo"
  836
+        end
  837
+
  838
+        it "mocks the method correctly" do
  839
+          instance_one = sub_klass.new
  840
+          instance_two = sub_klass.new
  841
+          expect do
  842
+            klass.any_instance.should_receive(:existing_method)
  843
+            instance_one.existing_method
  844
+            instance_two.existing_method
  845
+          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}")
  846
+        end
821 847
       end
822 848
 
  849
+      context "when a class overrides Object#method" do
  850
+        let(:http_request_class) { Struct.new(:method, :uri) }
  851
+
  852
+        it "stubs the method correctly" do
  853
+          http_request_class.any_instance.stub(:existing_method).and_return("foo")
  854
+          http_request_class.new.existing_method.should == "foo"
  855
+        end
  856
+
  857
+        it "mocks the method correctly" do
  858
+          http_request_class.any_instance.should_receive(:existing_method).and_return("foo")
  859
+          http_request_class.new.existing_method.should == "foo"
  860
+        end
  861
+      end
  862
+
  863
+      context "when used after the test has finished" do
  864
+        it "restores the original behavior of a stubbed method" do
  865
+          klass.any_instance.stub(:existing_method).and_return(:stubbed_return_value)
  866
+
  867
+          instance = klass.new
  868
+          instance.existing_method.should == :stubbed_return_value
  869
+
  870
+          RSpec::Mocks.verify
  871
+
  872
+          instance.existing_method.should == :existing_method_return_value
  873
+        end
  874
+      end
823 875
     end
824 876
   end
825 877
 end
15  spec/rspec/mocks/partial_mock_spec.rb
@@ -93,6 +93,21 @@ module Mocks
93 93
           %Q|(nil).foobar(any args)\n    expected: 1 time\n    received: 0 times|
94 94
         )
95 95
       end
  96
+
  97
+      it "includes the class name in the error when mocking a class method that is called an extra time with the wrong args" do
  98
+        klass = Class.new do
  99
+          def self.to_s
  100
+            "MyClass"
  101
+          end
  102
+        end
  103
+
  104
+        klass.should_receive(:bar).with(1)
  105
+        klass.bar(1)
  106
+
  107
+        expect {
  108
+          klass.bar(2)
  109
+        }.to raise_error(RSpec::Mocks::MockExpectationError, /MyClass/)
  110
+      end
96 111
     end
97 112
     
98 113
     describe "Partially mocking an object that defines ==, after another mock has been defined" do
6  spec/rspec/mocks/serialization_spec.rb
@@ -59,7 +59,7 @@ def set_stub
59 59
         end
60 60
 
61 61
         it 'serializes to yaml the same with and without stubbing, using YAML.dump' do
62  
-          expect { set_stub }.to_not change { YAML.dump(serializable_object) }
  62
+          expect { set_stub }.to_not change { ::YAML.dump(serializable_object) }
63 63
         end
64 64
       end
65 65
 
@@ -73,12 +73,12 @@ def set_stub
73 73
 
74 74
         if compiled_with_psych
75 75
           context 'using Syck as the YAML engine' do
76  
-            before(:each) { YAML::ENGINE.yamler = 'syck' }
  76
+            before(:each) { ::YAML::ENGINE.yamler = 'syck' }
77 77
             it_behaves_like 'normal YAML serialization'
78 78
           end
79 79
 
80 80
           context 'using Psych as the YAML engine' do
81  
-            before(:each) { YAML::ENGINE.yamler = 'psych' }
  81
+            before(:each) { ::YAML::ENGINE.yamler = 'psych' }
82 82
             it_behaves_like 'normal YAML serialization'
83 83
           end
84 84
         else
53  spec/rspec/mocks/stashed_instance_method_spec.rb
... ...
@@ -0,0 +1,53 @@
  1
+require 'spec_helper'
  2
+
  3
+describe StashedInstanceMethod do
  4
+  class ExampleClass
  5
+    def hello
  6
+      :hello_defined_on_class
  7
+    end
  8
+  end
  9
+
  10
+  def singleton_class_for(obj)
  11
+    class << obj; self; end
  12
+  end
  13
+
  14
+  it "stashes the current implementation of an instance method so it can be temporarily replaced" do
  15
+    obj = Object.new
  16
+    def obj.hello; :hello_defined_on_singleton_class; end;
  17
+
  18
+    stashed_method = StashedInstanceMethod.new(singleton_class_for(obj), :hello)
  19
+    stashed_method.stash
  20
+
  21
+    def obj.hello; :overridden_hello; end
  22
+    expect(obj.hello).to eql :overridden_hello
  23
+
  24
+    stashed_method.restore
  25
+    expect(obj.hello).to eql :hello_defined_on_singleton_class
  26
+  end
  27
+
  28
+  it "stashes private instance methods" do
  29
+    obj = Object.new
  30
+    def obj.hello; :hello_defined_on_singleton_class; end;
  31
+    singleton_class_for(obj).__send__(:private, :hello)
  32
+
  33
+    stashed_method = StashedInstanceMethod.new(singleton_class_for(obj), :hello)
  34
+    stashed_method.stash
  35
+
  36
+    def obj.hello; :overridden_hello; end
  37
+    stashed_method.restore
  38
+    expect(obj.send(:hello)).to eql :hello_defined_on_singleton_class
  39
+  end
  40
+
  41
+  it "only stashes methods directly defined on the given class, not its ancestors" do
  42
+    obj = ExampleClass.new
  43
+
  44
+    stashed_method = StashedInstanceMethod.new(singleton_class_for(obj), :hello)
  45
+    stashed_method.stash
  46
+
  47
+    def obj.hello; :overridden_hello; end;
  48
+    expect(obj.hello).to eql :overridden_hello
  49
+
  50
+    stashed_method.restore
  51
+    expect(obj.hello).to eql :overridden_hello
  52
+  end
  53
+end
5  spec/rspec/mocks/stub_chain_spec.rb
@@ -144,6 +144,11 @@ module Mocks
144 144
         object.msg1.msg2.msg3.msg4.should equal(:first)
145 145
         object.msg1.msg2.msg3.msg5.should equal(:second)
146 146
       end
  147
+
  148
+      it "handles private instance methods (like Object#select) in the middle of a chain" do
  149
+        object.stub_chain(:msg1, :select, :msg3 => 'answer')
  150
+        expect(object.msg1.select.msg3).to eq 'answer'
  151
+      end
147 152
     end
148 153
   end
149 154
 end
27  spec/rspec/mocks/stub_const_spec.rb
@@ -12,6 +12,10 @@ class NestedEvenMore
12 12
   end
13 13
 end
14 14
 
  15
+class TestSubClass < TestClass
  16
+  P = :p
  17
+end
  18
+
15 19
 module RSpec
16 20
   module Mocks
17 21
     describe "Constant Stubbing" do
@@ -57,7 +61,6 @@ def change_const_value_to(value)
57 61
         end
58 62
 
59 63
         it 'returns the stubbed value' do
60  
-          orig_value = const
61 64
           stub_const(const_name, 7).should eq(7)
62 65
         end
63 66
       end
@@ -122,6 +125,24 @@ def change_const_value_to(value)
122 125
             stub::Nested.should be(tc_nested)
123 126
           end
124 127
 
  128
+          it 'does not transfer nested constants that are inherited from a superclass' do
  129
+            stub = Module.new
  130
+            stub_const("TestSubClass", stub, :transfer_nested_constants => true)
  131
+            stub::P.should eq(:p)
  132
+            defined?(stub::M).should be_false
  133
+            defined?(stub::N).should be_false
  134
+          end
  135
+
  136
+          it 'raises an error when asked to transfer a nested inherited constant' do
  137
+            original_tsc = TestSubClass
  138
+
  139
+            expect {
  140
+              stub_const("TestSubClass", Module.new, :transfer_nested_constants => [:M])
  141
+            }.to raise_error(ArgumentError)
  142
+
  143
+            TestSubClass.should be(original_tsc)
  144
+          end
  145
+
125 146
           it 'allows nested constants to be selectively transferred to a stub module' do
126 147
             stub = Module.new
127 148
             stub_const("TestClass", stub, :transfer_nested_constants => [:M, :N])
@@ -178,6 +199,10 @@ def change_const_value_to(value)
178 199
           it_behaves_like "loaded constant stubbing", "TestClass::Nested"
179 200
         end
180 201
 
  202
+        context 'for an unloaded constant with nested name that matches a top-level constant' do
  203
+          it_behaves_like "unloaded constant stubbing", "TestClass::Hash"
  204
+        end
  205
+
181 206
         context 'for a loaded deeply nested constant' do
182 207
           it_behaves_like "loaded constant stubbing", "TestClass::Nested::NestedEvenMore"
183 208
         end
13  spec/rspec/mocks/stub_implementation_spec.rb
@@ -56,6 +56,19 @@ def obj.foo; :original; end
56 56
         obj.unstub(:foo)
57 57
         obj.foo(3).should eq :three
58 58
       end
  59
+
  60
+      it "restores the correct implementations when stubbed and unstubbed on a parent and child class" do
  61
+        parent = Class.new
  62
+        child  = Class.new(parent)
  63
+
  64
+        parent.stub(:new)
  65
+        child.stub(:new)
  66
+        parent.unstub(:new)
  67
+        child.unstub(:new)
  68
+
  69
+        parent.new.should be_an_instance_of parent
  70
+        child.new.should be_an_instance_of child
  71
+      end
59 72
     
60 73
       it "raises a MockExpectationError if the method has not been stubbed" do
61 74
         obj = Object.new
9  spec/rspec/mocks/stub_spec.rb
@@ -112,6 +112,15 @@ def existing_private_instance_method
112 112
           @class.rspec_reset
113 113
           @class.send(:existing_private_class_method).should eq(:original_value)
114 114
         end
  115
+
  116
+        it "does not remove existing methods that have been stubbed twice" do
  117
+          @instance.stub(:existing_instance_method)
  118
+          @instance.stub(:existing_instance_method)
  119
+
  120
+          @instance.rspec_reset
  121
+
  122
+          @instance.existing_instance_method.should eq(:original_value)
  123
+        end
115 124
       end
116 125
 
117 126
       it "returns values in order to consecutive calls" do
6  spec/spec_helper.rb
... ...
@@ -1,3 +1,9 @@
  1
+require 'yaml'
  2
+begin
  3
+  require 'psych'
  4
+rescue LoadError
  5
+end
  6
+
1 7
 RSpec::Matchers.define :include_method do |expected|
2 8
   match do |actual|
3 9
     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.