mocha 2.1.0 → 2.7.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -1
- data/.yardopts +1 -1
- data/Gemfile +6 -1
- data/README.md +12 -18
- data/RELEASE.md +191 -0
- data/Rakefile +9 -9
- data/lib/mocha/api.rb +25 -6
- data/lib/mocha/cardinality.rb +4 -0
- data/lib/mocha/detection/{mini_test.rb → minitest.rb} +5 -5
- data/lib/mocha/detection/test_unit.rb +2 -2
- data/lib/mocha/expectation.rb +13 -2
- data/lib/mocha/expectation_error_factory.rb +2 -2
- data/lib/mocha/expectation_list.rb +8 -6
- data/lib/mocha/hooks.rb +10 -4
- data/lib/mocha/inspect.rb +13 -2
- data/lib/mocha/integration/{mini_test → minitest}/adapter.rb +20 -5
- data/lib/mocha/integration/{mini_test → minitest}/exception_translation.rb +2 -2
- data/lib/mocha/integration/minitest.rb +28 -0
- data/lib/mocha/integration/test_unit/adapter.rb +5 -0
- data/lib/mocha/minitest.rb +3 -3
- data/lib/mocha/mock.rb +37 -14
- data/lib/mocha/mockery.rb +13 -9
- data/lib/mocha/object_methods.rb +2 -2
- data/lib/mocha/parameter_matchers/base.rb +4 -9
- data/lib/mocha/parameter_matchers/has_entries.rb +7 -2
- data/lib/mocha/parameter_matchers/includes.rb +3 -3
- data/lib/mocha/parameter_matchers/instance_methods.rb +9 -10
- data/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb +3 -1
- data/lib/mocha/parameter_matchers/responds_with.rb +32 -5
- data/lib/mocha/parameters_matcher.rb +1 -1
- data/lib/mocha/ruby_version.rb +1 -0
- data/lib/mocha/version.rb +1 -1
- data/mocha.gemspec +9 -1
- metadata +15 -9
- data/lib/mocha/integration/mini_test.rb +0 -28
|
@@ -17,14 +17,18 @@ module Mocha
|
|
|
17
17
|
@expectations.any? { |expectation| expectation.matches_method?(method_name) }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def match(invocation)
|
|
21
|
-
matching_expectations(invocation).first
|
|
20
|
+
def match(invocation, ignoring_order: false)
|
|
21
|
+
matching_expectations(invocation, ignoring_order: ignoring_order).first
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def match_allowing_invocation(invocation)
|
|
25
25
|
matching_expectations(invocation).detect(&:invocations_allowed?)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def match_never_allowing_invocation(invocation)
|
|
29
|
+
matching_expectations(invocation).detect(&:invocations_never_allowed?)
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
def verified?(assertion_counter = nil)
|
|
29
33
|
@expectations.all? { |expectation| expectation.verified?(assertion_counter) }
|
|
30
34
|
end
|
|
@@ -49,10 +53,8 @@ module Mocha
|
|
|
49
53
|
self.class.new(to_a + other.to_a)
|
|
50
54
|
end
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def matching_expectations(invocation)
|
|
55
|
-
@expectations.select { |e| e.match?(invocation) }
|
|
56
|
+
def matching_expectations(invocation, ignoring_order: false)
|
|
57
|
+
@expectations.select { |e| e.match?(invocation, ignoring_order: ignoring_order) }
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
60
|
end
|
data/lib/mocha/hooks.rb
CHANGED
|
@@ -7,12 +7,12 @@ module Mocha
|
|
|
7
7
|
#
|
|
8
8
|
# This module is provided as part of the +Mocha::API+ module and is therefore part of the public API, but should only be used by authors of test libraries and not by typical "users" of Mocha.
|
|
9
9
|
#
|
|
10
|
-
# Integration with Test::Unit and
|
|
10
|
+
# Integration with Test::Unit and Minitest are provided as part of Mocha, because they are (or were once) part of the Ruby standard library. Integration with other test libraries is not provided as *part* of Mocha, but is supported by means of the methods in this module.
|
|
11
11
|
#
|
|
12
12
|
# See the code in the +Adapter+ modules for examples of how to use the methods in this module. +Mocha::ExpectationErrorFactory+ may be used if you want +Mocha+ to raise a different type of exception.
|
|
13
13
|
#
|
|
14
14
|
# @see Mocha::Integration::TestUnit::Adapter
|
|
15
|
-
# @see Mocha::Integration::
|
|
15
|
+
# @see Mocha::Integration::Minitest::Adapter
|
|
16
16
|
# @see Mocha::ExpectationErrorFactory
|
|
17
17
|
# @see Mocha::API
|
|
18
18
|
module Hooks
|
|
@@ -35,8 +35,14 @@ module Mocha
|
|
|
35
35
|
# Resets Mocha after a test (only for use by authors of test libraries).
|
|
36
36
|
#
|
|
37
37
|
# This method should be called after each individual test has finished (including after any "teardown" code).
|
|
38
|
-
def mocha_teardown
|
|
39
|
-
Mockery.teardown
|
|
38
|
+
def mocha_teardown(origin = mocha_test_name)
|
|
39
|
+
Mockery.teardown(origin)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns a string representing the unit test name, to be included in some Mocha
|
|
43
|
+
# to help track down potential bugs.
|
|
44
|
+
def mocha_test_name
|
|
45
|
+
nil
|
|
40
46
|
end
|
|
41
47
|
end
|
|
42
48
|
end
|
data/lib/mocha/inspect.rb
CHANGED
|
@@ -19,8 +19,19 @@ module Mocha
|
|
|
19
19
|
|
|
20
20
|
module HashMethods
|
|
21
21
|
def mocha_inspect
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if Hash.ruby2_keywords_hash?(self)
|
|
23
|
+
collect do |key, value|
|
|
24
|
+
case key
|
|
25
|
+
when Symbol
|
|
26
|
+
"#{key}: #{value.mocha_inspect}"
|
|
27
|
+
else
|
|
28
|
+
"#{key.mocha_inspect} => #{value.mocha_inspect}"
|
|
29
|
+
end
|
|
30
|
+
end.join(', ')
|
|
31
|
+
else
|
|
32
|
+
unwrapped = collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ')
|
|
33
|
+
"{#{unwrapped}}"
|
|
34
|
+
end
|
|
24
35
|
end
|
|
25
36
|
end
|
|
26
37
|
|
|
@@ -4,21 +4,21 @@ require 'mocha/expectation_error_factory'
|
|
|
4
4
|
|
|
5
5
|
module Mocha
|
|
6
6
|
module Integration
|
|
7
|
-
module
|
|
8
|
-
# Integrates Mocha into recent versions of
|
|
7
|
+
module Minitest
|
|
8
|
+
# Integrates Mocha into recent versions of Minitest.
|
|
9
9
|
#
|
|
10
10
|
# See the source code for an example of how to integrate Mocha into a test library.
|
|
11
11
|
module Adapter
|
|
12
12
|
include Mocha::API
|
|
13
13
|
|
|
14
14
|
# @private
|
|
15
|
-
def self.applicable_to?(
|
|
16
|
-
Gem::Requirement.new('>= 3.3.0').satisfied_by?(
|
|
15
|
+
def self.applicable_to?(minitest_version)
|
|
16
|
+
Gem::Requirement.new('>= 3.3.0').satisfied_by?(minitest_version)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# @private
|
|
20
20
|
def self.description
|
|
21
|
-
'adapter for
|
|
21
|
+
'adapter for Minitest gem >= v3.3.0'
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# @private
|
|
@@ -46,6 +46,21 @@ module Mocha
|
|
|
46
46
|
super
|
|
47
47
|
mocha_teardown
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
# @private
|
|
51
|
+
def mocha_test_name
|
|
52
|
+
if respond_to?(:name)
|
|
53
|
+
test_name = name
|
|
54
|
+
elsif respond_to?(:__name__) # Older minitest
|
|
55
|
+
test_name = __name__
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if test_name
|
|
59
|
+
"#{self.class.name}##{test_name}"
|
|
60
|
+
else
|
|
61
|
+
self.class.name
|
|
62
|
+
end
|
|
63
|
+
end
|
|
49
64
|
end
|
|
50
65
|
end
|
|
51
66
|
end
|
|
@@ -2,10 +2,10 @@ require 'mocha/expectation_error'
|
|
|
2
2
|
|
|
3
3
|
module Mocha
|
|
4
4
|
module Integration
|
|
5
|
-
module
|
|
5
|
+
module Minitest
|
|
6
6
|
def self.translate(exception)
|
|
7
7
|
return exception unless exception.is_a?(::Mocha::ExpectationError)
|
|
8
|
-
translated_exception = ::
|
|
8
|
+
translated_exception = ::Minitest::Assertion.new(exception.message)
|
|
9
9
|
translated_exception.set_backtrace(exception.backtrace)
|
|
10
10
|
translated_exception
|
|
11
11
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'mocha/debug'
|
|
2
|
+
require 'mocha/detection/minitest'
|
|
3
|
+
require 'mocha/integration/minitest/adapter'
|
|
4
|
+
|
|
5
|
+
module Mocha
|
|
6
|
+
module Integration
|
|
7
|
+
module Minitest
|
|
8
|
+
def self.activate
|
|
9
|
+
target = Detection::Minitest.testcase
|
|
10
|
+
return false unless target
|
|
11
|
+
|
|
12
|
+
minitest_version = Gem::Version.new(Detection::Minitest.version)
|
|
13
|
+
Debug.puts "Detected Minitest version: #{minitest_version}"
|
|
14
|
+
|
|
15
|
+
unless Minitest::Adapter.applicable_to?(minitest_version)
|
|
16
|
+
raise 'Versions of minitest earlier than v3.3.0 are not supported.'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
unless target < Minitest::Adapter
|
|
20
|
+
Debug.puts "Applying #{Minitest::Adapter.description}"
|
|
21
|
+
target.send(:include, Minitest::Adapter)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/mocha/minitest.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'mocha/ruby_version'
|
|
2
|
-
require 'mocha/integration/
|
|
2
|
+
require 'mocha/integration/minitest'
|
|
3
3
|
|
|
4
|
-
unless Mocha::Integration::
|
|
5
|
-
raise "
|
|
4
|
+
unless Mocha::Integration::Minitest.activate
|
|
5
|
+
raise "Minitest must be loaded *before* `require 'mocha/minitest'`."
|
|
6
6
|
end
|
data/lib/mocha/mock.rb
CHANGED
|
@@ -8,6 +8,7 @@ require 'mocha/method_matcher'
|
|
|
8
8
|
require 'mocha/parameters_matcher'
|
|
9
9
|
require 'mocha/argument_iterator'
|
|
10
10
|
require 'mocha/expectation_error_factory'
|
|
11
|
+
require 'mocha/deprecation'
|
|
11
12
|
|
|
12
13
|
module Mocha
|
|
13
14
|
# Traditional mock object.
|
|
@@ -34,6 +35,9 @@ module Mocha
|
|
|
34
35
|
# while an +expects(:foo).at_least_once+ expectation will always be matched
|
|
35
36
|
# against invocations.
|
|
36
37
|
#
|
|
38
|
+
# However, note that if the expectation that matches the invocation has a
|
|
39
|
+
# cardinality of "never", then an unexpected invocation error is reported.
|
|
40
|
+
#
|
|
37
41
|
# This scheme allows you to:
|
|
38
42
|
#
|
|
39
43
|
# - Set up default stubs in your the +setup+ method of your test class and
|
|
@@ -100,7 +104,7 @@ module Mocha
|
|
|
100
104
|
#
|
|
101
105
|
# @example Setup multiple expectations using +expected_methods_vs_return_values+.
|
|
102
106
|
# object = mock()
|
|
103
|
-
# object.expects(:
|
|
107
|
+
# object.expects(expected_method_one: :result_one, expected_method_two: :result_two)
|
|
104
108
|
#
|
|
105
109
|
# # is exactly equivalent to
|
|
106
110
|
#
|
|
@@ -114,6 +118,7 @@ module Mocha
|
|
|
114
118
|
method_name = args.shift
|
|
115
119
|
ensure_method_not_already_defined(method_name)
|
|
116
120
|
expectation = Expectation.new(self, method_name, backtrace)
|
|
121
|
+
expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
|
|
117
122
|
expectation.returns(args.shift) unless args.empty?
|
|
118
123
|
@expectations.add(expectation)
|
|
119
124
|
end
|
|
@@ -138,7 +143,7 @@ module Mocha
|
|
|
138
143
|
#
|
|
139
144
|
# @example Setup multiple expectations using +stubbed_methods_vs_return_values+.
|
|
140
145
|
# object = mock()
|
|
141
|
-
# object.stubs(:
|
|
146
|
+
# object.stubs(stubbed_method_one: :result_one, stubbed_method_two: :result_two)
|
|
142
147
|
#
|
|
143
148
|
# # is exactly equivalent to
|
|
144
149
|
#
|
|
@@ -153,6 +158,7 @@ module Mocha
|
|
|
153
158
|
ensure_method_not_already_defined(method_name)
|
|
154
159
|
expectation = Expectation.new(self, method_name, backtrace)
|
|
155
160
|
expectation.at_least(0)
|
|
161
|
+
expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
|
|
156
162
|
expectation.returns(args.shift) unless args.empty?
|
|
157
163
|
@expectations.add(expectation)
|
|
158
164
|
end
|
|
@@ -309,22 +315,33 @@ module Mocha
|
|
|
309
315
|
end
|
|
310
316
|
|
|
311
317
|
# @private
|
|
312
|
-
# rubocop:disable Style/MethodMissingSuper
|
|
313
|
-
def method_missing(symbol, *arguments, &block)
|
|
318
|
+
def method_missing(symbol, *arguments, &block) # rubocop:disable Style/MethodMissingSuper
|
|
314
319
|
handle_method_call(symbol, arguments, block)
|
|
315
320
|
end
|
|
316
321
|
ruby2_keywords(:method_missing)
|
|
317
|
-
# rubocop:enable Style/MethodMissingSuper
|
|
318
322
|
|
|
319
323
|
# @private
|
|
320
|
-
def handle_method_call(symbol, arguments, block)
|
|
324
|
+
def handle_method_call(symbol, arguments, block) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
321
325
|
check_expiry
|
|
322
326
|
check_responder_responds_to(symbol)
|
|
323
327
|
invocation = Invocation.new(self, symbol, arguments, block)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
+
|
|
329
|
+
matching_expectations = all_expectations.matching_expectations(invocation)
|
|
330
|
+
|
|
331
|
+
index = 0
|
|
332
|
+
while index < matching_expectations.length
|
|
333
|
+
matching_expectation = matching_expectations[index]
|
|
334
|
+
if matching_expectation.invocations_never_allowed?
|
|
335
|
+
raise_unexpected_invocation_error(invocation, matching_expectation)
|
|
336
|
+
elsif matching_expectation.invocations_allowed?
|
|
337
|
+
return matching_expectation.invoke(invocation)
|
|
338
|
+
end
|
|
339
|
+
index += 1
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
matching_expectation_ignoring_order = all_expectations.match(invocation, ignoring_order: true)
|
|
343
|
+
if matching_expectation_ignoring_order || (!matching_expectation_ignoring_order && !@everything_stubbed) # rubocop:disable Style/GuardClause
|
|
344
|
+
raise_unexpected_invocation_error(invocation, matching_expectation_ignoring_order)
|
|
328
345
|
end
|
|
329
346
|
end
|
|
330
347
|
|
|
@@ -343,8 +360,8 @@ module Mocha
|
|
|
343
360
|
end
|
|
344
361
|
|
|
345
362
|
# @private
|
|
346
|
-
def __expire__
|
|
347
|
-
@expired = true
|
|
363
|
+
def __expire__(origin)
|
|
364
|
+
@expired = origin || true
|
|
348
365
|
end
|
|
349
366
|
|
|
350
367
|
# @private
|
|
@@ -373,7 +390,11 @@ module Mocha
|
|
|
373
390
|
if @unexpected_invocation.nil?
|
|
374
391
|
@unexpected_invocation = invocation
|
|
375
392
|
matching_expectation.invoke(invocation) if matching_expectation
|
|
376
|
-
|
|
393
|
+
call_description = @unexpected_invocation.call_description
|
|
394
|
+
if matching_expectation && !matching_expectation.in_correct_order?
|
|
395
|
+
call_description += ' invoked out of order'
|
|
396
|
+
end
|
|
397
|
+
message = "#{call_description}\n#{@mockery.mocha_inspect}"
|
|
377
398
|
else
|
|
378
399
|
message = @unexpected_invocation.short_call_description
|
|
379
400
|
end
|
|
@@ -389,8 +410,10 @@ module Mocha
|
|
|
389
410
|
def check_expiry
|
|
390
411
|
return unless @expired
|
|
391
412
|
|
|
413
|
+
origin = @expired == true ? 'one test' : @expired
|
|
414
|
+
|
|
392
415
|
sentences = [
|
|
393
|
-
"#{mocha_inspect} was instantiated in
|
|
416
|
+
"#{mocha_inspect} was instantiated in #{origin} but it is receiving invocations within another test.",
|
|
394
417
|
'This can lead to unintended interactions between tests and hence unexpected test failures.',
|
|
395
418
|
'Ensure that every test correctly cleans up any state that it introduces.'
|
|
396
419
|
]
|
data/lib/mocha/mockery.rb
CHANGED
|
@@ -48,8 +48,8 @@ module Mocha
|
|
|
48
48
|
instance.verify(*args)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def teardown
|
|
52
|
-
instance.teardown
|
|
51
|
+
def teardown(origin = nil)
|
|
52
|
+
instance.teardown(origin)
|
|
53
53
|
ensure
|
|
54
54
|
@instances.pop
|
|
55
55
|
end
|
|
@@ -91,9 +91,9 @@ module Mocha
|
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
def teardown
|
|
94
|
+
def teardown(origin = nil)
|
|
95
95
|
stubba.unstub_all
|
|
96
|
-
mocks.each(
|
|
96
|
+
mocks.each { |m| m.__expire__(origin) }
|
|
97
97
|
reset
|
|
98
98
|
end
|
|
99
99
|
|
|
@@ -109,12 +109,16 @@ module Mocha
|
|
|
109
109
|
@state_machines ||= []
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
def sequences
|
|
113
|
+
@sequences ||= []
|
|
114
|
+
end
|
|
115
|
+
|
|
112
116
|
def mocha_inspect
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
lines = []
|
|
118
|
+
lines << "unsatisfied expectations:\n- #{unsatisfied_expectations.map(&:mocha_inspect).join("\n- ")}\n" if unsatisfied_expectations.any?
|
|
119
|
+
lines << "satisfied expectations:\n- #{satisfied_expectations.map(&:mocha_inspect).join("\n- ")}\n" if satisfied_expectations.any?
|
|
120
|
+
lines << "states:\n- #{state_machines.map(&:mocha_inspect).join("\n- ")}\n" if state_machines.any?
|
|
121
|
+
lines.join
|
|
118
122
|
end
|
|
119
123
|
|
|
120
124
|
def on_stubbing(object, method)
|
data/lib/mocha/object_methods.rb
CHANGED
|
@@ -59,7 +59,7 @@ module Mocha
|
|
|
59
59
|
#
|
|
60
60
|
# @example Setting up multiple expectations on a non-mock object.
|
|
61
61
|
# product = Product.new
|
|
62
|
-
# product.expects(
|
|
62
|
+
# product.expects(valid?: true, save: true)
|
|
63
63
|
#
|
|
64
64
|
# # exactly equivalent to
|
|
65
65
|
#
|
|
@@ -108,7 +108,7 @@ module Mocha
|
|
|
108
108
|
#
|
|
109
109
|
# @example Setting up multiple stubbed methods on a non-mock object.
|
|
110
110
|
# product = Product.new
|
|
111
|
-
# product.stubs(
|
|
111
|
+
# product.stubs(valid?: true, save: true)
|
|
112
112
|
#
|
|
113
113
|
# # exactly equivalent to
|
|
114
114
|
#
|
|
@@ -2,11 +2,6 @@ module Mocha
|
|
|
2
2
|
module ParameterMatchers
|
|
3
3
|
# @abstract Subclass and implement +#matches?+ and +#mocha_inspect+ to define a custom matcher. Also add a suitably named instance method to {ParameterMatchers} to build an instance of the new matcher c.f. {#equals}.
|
|
4
4
|
class Base
|
|
5
|
-
# @private
|
|
6
|
-
def to_matcher(_expectation = nil)
|
|
7
|
-
self
|
|
8
|
-
end
|
|
9
|
-
|
|
10
5
|
# A shorthand way of combining two matchers when both must match.
|
|
11
6
|
#
|
|
12
7
|
# Returns a new {AllOf} parameter matcher combining two matchers using a logical AND.
|
|
@@ -21,12 +16,12 @@ module Mocha
|
|
|
21
16
|
# @example Alternative ways to combine matchers with a logical AND.
|
|
22
17
|
# object = mock()
|
|
23
18
|
# object.expects(:run).with(all_of(has_key(:foo), has_key(:bar)))
|
|
24
|
-
# object.run(:
|
|
19
|
+
# object.run(foo: 'foovalue', bar: 'barvalue')
|
|
25
20
|
#
|
|
26
21
|
# # is exactly equivalent to
|
|
27
22
|
#
|
|
28
23
|
# object.expects(:run).with(has_key(:foo) & has_key(:bar))
|
|
29
|
-
# object.run(:
|
|
24
|
+
# object.run(foo: 'foovalue', bar: 'barvalue)
|
|
30
25
|
def &(other)
|
|
31
26
|
AllOf.new(self, other)
|
|
32
27
|
end
|
|
@@ -45,12 +40,12 @@ module Mocha
|
|
|
45
40
|
# @example Alternative ways to combine matchers with a logical OR.
|
|
46
41
|
# object = mock()
|
|
47
42
|
# object.expects(:run).with(any_of(has_key(:foo), has_key(:bar)))
|
|
48
|
-
# object.run(:
|
|
43
|
+
# object.run(foo: 'foovalue')
|
|
49
44
|
#
|
|
50
45
|
# # is exactly equivalent to
|
|
51
46
|
#
|
|
52
47
|
# object.expects(:run).with(has_key(:foo) | has_key(:bar))
|
|
53
|
-
# object.run(:
|
|
48
|
+
# object.run(foo: 'foovalue')
|
|
54
49
|
#
|
|
55
50
|
# @example Using an explicit {Equals} matcher in combination with {#|}.
|
|
56
51
|
# object.expects(:run).with(equals(1) | equals(2))
|
|
@@ -30,20 +30,25 @@ module Mocha
|
|
|
30
30
|
# Parameter matcher which matches when actual parameter contains all expected +Hash+ entries.
|
|
31
31
|
class HasEntries < Base
|
|
32
32
|
# @private
|
|
33
|
-
def initialize(entries)
|
|
33
|
+
def initialize(entries, exact: false)
|
|
34
34
|
@entries = entries
|
|
35
|
+
@exact = exact
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
# @private
|
|
38
39
|
def matches?(available_parameters)
|
|
39
40
|
parameter = available_parameters.shift
|
|
41
|
+
return false unless parameter
|
|
42
|
+
return false unless parameter.respond_to?(:keys)
|
|
43
|
+
return false if @exact && @entries.length != parameter.keys.length
|
|
44
|
+
|
|
40
45
|
has_entry_matchers = @entries.map { |key, value| HasEntry.new(key, value) }
|
|
41
46
|
AllOf.new(*has_entry_matchers).matches?([parameter])
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
# @private
|
|
45
50
|
def mocha_inspect
|
|
46
|
-
"has_entries(#{@entries.mocha_inspect})"
|
|
51
|
+
@exact ? @entries.mocha_inspect : "has_entries(#{@entries.mocha_inspect})"
|
|
47
52
|
end
|
|
48
53
|
end
|
|
49
54
|
end
|
|
@@ -24,7 +24,7 @@ module Mocha
|
|
|
24
24
|
# @example Actual parameter includes item which matches nested matcher.
|
|
25
25
|
# object = mock()
|
|
26
26
|
# object.expects(:method_1).with(includes(has_key(:key)))
|
|
27
|
-
# object.method_1(['foo', 'bar', {:
|
|
27
|
+
# object.method_1(['foo', 'bar', {key: 'baz'}])
|
|
28
28
|
# # no error raised
|
|
29
29
|
#
|
|
30
30
|
# @example Actual parameter does not include item matching nested matcher.
|
|
@@ -44,11 +44,11 @@ module Mocha
|
|
|
44
44
|
# @example Actual parameter is a Hash including the given key.
|
|
45
45
|
# object = mock()
|
|
46
46
|
# object.expects(:method_1).with(includes(:bar))
|
|
47
|
-
# object.method_1({:
|
|
47
|
+
# object.method_1({foo: 1, bar: 2})
|
|
48
48
|
# # no error raised
|
|
49
49
|
#
|
|
50
50
|
# @example Actual parameter is a Hash without the given key.
|
|
51
|
-
# object.method_1({:
|
|
51
|
+
# object.method_1({foo: 1, baz: 2})
|
|
52
52
|
# # error raised, because hash does not include key 'bar'
|
|
53
53
|
#
|
|
54
54
|
# @example Actual parameter is a Hash with a key matching the given matcher.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'mocha/parameter_matchers/base'
|
|
1
2
|
require 'mocha/parameter_matchers/equals'
|
|
2
3
|
require 'mocha/parameter_matchers/positional_or_keyword_hash'
|
|
3
4
|
|
|
@@ -6,8 +7,14 @@ module Mocha
|
|
|
6
7
|
# @private
|
|
7
8
|
module InstanceMethods
|
|
8
9
|
# @private
|
|
9
|
-
def to_matcher(
|
|
10
|
-
|
|
10
|
+
def to_matcher(expectation: nil, top_level: false)
|
|
11
|
+
if Base === self
|
|
12
|
+
self
|
|
13
|
+
elsif Hash === self && top_level
|
|
14
|
+
Mocha::ParameterMatchers::PositionalOrKeywordHash.new(self, expectation)
|
|
15
|
+
else
|
|
16
|
+
Mocha::ParameterMatchers::Equals.new(self)
|
|
17
|
+
end
|
|
11
18
|
end
|
|
12
19
|
end
|
|
13
20
|
end
|
|
@@ -17,11 +24,3 @@ end
|
|
|
17
24
|
class Object
|
|
18
25
|
include Mocha::ParameterMatchers::InstanceMethods
|
|
19
26
|
end
|
|
20
|
-
|
|
21
|
-
# @private
|
|
22
|
-
class Hash
|
|
23
|
-
# @private
|
|
24
|
-
def to_matcher(expectation = nil)
|
|
25
|
-
Mocha::ParameterMatchers::PositionalOrKeywordHash.new(self, expectation)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'mocha/configuration'
|
|
2
2
|
require 'mocha/deprecation'
|
|
3
3
|
require 'mocha/parameter_matchers/base'
|
|
4
|
+
require 'mocha/parameter_matchers/has_entries'
|
|
4
5
|
|
|
5
6
|
module Mocha
|
|
6
7
|
module ParameterMatchers
|
|
@@ -13,7 +14,8 @@ module Mocha
|
|
|
13
14
|
|
|
14
15
|
def matches?(available_parameters)
|
|
15
16
|
parameter, is_last_parameter = extract_parameter(available_parameters)
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
return false unless HasEntries.new(@value, exact: true).matches?([parameter])
|
|
17
19
|
|
|
18
20
|
if is_last_parameter && !same_type_of_hash?(parameter, @value)
|
|
19
21
|
return false if Mocha.configuration.strict_keyword_argument_matching?
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
require 'mocha/parameter_matchers/base'
|
|
2
|
+
require 'mocha/parameter_matchers/all_of'
|
|
2
3
|
require 'yaml'
|
|
3
4
|
|
|
4
5
|
module Mocha
|
|
5
6
|
module ParameterMatchers
|
|
6
|
-
#
|
|
7
|
+
# @overload def responds_with(message, result)
|
|
8
|
+
# Matches any object that responds to +message+ with +result+. To put it another way, it tests the quack, not the duck.
|
|
9
|
+
# @param [Symbol] message method to invoke.
|
|
10
|
+
# @param [Object] result expected result of sending +message+.
|
|
11
|
+
# @overload def responds_with(messages_vs_results)
|
|
12
|
+
# Matches any object that responds to all the messages with the corresponding results as specified by +messages_vs_results+.
|
|
13
|
+
# @param [Hash<Symbol,Object>] messages_vs_results +Hash+ of messages vs results.
|
|
14
|
+
# @raise [ArgumentError] if +messages_vs_results+ does not contain at least one entry.
|
|
7
15
|
#
|
|
8
|
-
# @param [Symbol] message method to invoke.
|
|
9
|
-
# @param [Object] result expected result of sending +message+.
|
|
10
16
|
# @return [RespondsWith] parameter matcher.
|
|
11
17
|
#
|
|
12
18
|
# @see Expectation#with
|
|
@@ -22,8 +28,29 @@ module Mocha
|
|
|
22
28
|
# object.expects(:method_1).with(responds_with(:upcase, "BAR"))
|
|
23
29
|
# object.method_1("foo")
|
|
24
30
|
# # error raised, because "foo".upcase != "BAR"
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
#
|
|
32
|
+
# @example Actual parameter responds with "FOO" when :upcase is invoked and "oof" when :reverse is invoked.
|
|
33
|
+
# object = mock()
|
|
34
|
+
# object.expects(:method_1).with(responds_with(upcase: "FOO", reverse: "oof"))
|
|
35
|
+
# object.method_1("foo")
|
|
36
|
+
# # no error raised, because "foo".upcase == "FOO" and "foo".reverse == "oof"
|
|
37
|
+
def responds_with(*options)
|
|
38
|
+
case options.length
|
|
39
|
+
when 0
|
|
40
|
+
raise ArgumentError, 'No arguments. Expecting at least one.'
|
|
41
|
+
when 1
|
|
42
|
+
option = options.first
|
|
43
|
+
raise ArgumentError, 'Argument is not a Hash.' unless option.is_a?(Hash)
|
|
44
|
+
raise ArgumentError, 'Argument has no entries.' if option.empty?
|
|
45
|
+
|
|
46
|
+
matchers = option.map { |message, result| RespondsWith.new(message, result) }
|
|
47
|
+
AllOf.new(*matchers)
|
|
48
|
+
when 2
|
|
49
|
+
message, result = options
|
|
50
|
+
RespondsWith.new(message, result)
|
|
51
|
+
else
|
|
52
|
+
raise ArgumentError, 'Too many arguments; use either a single argument (must be a Hash) or two arguments (a message and a result).'
|
|
53
|
+
end
|
|
27
54
|
end
|
|
28
55
|
|
|
29
56
|
# Parameter matcher which matches if actual parameter returns expected result when specified method is invoked.
|
data/lib/mocha/ruby_version.rb
CHANGED
data/lib/mocha/version.rb
CHANGED
data/mocha.gemspec
CHANGED
|
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
|
|
|
6
6
|
s.name = 'mocha'
|
|
7
7
|
s.version = Mocha::VERSION
|
|
8
8
|
s.licenses = ['MIT', 'BSD-2-Clause']
|
|
9
|
-
s.required_ruby_version = '>= 2.
|
|
9
|
+
s.required_ruby_version = '>= 2.1'
|
|
10
10
|
|
|
11
11
|
s.authors = ['James Mead']
|
|
12
12
|
s.description = 'Mocking and stubbing library with JMock/SchMock syntax, which allows mocking and stubbing of methods on real (non-mock) classes.'
|
|
@@ -21,6 +21,14 @@ Gem::Specification.new do |s|
|
|
|
21
21
|
s.homepage = 'https://mocha.jamesmead.org'
|
|
22
22
|
s.require_paths = ['lib']
|
|
23
23
|
s.summary = 'Mocking and stubbing library'
|
|
24
|
+
s.metadata = {
|
|
25
|
+
'bug_tracker_uri' => 'https://github.com/freerange/mocha/issues',
|
|
26
|
+
'changelog_uri' => 'https://github.com/freerange/mocha/blob/main/RELEASE.md',
|
|
27
|
+
'documentation_uri' => 'https://mocha.jamesmead.org/',
|
|
28
|
+
'funding_uri' => 'https://github.com/sponsors/floehopper',
|
|
29
|
+
'homepage_uri' => s.homepage,
|
|
30
|
+
'source_code_uri' => 'https://github.com/freerange/mocha'
|
|
31
|
+
}
|
|
24
32
|
|
|
25
33
|
s.add_runtime_dependency 'ruby2_keywords', '>= 0.0.5'
|
|
26
34
|
end
|