mocha 0.5.6 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.github/FUNDING.yml +1 -0
  4. data/.rubocop.yml +92 -0
  5. data/.rubocop_todo.yml +39 -0
  6. data/.yardopts +25 -0
  7. data/CONTRIBUTING.md +7 -0
  8. data/COPYING.md +3 -0
  9. data/Gemfile +17 -0
  10. data/{MIT-LICENSE → MIT-LICENSE.md} +2 -2
  11. data/README.md +361 -0
  12. data/RELEASE.md +1247 -0
  13. data/Rakefile +165 -123
  14. data/gemfiles/Gemfile.minitest.latest +8 -0
  15. data/gemfiles/Gemfile.rubocop +9 -0
  16. data/gemfiles/Gemfile.test-unit.latest +8 -0
  17. data/lib/mocha/any_instance_method.rb +12 -26
  18. data/lib/mocha/any_instance_receiver.rb +20 -0
  19. data/lib/mocha/api.rb +213 -0
  20. data/lib/mocha/argument_iterator.rb +17 -0
  21. data/lib/mocha/backtrace_filter.rb +25 -0
  22. data/lib/mocha/block_matchers.rb +33 -0
  23. data/lib/mocha/cardinality.rb +110 -0
  24. data/lib/mocha/central.rb +33 -22
  25. data/lib/mocha/change_state_side_effect.rb +17 -0
  26. data/lib/mocha/class_methods.rb +67 -0
  27. data/lib/mocha/configuration.rb +338 -0
  28. data/lib/mocha/default_name.rb +15 -0
  29. data/lib/mocha/default_receiver.rb +13 -0
  30. data/lib/mocha/deprecation.rb +6 -15
  31. data/lib/mocha/detection/minitest.rb +25 -0
  32. data/lib/mocha/detection/test_unit.rb +30 -0
  33. data/lib/mocha/error_with_filtered_backtrace.rb +15 -0
  34. data/lib/mocha/exception_raiser.rb +11 -10
  35. data/lib/mocha/expectation.rb +562 -171
  36. data/lib/mocha/expectation_error.rb +9 -14
  37. data/lib/mocha/expectation_error_factory.rb +37 -0
  38. data/lib/mocha/expectation_list.rb +30 -14
  39. data/lib/mocha/hooks.rb +55 -0
  40. data/lib/mocha/ignoring_warning.rb +20 -0
  41. data/lib/mocha/impersonating_any_instance_name.rb +13 -0
  42. data/lib/mocha/impersonating_name.rb +13 -0
  43. data/lib/mocha/in_state_ordering_constraint.rb +17 -0
  44. data/lib/mocha/inspect.rb +54 -30
  45. data/lib/mocha/instance_method.rb +17 -4
  46. data/lib/mocha/integration/assertion_counter.rb +15 -0
  47. data/lib/mocha/integration/minitest/adapter.rb +71 -0
  48. data/lib/mocha/integration/minitest.rb +29 -0
  49. data/lib/mocha/integration/monkey_patcher.rb +26 -0
  50. data/lib/mocha/integration/test_unit/adapter.rb +61 -0
  51. data/lib/mocha/integration/test_unit.rb +29 -0
  52. data/lib/mocha/integration.rb +5 -0
  53. data/lib/mocha/invocation.rb +76 -0
  54. data/lib/mocha/logger.rb +26 -0
  55. data/lib/mocha/macos_version.rb +7 -0
  56. data/lib/mocha/method_matcher.rb +8 -10
  57. data/lib/mocha/minitest.rb +7 -0
  58. data/lib/mocha/mock.rb +333 -108
  59. data/lib/mocha/mockery.rb +192 -0
  60. data/lib/mocha/name.rb +13 -0
  61. data/lib/mocha/not_initialized_error.rb +9 -0
  62. data/lib/mocha/object_methods.rb +183 -0
  63. data/lib/mocha/object_receiver.rb +20 -0
  64. data/lib/mocha/parameter_matchers/all_of.rb +38 -28
  65. data/lib/mocha/parameter_matchers/any_of.rb +44 -33
  66. data/lib/mocha/parameter_matchers/any_parameters.rb +33 -26
  67. data/lib/mocha/parameter_matchers/anything.rb +31 -22
  68. data/lib/mocha/parameter_matchers/base_methods.rb +64 -0
  69. data/lib/mocha/parameter_matchers/equals.rb +36 -25
  70. data/lib/mocha/parameter_matchers/equivalent_uri.rb +65 -0
  71. data/lib/mocha/parameter_matchers/has_entries.rb +48 -29
  72. data/lib/mocha/parameter_matchers/has_entry.rb +90 -42
  73. data/lib/mocha/parameter_matchers/has_key.rb +39 -26
  74. data/lib/mocha/parameter_matchers/has_keys.rb +59 -0
  75. data/lib/mocha/parameter_matchers/has_value.rb +39 -26
  76. data/lib/mocha/parameter_matchers/includes.rb +88 -23
  77. data/lib/mocha/parameter_matchers/instance_methods.rb +28 -0
  78. data/lib/mocha/parameter_matchers/instance_of.rb +37 -26
  79. data/lib/mocha/parameter_matchers/is_a.rb +38 -26
  80. data/lib/mocha/parameter_matchers/kind_of.rb +39 -26
  81. data/lib/mocha/parameter_matchers/not.rb +37 -26
  82. data/lib/mocha/parameter_matchers/optionally.rb +52 -17
  83. data/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb +91 -0
  84. data/lib/mocha/parameter_matchers/regexp_matches.rb +37 -25
  85. data/lib/mocha/parameter_matchers/responds_with.rb +82 -0
  86. data/lib/mocha/parameter_matchers/yaml_equivalent.rb +55 -0
  87. data/lib/mocha/parameter_matchers.rb +12 -5
  88. data/lib/mocha/parameters_matcher.rb +28 -19
  89. data/lib/mocha/raised_exception.rb +13 -0
  90. data/lib/mocha/return_values.rb +13 -18
  91. data/lib/mocha/ruby_version.rb +7 -0
  92. data/lib/mocha/sequence.rb +23 -17
  93. data/lib/mocha/single_return_value.rb +8 -18
  94. data/lib/mocha/state_machine.rb +95 -0
  95. data/lib/mocha/stubbed_method.rb +96 -0
  96. data/lib/mocha/stubbing_error.rb +10 -0
  97. data/lib/mocha/test_unit.rb +7 -0
  98. data/lib/mocha/thrower.rb +15 -0
  99. data/lib/mocha/thrown_object.rb +14 -0
  100. data/lib/mocha/version.rb +5 -0
  101. data/lib/mocha/yield_parameters.rb +12 -20
  102. data/lib/mocha.rb +19 -17
  103. data/mocha.gemspec +40 -0
  104. metadata +129 -145
  105. data/COPYING +0 -3
  106. data/README +0 -35
  107. data/RELEASE +0 -188
  108. data/examples/misc.rb +0 -44
  109. data/examples/mocha.rb +0 -26
  110. data/examples/stubba.rb +0 -65
  111. data/lib/mocha/auto_verify.rb +0 -118
  112. data/lib/mocha/class_method.rb +0 -66
  113. data/lib/mocha/infinite_range.rb +0 -25
  114. data/lib/mocha/is_a.rb +0 -9
  115. data/lib/mocha/metaclass.rb +0 -7
  116. data/lib/mocha/missing_expectation.rb +0 -17
  117. data/lib/mocha/multiple_yields.rb +0 -20
  118. data/lib/mocha/no_yields.rb +0 -11
  119. data/lib/mocha/object.rb +0 -110
  120. data/lib/mocha/parameter_matchers/base.rb +0 -15
  121. data/lib/mocha/parameter_matchers/object.rb +0 -9
  122. data/lib/mocha/pretty_parameters.rb +0 -28
  123. data/lib/mocha/setup_and_teardown.rb +0 -23
  124. data/lib/mocha/single_yield.rb +0 -18
  125. data/lib/mocha/standalone.rb +0 -32
  126. data/lib/mocha/stub.rb +0 -18
  127. data/lib/mocha/test_case_adapter.rb +0 -49
  128. data/lib/mocha_standalone.rb +0 -2
  129. data/lib/stubba.rb +0 -2
  130. data/test/acceptance/expected_invocation_count_acceptance_test.rb +0 -187
  131. data/test/acceptance/mocha_acceptance_test.rb +0 -98
  132. data/test/acceptance/mock_with_initializer_block_acceptance_test.rb +0 -44
  133. data/test/acceptance/mocked_methods_dispatch_acceptance_test.rb +0 -71
  134. data/test/acceptance/optional_parameters_acceptance_test.rb +0 -63
  135. data/test/acceptance/parameter_matcher_acceptance_test.rb +0 -117
  136. data/test/acceptance/partial_mocks_acceptance_test.rb +0 -40
  137. data/test/acceptance/sequence_acceptance_test.rb +0 -179
  138. data/test/acceptance/standalone_acceptance_test.rb +0 -131
  139. data/test/acceptance/stubba_acceptance_test.rb +0 -102
  140. data/test/active_record_test_case.rb +0 -36
  141. data/test/deprecation_disabler.rb +0 -15
  142. data/test/execution_point.rb +0 -34
  143. data/test/integration/mocha_test_result_integration_test.rb +0 -105
  144. data/test/integration/stubba_integration_test.rb +0 -89
  145. data/test/integration/stubba_test_result_integration_test.rb +0 -85
  146. data/test/method_definer.rb +0 -18
  147. data/test/test_helper.rb +0 -12
  148. data/test/test_runner.rb +0 -31
  149. data/test/unit/any_instance_method_test.rb +0 -126
  150. data/test/unit/array_inspect_test.rb +0 -16
  151. data/test/unit/auto_verify_test.rb +0 -129
  152. data/test/unit/central_test.rb +0 -124
  153. data/test/unit/class_method_test.rb +0 -200
  154. data/test/unit/date_time_inspect_test.rb +0 -21
  155. data/test/unit/expectation_error_test.rb +0 -24
  156. data/test/unit/expectation_list_test.rb +0 -75
  157. data/test/unit/expectation_raiser_test.rb +0 -28
  158. data/test/unit/expectation_test.rb +0 -483
  159. data/test/unit/hash_inspect_test.rb +0 -16
  160. data/test/unit/infinite_range_test.rb +0 -53
  161. data/test/unit/metaclass_test.rb +0 -22
  162. data/test/unit/method_matcher_test.rb +0 -23
  163. data/test/unit/missing_expectation_test.rb +0 -42
  164. data/test/unit/mock_test.rb +0 -323
  165. data/test/unit/multiple_yields_test.rb +0 -18
  166. data/test/unit/no_yield_test.rb +0 -18
  167. data/test/unit/object_inspect_test.rb +0 -37
  168. data/test/unit/object_test.rb +0 -165
  169. data/test/unit/parameter_matchers/all_of_test.rb +0 -26
  170. data/test/unit/parameter_matchers/any_of_test.rb +0 -26
  171. data/test/unit/parameter_matchers/anything_test.rb +0 -21
  172. data/test/unit/parameter_matchers/has_entries_test.rb +0 -30
  173. data/test/unit/parameter_matchers/has_entry_test.rb +0 -40
  174. data/test/unit/parameter_matchers/has_key_test.rb +0 -25
  175. data/test/unit/parameter_matchers/has_value_test.rb +0 -25
  176. data/test/unit/parameter_matchers/includes_test.rb +0 -25
  177. data/test/unit/parameter_matchers/instance_of_test.rb +0 -25
  178. data/test/unit/parameter_matchers/is_a_test.rb +0 -25
  179. data/test/unit/parameter_matchers/kind_of_test.rb +0 -25
  180. data/test/unit/parameter_matchers/not_test.rb +0 -26
  181. data/test/unit/parameter_matchers/regexp_matches_test.rb +0 -25
  182. data/test/unit/parameter_matchers/stub_matcher.rb +0 -23
  183. data/test/unit/parameters_matcher_test.rb +0 -121
  184. data/test/unit/return_values_test.rb +0 -63
  185. data/test/unit/sequence_test.rb +0 -104
  186. data/test/unit/setup_and_teardown_test.rb +0 -76
  187. data/test/unit/single_return_value_test.rb +0 -33
  188. data/test/unit/single_yield_test.rb +0 -18
  189. data/test/unit/string_inspect_test.rb +0 -11
  190. data/test/unit/stub_test.rb +0 -24
  191. data/test/unit/yield_parameters_test.rb +0 -93
data/lib/mocha/mock.rb CHANGED
@@ -1,97 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby2_keywords'
1
4
  require 'mocha/expectation'
2
5
  require 'mocha/expectation_list'
3
- require 'mocha/stub'
4
- require 'mocha/missing_expectation'
5
- require 'mocha/metaclass'
6
+ require 'mocha/invocation'
7
+ require 'mocha/default_name'
8
+ require 'mocha/default_receiver'
9
+ require 'mocha/method_matcher'
10
+ require 'mocha/parameters_matcher'
11
+ require 'mocha/argument_iterator'
12
+ require 'mocha/expectation_error_factory'
13
+ require 'mocha/deprecation'
6
14
 
7
- module Mocha # :nodoc:
8
-
15
+ module Mocha
9
16
  # Traditional mock object.
10
17
  #
11
- # Methods return an Expectation which can be further modified by methods on Expectation.
18
+ # {expects} and {stubs} return an {Expectation} which can be further modified
19
+ # by methods on {Expectation}.
20
+ #
21
+ # {responds_like} and {responds_like_instance_of} both return a {Mock}, and
22
+ # can therefore, be chained to the original creation methods in {API}.
23
+ # They force the mock to indicate what it is supposed to be mocking, thus
24
+ # making it a safer verifying mock. They check that the underlying +responder+
25
+ # will actually respond to the methods being stubbed, throwing a
26
+ # +NoMethodError+ upon invocation otherwise.
27
+ #
28
+ # Stubs and expectations are basically the same thing. A stub is just an
29
+ # expectation of zero or more invocations. The {#stubs} method is syntactic
30
+ # sugar to make the intent of the test more explicit.
31
+ #
32
+ # When a method is invoked on a mock object, the mock object searches through
33
+ # its expectations from newest to oldest to find one that matches the
34
+ # invocation. After the invocation, the matching expectation might stop
35
+ # matching further invocations. For example, an +expects(:foo).once+
36
+ # expectation only matches once and will be ignored on future invocations
37
+ # while an +expects(:foo).at_least_once+ expectation will always be matched
38
+ # against invocations.
39
+ #
40
+ # However, note that if the expectation that matches the invocation has a
41
+ # cardinality of "never", then an unexpected invocation error is reported.
42
+ #
43
+ # This scheme allows you to:
44
+ #
45
+ # - Set up default stubs in your the +setup+ method of your test class and
46
+ # override some of those stubs in individual tests.
47
+ # - Set up different +once+ expectations for the same method with different
48
+ # action per invocation. However, it's better to use the
49
+ # {Expectation#returns} method with multiple arguments to do this, as
50
+ # described below.
51
+ #
52
+ # However, there are some possible "gotchas" caused by this scheme:
53
+ #
54
+ # - if you create an expectation and then a stub for the same method, the
55
+ # stub will always override the expectation and the expectation will never
56
+ # be met.
57
+ # - if you create a stub and then an expectation for the same method, the
58
+ # expectation will match, and when it stops matching the stub will be used
59
+ # instead, possibly masking test failures.
60
+ # - if you create different expectations for the same method, they will be
61
+ # invoked in the opposite order than that in which they were specified,
62
+ # rather than the same order.
63
+ #
64
+ # The best thing to do is not set up multiple expectations and stubs for the
65
+ # same method with exactly the same matchers. Instead, use the
66
+ # {Expectation#returns} method with multiple arguments to create multiple
67
+ # actions for a method. You can also chain multiple calls to
68
+ # {Expectation#returns} and {Expectation#raises} (along with syntactic sugar
69
+ # {Expectation#then} if desired).
70
+ #
71
+ # @example
72
+ # object = mock()
73
+ # object.stubs(:expected_method).returns(1, 2).then.raises(Exception)
74
+ # object.expected_method # => 1
75
+ # object.expected_method # => 2
76
+ # object.expected_method # => raises exception of class Exception1
77
+ #
78
+ # If you want to specify more complex ordering or order invocations across
79
+ # different mock objects, use the {Expectation#in_sequence} method to
80
+ # explicitly define a total or partial ordering of invocations.
12
81
  class Mock
13
-
14
- # :call-seq: expects(method_name) -> expectation
15
- # expects(method_names) -> last expectation
82
+ # Adds an expectation that the specified method must be called exactly once with any parameters.
16
83
  #
17
- # Adds an expectation that a method identified by +method_name+ symbol must be called exactly once with any parameters.
18
- # Returns the new expectation which can be further modified by methods on Expectation.
19
- # object = mock()
20
- # object.expects(:method1)
21
- # object.method1
22
- # # no error raised
84
+ # @return [Expectation] last-built expectation which can be further modified by methods on {Expectation}.
23
85
  #
86
+ # @overload def expects(method_name)
87
+ # @param [Symbol,String] method_name name of expected method
88
+ # @overload def expects(expected_methods_vs_return_values)
89
+ # @param [Hash] expected_methods_vs_return_values expected method name symbols as keys and corresponding return values as values - these expectations are setup as if {#expects} were called multiple times.
90
+ #
91
+ # @example Expected method invoked once so no error raised
24
92
  # object = mock()
25
- # object.expects(:method1)
26
- # # error raised, because method1 not called exactly once
27
- # If +method_names+ is a +Hash+, an expectation will be set up for each entry using the key as +method_name+ and value as +return_value+.
93
+ # object.expects(:expected_method)
94
+ # object.expected_method
95
+ #
96
+ # @example Expected method not invoked so error raised
28
97
  # object = mock()
29
- # object.expects(:method1 => :result1, :method2 => :result2)
98
+ # object.expects(:expected_method)
99
+ # # error raised when test completes, because expected_method not called exactly once
30
100
  #
31
- # # exactly equivalent to
101
+ # @example Expected method invoked twice so error raised
102
+ # object = mock()
103
+ # object.expects(:expected_method)
104
+ # object.expected_method
105
+ # object.expected_method # => error raised when expected method invoked second time
32
106
  #
107
+ # @example Setup multiple expectations using +expected_methods_vs_return_values+.
33
108
  # object = mock()
34
- # object.expects(:method1).returns(:result1)
35
- # object.expects(:method2).returns(:result2)
109
+ # object.expects(expected_method_one: :result_one, expected_method_two: :result_two)
36
110
  #
37
- # Aliased by <tt>\_\_expects\_\_</tt>
111
+ # # is exactly equivalent to
112
+ #
113
+ # object = mock()
114
+ # object.expects(:expected_method_one).returns(:result_one)
115
+ # object.expects(:expected_method_two).returns(:result_two)
38
116
  def expects(method_name_or_hash, backtrace = nil)
39
- if method_name_or_hash.is_a?(Hash) then
40
- method_name_or_hash.each do |method_name, return_value|
41
- ensure_method_not_already_defined(method_name)
42
- @expectations.add(Expectation.new(self, method_name, backtrace).returns(return_value))
43
- end
44
- else
45
- ensure_method_not_already_defined(method_name_or_hash)
46
- @expectations.add(Expectation.new(self, method_name_or_hash, backtrace))
117
+ expectation = nil
118
+ iterator = ArgumentIterator.new(method_name_or_hash)
119
+ iterator.each do |method_name, *args|
120
+ ensure_method_not_already_defined(method_name)
121
+ expectation = Expectation.new(self, method_name, backtrace)
122
+ expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
123
+ expectation.returns(args.shift) unless args.empty?
124
+ @expectations.add(expectation)
47
125
  end
126
+ expectation
48
127
  end
49
-
50
- # :call-seq: stubs(method_name) -> expectation
51
- # stubs(method_names) -> last expectation
128
+
129
+ # Adds an expectation that the specified method may be called any number of times with any parameters.
130
+ #
131
+ # @return [Expectation] last-built expectation which can be further modified by methods on {Expectation}.
52
132
  #
53
- # Adds an expectation that a method identified by +method_name+ symbol may be called any number of times with any parameters.
54
- # Returns the new expectation which can be further modified by methods on Expectation.
133
+ # @overload def stubs(method_name)
134
+ # @param [Symbol,String] method_name name of stubbed method
135
+ # @overload def stubs(stubbed_methods_vs_return_values)
136
+ # @param [Hash] stubbed_methods_vs_return_values stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {#stubs} were called multiple times.
137
+ #
138
+ # @example No error raised however many times stubbed method is invoked
55
139
  # object = mock()
56
- # object.stubs(:method1)
57
- # object.method1
58
- # object.method1
140
+ # object.stubs(:stubbed_method)
141
+ # object.stubbed_method
142
+ # object.stubbed_method
59
143
  # # no error raised
60
- # If +method_names+ is a +Hash+, an expectation will be set up for each entry using the key as +method_name+ and value as +return_value+.
144
+ #
145
+ # @example Setup multiple expectations using +stubbed_methods_vs_return_values+.
61
146
  # object = mock()
62
- # object.stubs(:method1 => :result1, :method2 => :result2)
147
+ # object.stubs(stubbed_method_one: :result_one, stubbed_method_two: :result_two)
63
148
  #
64
- # # exactly equivalent to
149
+ # # is exactly equivalent to
65
150
  #
66
151
  # object = mock()
67
- # object.stubs(:method1).returns(:result1)
68
- # object.stubs(:method2).returns(:result2)
69
- #
70
- # Aliased by <tt>\_\_stubs\_\_</tt>
152
+ # object.stubs(:stubbed_method_one).returns(:result_one)
153
+ # object.stubs(:stubbed_method_two).returns(:result_two)
71
154
  def stubs(method_name_or_hash, backtrace = nil)
72
- if method_name_or_hash.is_a?(Hash) then
73
- method_name_or_hash.each do |method_name, return_value|
74
- ensure_method_not_already_defined(method_name)
75
- @expectations.add(Stub.new(self, method_name, backtrace).returns(return_value))
76
- end
77
- else
78
- ensure_method_not_already_defined(method_name_or_hash)
79
- @expectations.add(Stub.new(self, method_name_or_hash, backtrace))
155
+ expectation = nil
156
+ iterator = ArgumentIterator.new(method_name_or_hash)
157
+ iterator.each do |method_name, *args|
158
+ ensure_method_not_already_defined(method_name)
159
+ expectation = Expectation.new(self, method_name, backtrace)
160
+ expectation.at_least(0)
161
+ expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
162
+ expectation.returns(args.shift) unless args.empty?
163
+ @expectations.add(expectation)
80
164
  end
165
+ expectation
81
166
  end
82
-
83
- # :call-seq: responds_like(responder) -> mock
167
+
168
+ # Removes the specified stubbed methods (added by calls to {#expects} or {#stubs}) and all expectations associated with them.
84
169
  #
85
- # Constrains the +mock+ so that it can only expect or stub methods to which +responder+ responds. The constraint is only applied at method invocation time.
170
+ # @param [Array<Symbol>] method_names names of methods to unstub.
86
171
  #
87
- # A +NoMethodError+ will be raised if the +responder+ does not <tt>respond_to?</tt> a method invocation (even if the method has been expected or stubbed).
172
+ # @example Invoking an unstubbed method causes error to be raised
173
+ # object = mock('mock')
174
+ # object.stubs(:stubbed_method).returns(:result_one)
175
+ # object.stubbed_method # => :result_one
176
+ # object.unstub(:stubbed_method)
177
+ # object.stubbed_method # => unexpected invocation: #<Mock:mock>.stubbed_method()
88
178
  #
89
- # The +mock+ will delegate its <tt>respond_to?</tt> method to the +responder+.
90
- # class Sheep
91
- # def chew(grass); end
92
- # def self.number_of_legs; end
93
- # end
179
+ # @example Unstubbing multiple methods.
180
+ # multiplier.unstub(:double, :triple)
181
+ #
182
+ # # exactly equivalent to
183
+ #
184
+ # multiplier.unstub(:double)
185
+ # multiplier.unstub(:triple)
186
+ def unstub(*method_names)
187
+ method_names.each do |method_name|
188
+ @expectations.remove_all_matching_method(method_name)
189
+ end
190
+ end
191
+
192
+ # Constrains the {Mock} instance so that it can only expect or stub methods to which +responder+ responds publicly. The constraint is only applied at method invocation time.
94
193
  #
194
+ # A +NoMethodError+ will be raised if the +responder+ does not publicly +#respond_to?+ the invoked method (even if the method has been expected or stubbed).
195
+ #
196
+ # The {Mock} instance will delegate its +#respond_to?+ method to the +responder+. However, the +include_all+ parameter is not passed through, so only public methods on the +responder+ will be considered.
197
+ #
198
+ # Note that the methods on +responder+ are never actually invoked.
199
+ #
200
+ # @param [Object, #respond_to?] responder an object used to determine whether {Mock} instance should +#respond_to?+ to an invocation.
201
+ # @return [Mock] the same {Mock} instance, thereby allowing invocations of other {Mock} methods to be chained.
202
+ # @see #responds_like_instance_of
203
+ #
204
+ # @example Normal mocking
95
205
  # sheep = mock('sheep')
96
206
  # sheep.expects(:chew)
97
207
  # sheep.expects(:foo)
@@ -101,6 +211,11 @@ module Mocha # :nodoc:
101
211
  # sheep.foo
102
212
  # # no error raised
103
213
  #
214
+ # @example Using {#responds_like} with an instance method
215
+ # class Sheep
216
+ # def chew(grass); end
217
+ # end
218
+ #
104
219
  # sheep = mock('sheep')
105
220
  # sheep.responds_like(Sheep.new)
106
221
  # sheep.expects(:chew)
@@ -110,93 +225,203 @@ module Mocha # :nodoc:
110
225
  # sheep.chew
111
226
  # sheep.foo # => raises NoMethodError exception
112
227
  #
228
+ # @example Using {#responds_like} with a class method
229
+ # class Sheep
230
+ # def self.number_of_legs; end
231
+ # end
232
+ #
113
233
  # sheep_class = mock('sheep_class')
114
234
  # sheep_class.responds_like(Sheep)
115
235
  # sheep_class.stubs(:number_of_legs).returns(4)
116
236
  # sheep_class.expects(:foo)
117
237
  # sheep_class.respond_to?(:number_of_legs) # => true
118
238
  # sheep_class.respond_to?(:foo) # => false
119
- # assert_equal 4, sheep_class.number_of_legs
239
+ # sheep_class.number_of_legs # => 4
120
240
  # sheep_class.foo # => raises NoMethodError exception
121
- #
122
- # Aliased by +quacks_like+
123
- def responds_like(object)
124
- @responder = object
241
+ def responds_like(responder)
242
+ @responder = responder
125
243
  self
126
244
  end
127
-
128
- # :stopdoc:
129
-
130
- def initialize(name = nil, &block)
131
- @mock_name = name
245
+
246
+ # Constrains the {Mock} instance so that it can only expect or stub methods to which an instance of the +responder_class+ responds publicly. The constraint is only applied at method invocation time. Note that the responder instance is instantiated using +Class#allocate+.
247
+ #
248
+ # A +NoMethodError+ will be raised if the responder instance does not publicly +#respond_to?+ the invoked method (even if the method has been expected or stubbed).
249
+ #
250
+ # The {Mock} instance will delegate its +#respond_to?+ method to the responder instance. However, the +include_all+ parameter is not passed through, so only public methods on the +responder+ will be considered.
251
+ #
252
+ # Note that the methods on the responder instance are never actually invoked.
253
+ #
254
+ # @param [Class] responder_class a class used to determine whether {Mock} instance should +#respond_to?+ to an invocation.
255
+ # @return [Mock] the same {Mock} instance, thereby allowing invocations of other {Mock} methods to be chained.
256
+ # @see #responds_like
257
+ #
258
+ # @example Using {#responds_like_instance_of}
259
+ # class Sheep
260
+ # def initialize
261
+ # raise "some awkward code we don't want to call"
262
+ # end
263
+ # def chew(grass); end
264
+ # end
265
+ #
266
+ # sheep = mock('sheep')
267
+ # sheep.responds_like_instance_of(Sheep)
268
+ # sheep.expects(:chew)
269
+ # sheep.expects(:foo)
270
+ # sheep.respond_to?(:chew) # => true
271
+ # sheep.respond_to?(:foo) # => false
272
+ # sheep.chew
273
+ # sheep.foo # => raises NoMethodError exception
274
+ def responds_like_instance_of(responder_class)
275
+ responds_like(responder_class.allocate)
276
+ end
277
+
278
+ # @private
279
+ def initialize(mockery, assertion_counter, name = nil, receiver = nil)
280
+ @mockery = mockery
281
+ @assertion_counter = assertion_counter
282
+ @name = name || DefaultName.new(self)
283
+ @receiver = receiver || DefaultReceiver.new(self)
132
284
  @expectations = ExpectationList.new
133
285
  @everything_stubbed = false
134
286
  @responder = nil
135
- instance_eval(&block) if block
287
+ @unexpected_invocation = nil
288
+ @expired = false
136
289
  end
137
290
 
138
- attr_reader :everything_stubbed, :expectations
291
+ # @private
292
+ attr_reader :everything_stubbed
139
293
 
140
294
  alias_method :__expects__, :expects
141
295
 
142
296
  alias_method :__stubs__, :stubs
143
-
297
+
298
+ alias_method :__singleton_class__, :singleton_class
299
+
144
300
  alias_method :quacks_like, :responds_like
301
+ alias_method :quacks_like_instance_of, :responds_like_instance_of
145
302
 
146
- def add_expectation(expectation)
147
- @expectations.add(expectation)
303
+ # @private
304
+ def __expectations__
305
+ @expectations
148
306
  end
149
-
307
+
308
+ # @private
150
309
  def stub_everything
151
310
  @everything_stubbed = true
152
311
  end
153
-
312
+
313
+ # @private
314
+ def all_expectations
315
+ @receiver.mocks.inject(ExpectationList.new) { |e, m| e + m.__expectations__ }
316
+ end
317
+
318
+ # @private
154
319
  def method_missing(symbol, *arguments, &block)
155
- if @responder and not @responder.respond_to?(symbol)
156
- raise NoMethodError, "undefined method `#{symbol}' for #{self.mocha_inspect} which responds like #{@responder.mocha_inspect}"
320
+ handle_method_call(symbol, arguments, block)
321
+ end
322
+ ruby2_keywords(:method_missing)
323
+
324
+ # @private
325
+ def handle_method_call(symbol, arguments, block)
326
+ check_expiry
327
+ check_responder_responds_to(symbol)
328
+ invocation = Invocation.new(self, symbol, arguments, block)
329
+
330
+ matching_expectations = all_expectations.matching_expectations(invocation)
331
+
332
+ index = 0
333
+ while index < matching_expectations.length
334
+ matching_expectation = matching_expectations[index]
335
+ if matching_expectation.invocations_never_allowed?
336
+ raise_unexpected_invocation_error(invocation, matching_expectation)
337
+ elsif matching_expectation.invocations_allowed?
338
+ return matching_expectation.invoke(invocation)
339
+ end
340
+ index += 1
157
341
  end
158
- matching_expectation = @expectations.detect(symbol, *arguments)
159
- if matching_expectation then
160
- matching_expectation.invoke(&block)
161
- elsif @everything_stubbed then
162
- return
163
- else
164
- unexpected_method_called(symbol, *arguments)
342
+
343
+ matching_expectation_ignoring_order = all_expectations.match(invocation, ignoring_order: true)
344
+ if matching_expectation_ignoring_order || (!matching_expectation_ignoring_order && !@everything_stubbed) # rubocop:disable Style/GuardClause
345
+ raise_unexpected_invocation_error(invocation, matching_expectation_ignoring_order)
165
346
  end
166
347
  end
167
-
168
- def respond_to?(symbol)
169
- if @responder then
348
+
349
+ # @private
350
+ def respond_to_missing?(symbol, _include_all)
351
+ if @responder
170
352
  @responder.respond_to?(symbol)
171
353
  else
172
- @expectations.matches_method?(symbol)
354
+ @everything_stubbed || all_expectations.matches_method?(symbol)
173
355
  end
174
356
  end
175
-
176
- def unexpected_method_called(symbol, *arguments)
177
- MissingExpectation.new(self, symbol).with(*arguments).verify
357
+
358
+ # @private
359
+ def __verified__?
360
+ @expectations.verified?(@assertion_counter)
178
361
  end
179
-
180
- def verify(&block)
181
- @expectations.verify(&block)
362
+
363
+ # @private
364
+ def __expire__(origin)
365
+ @expired = origin || true
182
366
  end
183
-
367
+
368
+ # @private
184
369
  def mocha_inspect
185
- address = self.__id__ * 2
186
- address += 0x100000000 if address < 0
187
- @mock_name ? "#<Mock:#{@mock_name}>" : "#<Mock:0x#{'%x' % address}>"
370
+ @name.mocha_inspect
188
371
  end
189
-
372
+
373
+ # @private
190
374
  def inspect
191
375
  mocha_inspect
192
376
  end
193
-
377
+
378
+ # @private
194
379
  def ensure_method_not_already_defined(method_name)
195
- self.__metaclass__.send(:undef_method, method_name) if self.__metaclass__.method_defined?(method_name)
380
+ __singleton_class__.send(:undef_method, method_name) if __singleton_class__.method_defined?(method_name) || __singleton_class__.private_method_defined?(method_name)
196
381
  end
197
382
 
198
- # :startdoc:
383
+ # @private
384
+ def any_expectations?
385
+ @expectations.any?
386
+ end
199
387
 
200
- end
388
+ private
201
389
 
390
+ def raise_unexpected_invocation_error(invocation, matching_expectation)
391
+ if @unexpected_invocation.nil?
392
+ @unexpected_invocation = invocation
393
+ if matching_expectation
394
+ matching_expectation.invoke(invocation)
395
+ @assertion_counter.increment
396
+ end
397
+ call_description = @unexpected_invocation.call_description
398
+ if matching_expectation && !matching_expectation.in_correct_order?
399
+ call_description += ' invoked out of order'
400
+ end
401
+ message = "#{call_description}\n#{@mockery.mocha_inspect}"
402
+ else
403
+ message = @unexpected_invocation.short_call_description
404
+ end
405
+ raise ExpectationErrorFactory.build("unexpected invocation: #{message}", caller)
406
+ end
407
+
408
+ def check_responder_responds_to(symbol)
409
+ if @responder && !@responder.respond_to?(symbol) # rubocop:disable Style/GuardClause
410
+ raise NoMethodError, "undefined method `#{symbol}' for #{mocha_inspect} which responds like #{@responder.mocha_inspect}"
411
+ end
412
+ end
413
+
414
+ def check_expiry
415
+ return unless @expired
416
+
417
+ origin = @expired == true ? 'one test' : @expired
418
+
419
+ sentences = [
420
+ "#{mocha_inspect} was instantiated in #{origin} but it is receiving invocations within another test.",
421
+ 'This can lead to unintended interactions between tests and hence unexpected test failures.',
422
+ 'Ensure that every test correctly cleans up any state that it introduces.'
423
+ ]
424
+ raise StubbingError.new(sentences.join(' '), caller)
425
+ end
426
+ end
202
427
  end