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
@@ -1,22 +1,29 @@
1
- require 'mocha/infinite_range'
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby2_keywords'
2
4
  require 'mocha/method_matcher'
3
5
  require 'mocha/parameters_matcher'
4
6
  require 'mocha/expectation_error'
5
7
  require 'mocha/return_values'
6
8
  require 'mocha/exception_raiser'
9
+ require 'mocha/thrower'
7
10
  require 'mocha/yield_parameters'
8
- require 'mocha/is_a'
11
+ require 'mocha/in_state_ordering_constraint'
12
+ require 'mocha/change_state_side_effect'
13
+ require 'mocha/cardinality'
14
+ require 'mocha/configuration'
15
+ require 'mocha/block_matchers'
16
+ require 'mocha/backtrace_filter'
9
17
 
10
- module Mocha # :nodoc:
11
-
12
- # Methods on expectations returned from Mock#expects, Mock#stubs, Object#expects and Object#stubs.
18
+ module Mocha
19
+ # Methods on expectations returned from {Mock#expects}, {Mock#stubs}, {ObjectMethods#expects} and {ObjectMethods#stubs}.
13
20
  class Expectation
14
-
15
- # :call-seq: times(range) -> expectation
21
+ # Modifies expectation so that the number of invocations of the expected method must be within a specified range or exactly equal to a specified number.
16
22
  #
17
- # Modifies expectation so that the number of calls to the expected method must be within a specific +range+.
23
+ # @param [Range,Integer] range_or_number specifies the allowable range for the number of expected invocations or the specified number of expected invocations.
24
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
18
25
  #
19
- # +range+ can be specified as an exact integer or as a range of integers
26
+ # @example Specifying an exact number of expected invocations.
20
27
  # object = mock()
21
28
  # object.expects(:expected_method).times(3)
22
29
  # 3.times { object.expected_method }
@@ -27,6 +34,7 @@ module Mocha # :nodoc:
27
34
  # 2.times { object.expected_method }
28
35
  # # => verify fails
29
36
  #
37
+ # @example Specifying a range for the number of expected invocations.
30
38
  # object = mock()
31
39
  # object.expects(:expected_method).times(2..4)
32
40
  # 3.times { object.expected_method }
@@ -36,15 +44,73 @@ module Mocha # :nodoc:
36
44
  # object.expects(:expected_method).times(2..4)
37
45
  # object.expected_method
38
46
  # # => verify fails
39
- def times(range)
40
- @expected_count = range
47
+ def times(range_or_number)
48
+ @cardinality.times(range_or_number)
41
49
  self
42
50
  end
43
-
44
- # :call-seq: once() -> expectation
51
+
52
+ # Modifies expectation so that the expected method must be called exactly three times. This is equivalent to calling {#times} with an argument of +3+.
53
+ #
54
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
55
+ #
56
+ # @example Expected method must be invoked exactly three times.
57
+ # object = mock()
58
+ # object.expects(:expected_method).thrice
59
+ # object.expected_method
60
+ # object.expected_method
61
+ # object.expected_method
62
+ # # => verify succeeds
63
+ #
64
+ # object = mock()
65
+ # object.expects(:expected_method).thrice
66
+ # object.expected_method
67
+ # object.expected_method
68
+ # object.expected_method
69
+ # object.expected_method # => unexpected invocation
70
+ #
71
+ # object = mock()
72
+ # object.expects(:expected_method).thrice
73
+ # object.expected_method
74
+ # object.expected_method
75
+ # # => verify fails
76
+ def thrice
77
+ @cardinality.exactly(3)
78
+ self
79
+ end
80
+
81
+ # Modifies expectation so that the expected method must be called exactly twice. This is equivalent to calling {#times} with an argument of +2+.
82
+ #
83
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
84
+ #
85
+ # @example Expected method must be invoked exactly twice.
86
+ # object = mock()
87
+ # object.expects(:expected_method).twice
88
+ # object.expected_method
89
+ # object.expected_method
90
+ # # => verify succeeds
91
+ #
92
+ # object = mock()
93
+ # object.expects(:expected_method).twice
94
+ # object.expected_method
95
+ # object.expected_method
96
+ # object.expected_method # => unexpected invocation
97
+ #
98
+ # object = mock()
99
+ # object.expects(:expected_method).twice
100
+ # object.expected_method
101
+ # # => verify fails
102
+ def twice
103
+ @cardinality.exactly(2)
104
+ self
105
+ end
106
+
107
+ # Modifies expectation so that the expected method must be called exactly once. This is equivalent to calling {#times} with an argument of +1+.
45
108
  #
46
- # Modifies expectation so that the expected method must be called exactly once.
47
109
  # Note that this is the default behaviour for an expectation, but you may wish to use it for clarity/emphasis.
110
+ #
111
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
112
+ #
113
+ # @example Expected method must be invoked exactly once.
48
114
  # object = mock()
49
115
  # object.expects(:expected_method).once
50
116
  # object.expected_method
@@ -53,37 +119,39 @@ module Mocha # :nodoc:
53
119
  # object = mock()
54
120
  # object.expects(:expected_method).once
55
121
  # object.expected_method
56
- # object.expected_method
57
- # # => verify fails
122
+ # object.expected_method # => unexpected invocation
58
123
  #
59
124
  # object = mock()
60
125
  # object.expects(:expected_method).once
61
126
  # # => verify fails
62
- def once()
63
- times(1)
127
+ def once
128
+ @cardinality.exactly(1)
64
129
  self
65
130
  end
66
-
67
- # :call-seq: never() -> expectation
68
- #
131
+
69
132
  # Modifies expectation so that the expected method must never be called.
133
+ #
134
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
135
+ #
136
+ # @example Expected method must never be called.
70
137
  # object = mock()
71
138
  # object.expects(:expected_method).never
72
- # object.expected_method
73
- # # => verify fails
139
+ # object.expected_method # => unexpected invocation
74
140
  #
75
141
  # object = mock()
76
142
  # object.expects(:expected_method).never
77
- # object.expected_method
78
143
  # # => verify succeeds
79
144
  def never
80
- times(0)
145
+ @cardinality.exactly(0)
81
146
  self
82
147
  end
83
-
84
- # :call-seq: at_least(minimum_number_of_times) -> expectation
85
- #
148
+
86
149
  # Modifies expectation so that the expected method must be called at least a +minimum_number_of_times+.
150
+ #
151
+ # @param [Integer] minimum_number_of_times minimum number of expected invocations.
152
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
153
+ #
154
+ # @example Expected method must be called at least twice.
87
155
  # object = mock()
88
156
  # object.expects(:expected_method).at_least(2)
89
157
  # 3.times { object.expected_method }
@@ -94,13 +162,15 @@ module Mocha # :nodoc:
94
162
  # object.expected_method
95
163
  # # => verify fails
96
164
  def at_least(minimum_number_of_times)
97
- times(Range.at_least(minimum_number_of_times))
165
+ @cardinality.at_least(minimum_number_of_times)
98
166
  self
99
167
  end
100
-
101
- # :call-seq: at_least_once() -> expectation
168
+
169
+ # Modifies expectation so that the expected method must be called at least once. This is equivalent to calling {#at_least} with an argument of +1+.
102
170
  #
103
- # Modifies expectation so that the expected method must be called at least once.
171
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
172
+ #
173
+ # @example Expected method must be called at least once.
104
174
  # object = mock()
105
175
  # object.expects(:expected_method).at_least_once
106
176
  # object.expected_method
@@ -109,14 +179,16 @@ module Mocha # :nodoc:
109
179
  # object = mock()
110
180
  # object.expects(:expected_method).at_least_once
111
181
  # # => verify fails
112
- def at_least_once()
182
+ def at_least_once
113
183
  at_least(1)
114
- self
115
184
  end
116
-
117
- # :call-seq: at_most(maximum_number_of_times) -> expectation
118
- #
185
+
119
186
  # Modifies expectation so that the expected method must be called at most a +maximum_number_of_times+.
187
+ #
188
+ # @param [Integer] maximum_number_of_times maximum number of expected invocations.
189
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
190
+ #
191
+ # @example Expected method must be called at most twice.
120
192
  # object = mock()
121
193
  # object.expects(:expected_method).at_most(2)
122
194
  # 2.times { object.expected_method }
@@ -124,16 +196,17 @@ module Mocha # :nodoc:
124
196
  #
125
197
  # object = mock()
126
198
  # object.expects(:expected_method).at_most(2)
127
- # 3.times { object.expected_method }
128
- # # => verify fails
199
+ # 3.times { object.expected_method } # => unexpected invocation
129
200
  def at_most(maximum_number_of_times)
130
- times(Range.at_most(maximum_number_of_times))
201
+ @cardinality.at_most(maximum_number_of_times)
131
202
  self
132
203
  end
133
-
134
- # :call-seq: at_most_once() -> expectation
204
+
205
+ # Modifies expectation so that the expected method must be called at most once. This is equivalent to calling {#at_most} with an argument of +1+.
206
+ #
207
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
135
208
  #
136
- # Modifies expectation so that the expected method must be called at most once.
209
+ # @example Expected method must be called at most once.
137
210
  # object = mock()
138
211
  # object.expects(:expected_method).at_most_once
139
212
  # object.expected_method
@@ -141,16 +214,36 @@ module Mocha # :nodoc:
141
214
  #
142
215
  # object = mock()
143
216
  # object.expects(:expected_method).at_most_once
144
- # 2.times { object.expected_method }
145
- # # => verify fails
146
- def at_most_once()
217
+ # 2.times { object.expected_method } # => unexpected invocation
218
+ def at_most_once
147
219
  at_most(1)
148
- self
149
220
  end
150
-
151
- # :call-seq: with(*expected_parameters, &matching_block) -> expectation
221
+
222
+ # Modifies expectation so that the expected method must be called with +expected_parameters_or_matchers+.
223
+ #
224
+ # May be used with Ruby literals or variables for exact matching or with parameter matchers for less-specific matching, e.g. {ParameterMatchers::Methods#includes}, {ParameterMatchers::Methods#has_key}, etc. See {ParameterMatchers} for a list of all available parameter matchers.
225
+ #
226
+ # Alternatively a block argument can be passed to {#with} to implement custom parameter matching. The block receives the +*actual_parameters+ as its arguments and should return +true+ if they are acceptable or +false+ otherwise. See the example below where a method is expected to be called with a value divisible by 4.
227
+ # The block argument takes precedence over +expected_parameters_or_matchers+. The block may be called multiple times per invocation of the expected method and so it should be idempotent.
228
+ #
229
+ # Note that if {#with} is called multiple times on the same expectation, the last call takes precedence; other calls are ignored.
230
+ #
231
+ # Positional arguments were separated from keyword arguments in Ruby v3 (see {https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0 this article}). In relation to this a new configuration option ({Configuration#strict_keyword_argument_matching=}) is available in Ruby >= 2.7.
152
232
  #
153
- # Modifies expectation so that the expected method must be called with +expected_parameters+.
233
+ # When {Configuration#strict_keyword_argument_matching=} is set to +false+ (which is the default in Ruby v2.7), a positional +Hash+ and a set of keyword arguments passed to {#with} are treated the same for the purposes of parameter matching. However, a deprecation warning will be displayed if a positional +Hash+ matches a set of keyword arguments or vice versa.
234
+ #
235
+ # When {Configuration#strict_keyword_argument_matching=} is set to +true+ (which is the default in Ruby >= v3.0), an actual positional +Hash+ will not match an expected set of keyword arguments; and vice versa, an actual set of keyword arguments will not match an expected positional +Hash+, i.e. the parameter matching is stricter.
236
+ #
237
+ # @see ParameterMatchers
238
+ # @see Configuration#strict_keyword_argument_matching=
239
+ #
240
+ # @param [Array<Object,ParameterMatchers::BaseMethods>] expected_parameters_or_matchers expected parameter values or parameter matchers.
241
+ # @yield optional block specifying custom matching.
242
+ # @yieldparam [Array<Object>] actual_parameters parameters with which expected method was invoked.
243
+ # @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable; +false+ otherwise.
244
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
245
+ #
246
+ # @example Expected method must be called with exact parameter values.
154
247
  # object = mock()
155
248
  # object.expects(:expected_method).with(:param1, :param2)
156
249
  # object.expected_method(:param1, :param2)
@@ -160,10 +253,49 @@ module Mocha # :nodoc:
160
253
  # object.expects(:expected_method).with(:param1, :param2)
161
254
  # object.expected_method(:param3)
162
255
  # # => verify fails
163
- # May be used with parameter matchers in Mocha::ParameterMatchers.
164
256
  #
165
- # If a +matching_block+ is given, the block is called with the parameters passed to the expected method.
166
- # The expectation is matched if the block evaluates to +true+.
257
+ # @example Expected method must be called with parameters matching parameter matchers.
258
+ # object = mock()
259
+ # object.expects(:expected_method).with(includes('string2'), anything)
260
+ # object.expected_method(['string1', 'string2'], 'any-old-value')
261
+ # # => verify succeeds
262
+ #
263
+ # object = mock()
264
+ # object.expects(:expected_method).with(includes('string2'), anything)
265
+ # object.expected_method(['string1'], 'any-old-value')
266
+ # # => verify fails
267
+ #
268
+ # @example Loose keyword argument matching (default in Ruby v2.7).
269
+ #
270
+ # Mocha.configure do |c|
271
+ # c.strict_keyword_argument_matching = false
272
+ # end
273
+ #
274
+ # class Example
275
+ # def foo(a, bar:); end
276
+ # end
277
+ #
278
+ # example = Example.new
279
+ # example.expects(:foo).with('a', bar: 'b')
280
+ # example.foo('a', { bar: 'b' })
281
+ # # This passes the test, but would result in an ArgumentError in practice
282
+ #
283
+ # @example Strict keyword argument matching (default in Ruby >= 3.0).
284
+ #
285
+ # Mocha.configure do |c|
286
+ # c.strict_keyword_argument_matching = true
287
+ # end
288
+ #
289
+ # class Example
290
+ # def foo(a, bar:); end
291
+ # end
292
+ #
293
+ # example = Example.new
294
+ # example.expects(:foo).with('a', bar: 'b')
295
+ # example.foo('a', { bar: 'b' })
296
+ # # This now fails as expected
297
+ #
298
+ # @example Using a block argument to expect the method to be called with a value divisible by 4.
167
299
  # object = mock()
168
300
  # object.expects(:expected_method).with() { |value| value % 4 == 0 }
169
301
  # object.expected_method(16)
@@ -173,108 +305,211 @@ module Mocha # :nodoc:
173
305
  # object.expects(:expected_method).with() { |value| value % 4 == 0 }
174
306
  # object.expected_method(17)
175
307
  # # => verify fails
176
- def with(*expected_parameters, &matching_block)
177
- @parameters_matcher = ParametersMatcher.new(expected_parameters, &matching_block)
308
+ #
309
+ # @example Extracting a custom matcher into an instance method on the test class.
310
+ # class MyTest < Minitest::Test
311
+ # def test_expected_method_is_called_with_a_value_divisible_by_4
312
+ # object = mock()
313
+ # object.expects(:expected_method).with(&method(:divisible_by_4))
314
+ # object.expected_method(16)
315
+ # # => verify succeeds
316
+ # end
317
+ #
318
+ # private
319
+ #
320
+ # def divisible_by_4(value)
321
+ # value % 4 == 0
322
+ # end
323
+ # end
324
+ def with(*expected_parameters_or_matchers, &matching_block)
325
+ @parameters_matcher = ParametersMatcher.new(expected_parameters_or_matchers, self, &matching_block)
178
326
  self
179
327
  end
180
-
181
- # :call-seq: yields(*parameters) -> expectation
328
+ ruby2_keywords(:with)
329
+
330
+ # Modifies expectation so that the expected method must be called with a block.
331
+ #
332
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
182
333
  #
183
- # Modifies expectation so that when the expected method is called, it yields with the specified +parameters+.
334
+ # @example Expected method must be called with a block.
184
335
  # object = mock()
185
- # object.expects(:expected_method).yields('result')
186
- # yielded_value = nil
187
- # object.expected_method { |value| yielded_value = value }
188
- # yielded_value # => 'result'
189
- # May be called multiple times on the same expectation for consecutive invocations. Also see Expectation#then.
336
+ # object.expects(:expected_method).with_block_given
337
+ # object.expected_method { 1 + 1 }
338
+ # # => verify succeeds
339
+ #
190
340
  # object = mock()
191
- # object.stubs(:expected_method).yields(1).then.yields(2)
192
- # yielded_values_from_first_invocation = []
193
- # yielded_values_from_second_invocation = []
194
- # object.expected_method { |value| yielded_values_from_first_invocation << value } # first invocation
195
- # object.expected_method { |value| yielded_values_from_second_invocation << value } # second invocation
196
- # yielded_values_from_first_invocation # => [1]
197
- # yielded_values_from_second_invocation # => [2]
198
- def yields(*parameters)
199
- @yield_parameters.add(*parameters)
341
+ # object.expects(:expected_method).with_block_given
342
+ # object.expected_method
343
+ # # => verify fails
344
+ def with_block_given
345
+ @block_matcher = BlockMatchers::BlockGiven.new
200
346
  self
201
347
  end
202
-
203
- # :call-seq: multiple_yields(*parameter_groups) -> expectation
348
+
349
+ # Modifies expectation so that the expected method must be called without a block.
204
350
  #
205
- # Modifies expectation so that when the expected method is called, it yields multiple times per invocation with the specified +parameter_groups+.
351
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
352
+ #
353
+ # @example Expected method must be called without a block.
206
354
  # object = mock()
207
- # object.expects(:expected_method).multiple_yields(['result_1', 'result_2'], ['result_3'])
208
- # yielded_values = []
209
- # object.expected_method { |*values| yielded_values << values }
210
- # yielded_values # => [['result_1', 'result_2'], ['result_3]]
211
- # May be called multiple times on the same expectation for consecutive invocations. Also see Expectation#then.
212
- # object = mock()
213
- # object.stubs(:expected_method).multiple_yields([1, 2], [3]).then.multiple_yields([4], [5, 6])
214
- # yielded_values_from_first_invocation = []
215
- # yielded_values_from_second_invocation = []
216
- # object.expected_method { |*values| yielded_values_from_first_invocation << values } # first invocation
217
- # object.expected_method { |*values| yielded_values_from_second_invocation << values } # second invocation
218
- # yielded_values_from_first_invocation # => [[1, 2], [3]]
219
- # yielded_values_from_second_invocation # => [[4], [5, 6]]
220
- def multiple_yields(*parameter_groups)
221
- @yield_parameters.multiple_add(*parameter_groups)
355
+ # object.expects(:expected_method).with_no_block_given
356
+ # object.expected_method
357
+ # # => verify succeeds
358
+ #
359
+ # object = mock()
360
+ # object.expects(:expected_method).with_block_given
361
+ # object.expected_method { 1 + 1 }
362
+ # # => verify fails
363
+ def with_no_block_given
364
+ @block_matcher = BlockMatchers::NoBlockGiven.new
222
365
  self
223
366
  end
224
-
225
- # :call-seq: returns(value) -> expectation
226
- # returns(*values) -> expectation
367
+
368
+ # Modifies expectation so that when the expected method is called, it yields to the block with the specified +parameters+.
369
+ #
370
+ # If no +parameters+ are specified, it yields to the block without any parameters.
371
+ #
372
+ # If no block is provided, the method will still attempt to yield resulting in a +LocalJumpError+. Note that this is what would happen if a "real" (non-mock) method implementation tried to yield to a non-existent block.
373
+ #
374
+ # May be called multiple times on the same expectation for consecutive invocations.
375
+ #
376
+ # @param [*Array] parameters parameters to be yielded.
377
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
378
+ # @see #then
379
+ #
380
+ # @example Yield when expected method is invoked.
381
+ # benchmark = mock()
382
+ # benchmark.expects(:measure).yields
383
+ # yielded = false
384
+ # benchmark.measure { yielded = true }
385
+ # yielded # => true
386
+ #
387
+ # @example Yield parameters when expected method is invoked.
388
+ # fibonacci = mock()
389
+ # fibonacci.expects(:next_pair).yields(0, 1)
390
+ # sum = 0
391
+ # fibonacci.next_pair { |first, second| sum = first + second }
392
+ # sum # => 1
393
+ #
394
+ # @example Yield different parameters on different invocations of the expected method.
395
+ # fibonacci = mock()
396
+ # fibonacci.expects(:next_pair).yields(0, 1).then.yields(1, 1)
397
+ # sum = 0
398
+ # fibonacci.next_pair { |first, second| sum = first + second }
399
+ # sum # => 1
400
+ # fibonacci.next_pair { |first, second| sum = first + second }
401
+ # sum # => 2
402
+ def yields(*parameters)
403
+ multiple_yields(parameters)
404
+ end
405
+
406
+ # Modifies expectation so that when the expected method is called, it yields multiple times per invocation with the specified +parameter_groups+.
407
+ #
408
+ # If no block is provided, the method will still attempt to yield resulting in a +LocalJumpError+. Note that this is what would happen if a "real" (non-mock) method implementation tried to yield to a non-existent block.
409
+ #
410
+ # @param [*Array<Array>] parameter_groups each element of +parameter_groups+ should iself be an +Array+ representing the parameters to be passed to the block for a single yield. Any element of +parameter_groups+ that is not an +Array+ is wrapped in an +Array+.
411
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
412
+ # @see #then
413
+ #
414
+ # @example When +foreach+ is called, the stub will invoke the block twice, the first time it passes ['row1_col1', 'row1_col2'] as the parameters, and the second time it passes ['row2_col1', ''] as the parameters.
415
+ # csv = mock()
416
+ # csv.expects(:foreach).with("path/to/file.csv").multiple_yields(['row1_col1', 'row1_col2'], ['row2_col1', ''])
417
+ # rows = []
418
+ # csv.foreach { |row| rows << row }
419
+ # rows # => [['row1_col1', 'row1_col2'], ['row2_col1', '']]
227
420
  #
421
+ # @example Yield different groups of parameters on different invocations of the expected method. Simulating a situation where the CSV file at 'path/to/file.csv' has been modified between the two calls to +foreach+.
422
+ # csv = mock()
423
+ # csv.stubs(:foreach).with("path/to/file.csv").multiple_yields(['old_row1_col1', 'old_row1_col2'], ['old_row2_col1', '']).then.multiple_yields(['new_row1_col1', ''], ['new_row2_col1', 'new_row2_col2'])
424
+ # rows_from_first_invocation = []
425
+ # rows_from_second_invocation = []
426
+ # csv.foreach { |row| rows_from_first_invocation << row } # first invocation
427
+ # csv.foreach { |row| rows_from_second_invocation << row } # second invocation
428
+ # rows_from_first_invocation # => [['old_row1_col1', 'old_row1_col2'], ['old_row2_col1', '']]
429
+ # rows_from_second_invocation # => [['new_row1_col1', ''], ['new_row2_col1', 'new_row2_col2']]
430
+ def multiple_yields(*parameter_groups)
431
+ @yield_parameters.add(*parameter_groups)
432
+ self
433
+ end
434
+
228
435
  # Modifies expectation so that when the expected method is called, it returns the specified +value+.
436
+ #
437
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
438
+ # @see #then
439
+ #
440
+ # @overload def returns(value)
441
+ # @param [Object] value value to return on invocation of expected method.
442
+ # @overload def returns(*values)
443
+ # @param [*Array] values values to return on consecutive invocations of expected method.
444
+ #
445
+ # @example Return the same value on every invocation.
229
446
  # object = mock()
230
447
  # object.stubs(:stubbed_method).returns('result')
231
448
  # object.stubbed_method # => 'result'
232
449
  # object.stubbed_method # => 'result'
233
- # If multiple +values+ are given, these are returned in turn on consecutive calls to the method.
450
+ #
451
+ # @example Return a different value on consecutive invocations.
234
452
  # object = mock()
235
453
  # object.stubs(:stubbed_method).returns(1, 2)
236
454
  # object.stubbed_method # => 1
237
455
  # object.stubbed_method # => 2
238
- # May be called multiple times on the same expectation. Also see Expectation#then.
456
+ #
457
+ # @example Alternative way to return a different value on consecutive invocations.
239
458
  # object = mock()
240
459
  # object.stubs(:expected_method).returns(1, 2).then.returns(3)
241
460
  # object.expected_method # => 1
242
461
  # object.expected_method # => 2
243
462
  # object.expected_method # => 3
244
- # May be called in conjunction with Expectation#raises on the same expectation.
463
+ #
464
+ # @example May be called in conjunction with {#raises} on the same expectation.
245
465
  # object = mock()
246
466
  # object.stubs(:expected_method).returns(1, 2).then.raises(Exception)
247
467
  # object.expected_method # => 1
248
468
  # object.expected_method # => 2
249
469
  # object.expected_method # => raises exception of class Exception1
250
- # If +value+ is a +Proc+, then the expected method will return the result of calling <tt>Proc#call</tt>.
251
470
  #
252
- # This usage is _deprecated_.
253
- # Use explicit multiple return values and/or multiple expectations instead.
254
- #
255
- # A +Proc+ instance will be treated the same as any other value in a future release.
471
+ # @example Note that in Ruby a method returning multiple values is exactly equivalent to a method returning an +Array+ of those values.
256
472
  # object = mock()
257
- # object.stubs(:stubbed_method).returns(lambda { rand(100) })
258
- # object.stubbed_method # => 41
259
- # object.stubbed_method # => 77
473
+ # object.stubs(:expected_method).returns([1, 2])
474
+ # x, y = object.expected_method
475
+ # x # => 1
476
+ # y # => 2
260
477
  def returns(*values)
261
478
  @return_values += ReturnValues.build(*values)
262
479
  self
263
480
  end
264
-
265
- # :call-seq: raises(exception = RuntimeError, message = nil) -> expectation
481
+
482
+ # Modifies expectation so that when the expected method is called, it raises the specified +exception+ with the specified +message+ i.e. calls +Kernel#raise(exception, message)+.
266
483
  #
267
- # Modifies expectation so that when the expected method is called, it raises the specified +exception+ with the specified +message+.
268
- # object = mock()
269
- # object.expects(:expected_method).raises(Exception, 'message')
484
+ # @param [Class,Exception,String,#exception] exception exception to be raised or message to be passed to RuntimeError.
485
+ # @param [String] message exception message.
486
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
487
+ #
488
+ # @see Kernel#raise
489
+ # @see #then
490
+ #
491
+ # @overload def raises
492
+ # @overload def raises(exception)
493
+ # @overload def raises(exception, message)
494
+ #
495
+ # @example Raise specified exception if expected method is invoked.
496
+ # object = stub()
497
+ # object.stubs(:expected_method).raises(Exception, 'message')
270
498
  # object.expected_method # => raises exception of class Exception and with message 'message'
271
- # May be called multiple times on the same expectation. Also see Expectation#then.
272
- # object = mock()
499
+ #
500
+ # @example Raise custom exception with extra constructor parameters by passing in an instance of the exception.
501
+ # object = stub()
502
+ # object.stubs(:expected_method).raises(MyException.new('message', 1, 2, 3))
503
+ # object.expected_method # => raises the specified instance of MyException
504
+ #
505
+ # @example Raise different exceptions on consecutive invocations of the expected method.
506
+ # object = stub()
273
507
  # object.stubs(:expected_method).raises(Exception1).then.raises(Exception2)
274
508
  # object.expected_method # => raises exception of class Exception1
275
509
  # object.expected_method # => raises exception of class Exception2
276
- # May be called in conjunction with Expectation#returns on the same expectation.
277
- # object = mock()
510
+ #
511
+ # @example Raise an exception on first invocation of expected method and then return values on subsequent invocations.
512
+ # object = stub()
278
513
  # object.stubs(:expected_method).raises(Exception).then.returns(2, 3)
279
514
  # object.expected_method # => raises exception of class Exception1
280
515
  # object.expected_method # => 2
@@ -284,101 +519,257 @@ module Mocha # :nodoc:
284
519
  self
285
520
  end
286
521
 
287
- # :call-seq: then() -> expectation
522
+ # Modifies expectation so that when the expected method is called, it throws the specified +tag+ with the specific return value +object+ i.e. calls +Kernel#throw(tag, object)+.
523
+ #
524
+ # @param [Symbol,String] tag tag to throw to transfer control to the active catch block.
525
+ # @param [Object] object return value for the catch block.
526
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
527
+ #
528
+ # @see Kernel#throw
529
+ # @see #then
530
+ #
531
+ # @overload def throw(tag)
532
+ # @overload def throw(tag, object)
533
+ #
534
+ # @example Throw tag when expected method is invoked.
535
+ # object = stub()
536
+ # object.stubs(:expected_method).throws(:done)
537
+ # object.expected_method # => throws tag :done
538
+ #
539
+ # @example Throw tag with return value +object+ c.f. +Kernel#throw+.
540
+ # object = stub()
541
+ # object.stubs(:expected_method).throws(:done, 'result')
542
+ # object.expected_method # => throws tag :done and causes catch block to return 'result'
543
+ #
544
+ # @example Throw different tags on consecutive invocations of the expected method.
545
+ # object = stub()
546
+ # object.stubs(:expected_method).throws(:done).then.throws(:continue)
547
+ # object.expected_method # => throws :done
548
+ # object.expected_method # => throws :continue
549
+ #
550
+ # @example Throw tag on first invocation of expected method and then return values for subsequent invocations.
551
+ # object = stub()
552
+ # object.stubs(:expected_method).throws(:done).then.returns(2, 3)
553
+ # object.expected_method # => throws :done
554
+ # object.expected_method # => 2
555
+ # object.expected_method # => 3
556
+ def throws(tag, object = nil)
557
+ @return_values += ReturnValues.new(Thrower.new(tag, object))
558
+ self
559
+ end
560
+
561
+ # @overload def then
562
+ # Used as syntactic sugar to improve readability. It has no effect on state of the expectation.
563
+ # @overload def then(state)
564
+ # Used to change the +state_machine+ to the specified state when the expected invocation occurs.
565
+ # @param [StateMachine::State] state state_machine.is(state_name) provides a mechanism to change the +state_machine+ into the state specified by +state_name+ when the expected method is invoked.
566
+ #
567
+ # @see API#states
568
+ # @see StateMachine
569
+ # @see #when
288
570
  #
289
- # Syntactic sugar to improve readability. Has no effect on state of the expectation.
571
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
572
+ #
573
+ # @example Using {#then} as syntactic sugar when specifying values to be returned and exceptions to be raised on consecutive invocations of the expected method.
290
574
  # object = mock()
291
575
  # object.stubs(:expected_method).returns(1, 2).then.raises(Exception).then.returns(4)
292
576
  # object.expected_method # => 1
293
577
  # object.expected_method # => 2
294
578
  # object.expected_method # => raises exception of class Exception
295
579
  # object.expected_method # => 4
296
- def then
580
+ #
581
+ # @example Using {#then} to change the +state+ of a +state_machine+ on the invocation of an expected method.
582
+ # power = states('power').starts_as('off')
583
+ #
584
+ # radio = mock('radio')
585
+ # radio.expects(:switch_on).then(power.is('on'))
586
+ # radio.expects(:select_channel).with('BBC Radio 4').when(power.is('on'))
587
+ # radio.expects(:adjust_volume).with(+5).when(power.is('on'))
588
+ # radio.expects(:select_channel).with('BBC World Service').when(power.is('on'))
589
+ # radio.expects(:adjust_volume).with(-5).when(power.is('on'))
590
+ # radio.expects(:switch_off).then(power.is('off'))
591
+ def then(state = nil)
592
+ add_side_effect(ChangeStateSideEffect.new(state)) if state
297
593
  self
298
594
  end
299
-
300
- # :stopdoc:
301
-
302
- def in_sequence(*sequences)
303
- sequences.each { |sequence| sequence.constrain_as_next_in_sequence(self) }
595
+
596
+ # Constrains the expectation to occur only when the +state_machine+ is in the state specified by +state_predicate+.
597
+ #
598
+ # @param [StateMachine::StatePredicate] state_predicate +state_machine.is(state_name)+ provides a mechanism to determine whether the +state_machine+ is in the state specified by +state_predicate+ when the expected method is invoked.
599
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
600
+ #
601
+ # @see API#states
602
+ # @see StateMachine
603
+ # @see #then
604
+ #
605
+ # @example Using {#when} to only allow invocation of methods when "power" state machine is in the "on" state.
606
+ # power = states('power').starts_as('off')
607
+ #
608
+ # radio = mock('radio')
609
+ # radio.expects(:switch_on).then(power.is('on'))
610
+ # radio.expects(:select_channel).with('BBC Radio 4').when(power.is('on'))
611
+ # radio.expects(:adjust_volume).with(+5).when(power.is('on'))
612
+ # radio.expects(:select_channel).with('BBC World Service').when(power.is('on'))
613
+ # radio.expects(:adjust_volume).with(-5).when(power.is('on'))
614
+ # radio.expects(:switch_off).then(power.is('off'))
615
+ def when(state_predicate)
616
+ add_ordering_constraint(InStateOrderingConstraint.new(state_predicate))
617
+ self
618
+ end
619
+
620
+ # Constrains the expectation so that it must be invoked at the current point in the +sequence+.
621
+ #
622
+ # To expect a sequence of invocations, write the expectations in order and add the +in_sequence(sequence)+ clause to each one.
623
+ #
624
+ # Expectations in a +sequence+ can have any invocation count.
625
+ #
626
+ # If an expectation in a sequence is stubbed, rather than expected, it can be skipped in the +sequence+.
627
+ #
628
+ # An expected method can appear in multiple sequences.
629
+ #
630
+ # @param [Sequence] sequence sequence in which expected method should appear.
631
+ # @param [*Array<Sequence>] sequences more sequences in which expected method should appear.
632
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
633
+ #
634
+ # @see API#sequence
635
+ #
636
+ # @example Ensure methods are invoked in a specified order.
637
+ # breakfast = sequence('breakfast')
638
+ #
639
+ # egg = mock('egg')
640
+ # egg.expects(:crack).in_sequence(breakfast)
641
+ # egg.expects(:fry).in_sequence(breakfast)
642
+ # egg.expects(:eat).in_sequence(breakfast)
643
+ def in_sequence(sequence, *sequences)
644
+ sequences.unshift(sequence).each { |seq| add_in_sequence_ordering_constraint(seq) }
304
645
  self
305
646
  end
306
-
307
- attr_reader :backtrace
308
647
 
309
- def initialize(mock, expected_method_name, backtrace = nil)
648
+ # @private
649
+ attr_reader :backtrace_locations
650
+
651
+ # @private
652
+ def initialize(mock, expected_method_name, backtrace_locations = nil)
310
653
  @mock = mock
311
- @method_matcher = MethodMatcher.new(expected_method_name)
654
+ @method_matcher = MethodMatcher.new(expected_method_name.to_sym)
312
655
  @parameters_matcher = ParametersMatcher.new
656
+ @block_matcher = BlockMatchers::OptionalBlock.new
313
657
  @ordering_constraints = []
314
- @expected_count, @invoked_count = 1, 0
658
+ @side_effects = []
659
+ @cardinality = Cardinality.new.exactly(1)
315
660
  @return_values = ReturnValues.new
316
661
  @yield_parameters = YieldParameters.new
317
- @backtrace = backtrace || caller
662
+ @backtrace_locations = backtrace_locations || caller_locations
318
663
  end
319
-
664
+
665
+ # @private
320
666
  def add_ordering_constraint(ordering_constraint)
321
667
  @ordering_constraints << ordering_constraint
322
668
  end
323
-
669
+
670
+ # @private
671
+ def add_in_sequence_ordering_constraint(sequence)
672
+ sequence.constrain_as_next_in_sequence(self)
673
+ end
674
+
675
+ # @private
676
+ def add_side_effect(side_effect)
677
+ @side_effects << side_effect
678
+ end
679
+
680
+ # @private
681
+ def perform_side_effects
682
+ @side_effects.each(&:perform)
683
+ end
684
+
685
+ # @private
324
686
  def in_correct_order?
325
- @ordering_constraints.all? { |ordering_constraint| ordering_constraint.allows_invocation_now? }
687
+ @ordering_constraints.all?(&:allows_invocation_now?)
326
688
  end
327
-
689
+
690
+ # @private
691
+ def ordering_constraints_not_allowing_invocation_now
692
+ @ordering_constraints.reject(&:allows_invocation_now?)
693
+ end
694
+
695
+ # @private
328
696
  def matches_method?(method_name)
329
697
  @method_matcher.match?(method_name)
330
698
  end
331
-
332
- def match?(actual_method_name, *actual_parameters)
333
- @method_matcher.match?(actual_method_name) && @parameters_matcher.match?(actual_parameters) && in_correct_order?
699
+
700
+ # @private
701
+ def match?(invocation, ignoring_order: false)
702
+ order_independent_match = @method_matcher.match?(invocation.method_name) && @parameters_matcher.match?(invocation.arguments) && @block_matcher.match?(invocation.block)
703
+ ignoring_order ? order_independent_match : order_independent_match && in_correct_order?
334
704
  end
335
-
705
+
706
+ # @private
336
707
  def invocations_allowed?
337
- if @expected_count.is_a?(Range) then
338
- @invoked_count < @expected_count.last
339
- else
340
- @invoked_count < @expected_count
341
- end
708
+ @cardinality.invocations_allowed?
342
709
  end
343
710
 
711
+ # @private
712
+ def invocations_never_allowed?
713
+ @cardinality.invocations_never_allowed?
714
+ end
715
+
716
+ # @private
344
717
  def satisfied?
345
- if @expected_count.is_a?(Range) then
346
- @invoked_count >= @expected_count.first
347
- else
348
- @invoked_count >= @expected_count
349
- end
718
+ @cardinality.satisfied?
350
719
  end
351
-
352
- def invoke
353
- @invoked_count += 1
354
- if block_given? then
355
- @yield_parameters.next_invocation.each do |yield_parameters|
356
- yield(*yield_parameters)
357
- end
358
- end
359
- @return_values.next
720
+
721
+ # @private
722
+ def invoke(invocation)
723
+ perform_side_effects
724
+ @cardinality << invocation
725
+ invocation.call(@yield_parameters, @return_values)
726
+ end
727
+
728
+ # @private
729
+ def verified?(assertion_counter = nil)
730
+ assertion_counter.increment if assertion_counter && @cardinality.needs_verifying?
731
+ @cardinality.verified?
360
732
  end
361
733
 
362
- def verify
363
- yield(self) if block_given?
364
- unless (@expected_count === @invoked_count) then
365
- error = ExpectationError.new(error_message(@expected_count, @invoked_count), backtrace)
366
- raise error
734
+ # @private
735
+ def used?
736
+ @cardinality.used?
737
+ end
738
+
739
+ # @private
740
+ def inspect
741
+ address = __id__ * 2
742
+ address += 0x100000000 if address < 0
743
+ "#<Expectation:0x#{format('%<address>x', address: address)} #{mocha_inspect} >"
744
+ end
745
+
746
+ # @private
747
+ def mocha_inspect
748
+ strings = ["#{@cardinality.anticipated_times}, #{@cardinality.invoked_times}: #{method_signature}"]
749
+ strings << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty?
750
+ if Mocha.configuration.display_matching_invocations_on_failure?
751
+ strings << @cardinality.actual_invocations
367
752
  end
753
+ strings.join
368
754
  end
369
-
755
+
756
+ # @private
370
757
  def method_signature
371
- signature = "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}#{@parameters_matcher.mocha_inspect}"
372
- signature << "; #{@ordering_constraints.map { |oc| oc.mocha_inspect }.join("; ")}" unless @ordering_constraints.empty?
373
- signature
758
+ strings = ["#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}#{@parameters_matcher.mocha_inspect}"]
759
+ strings << " #{@block_matcher.mocha_inspect}" if @block_matcher.mocha_inspect
760
+ strings.join
761
+ end
762
+
763
+ # @private
764
+ def backtrace
765
+ backtrace_locations.map(&:to_s)
374
766
  end
375
-
376
- def error_message(expected_count, actual_count)
377
- "#{method_signature} - expected calls: #{expected_count.mocha_inspect}, actual calls: #{actual_count}"
767
+
768
+ # @private
769
+ def definition_location
770
+ filter = BacktraceFilter.new
771
+ location = filter.filtered_locations(backtrace_locations)[0]
772
+ "#{location.path}:#{location.lineno}"
378
773
  end
379
-
380
- # :startdoc:
381
-
382
774
  end
383
-
384
- end
775
+ end