rspec-mocks 3.8.1
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +3 -0
- data/.document +5 -0
- data/.yardopts +6 -0
- data/Changelog.md +1108 -0
- data/LICENSE.md +25 -0
- data/README.md +460 -0
- data/lib/rspec/mocks.rb +130 -0
- data/lib/rspec/mocks/any_instance.rb +11 -0
- data/lib/rspec/mocks/any_instance/chain.rb +110 -0
- data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
- data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +31 -0
- data/lib/rspec/mocks/any_instance/expectation_chain.rb +50 -0
- data/lib/rspec/mocks/any_instance/message_chains.rb +83 -0
- data/lib/rspec/mocks/any_instance/proxy.rb +116 -0
- data/lib/rspec/mocks/any_instance/recorder.rb +289 -0
- data/lib/rspec/mocks/any_instance/stub_chain.rb +51 -0
- data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +23 -0
- data/lib/rspec/mocks/argument_list_matcher.rb +100 -0
- data/lib/rspec/mocks/argument_matchers.rb +320 -0
- data/lib/rspec/mocks/configuration.rb +212 -0
- data/lib/rspec/mocks/error_generator.rb +369 -0
- data/lib/rspec/mocks/example_methods.rb +434 -0
- data/lib/rspec/mocks/instance_method_stasher.rb +146 -0
- data/lib/rspec/mocks/marshal_extension.rb +41 -0
- data/lib/rspec/mocks/matchers/expectation_customization.rb +20 -0
- data/lib/rspec/mocks/matchers/have_received.rb +134 -0
- data/lib/rspec/mocks/matchers/receive.rb +132 -0
- data/lib/rspec/mocks/matchers/receive_message_chain.rb +82 -0
- data/lib/rspec/mocks/matchers/receive_messages.rb +77 -0
- data/lib/rspec/mocks/message_chain.rb +87 -0
- data/lib/rspec/mocks/message_expectation.rb +741 -0
- data/lib/rspec/mocks/method_double.rb +287 -0
- data/lib/rspec/mocks/method_reference.rb +202 -0
- data/lib/rspec/mocks/minitest_integration.rb +68 -0
- data/lib/rspec/mocks/mutate_const.rb +339 -0
- data/lib/rspec/mocks/object_reference.rb +149 -0
- data/lib/rspec/mocks/order_group.rb +81 -0
- data/lib/rspec/mocks/proxy.rb +485 -0
- data/lib/rspec/mocks/space.rb +238 -0
- data/lib/rspec/mocks/standalone.rb +3 -0
- data/lib/rspec/mocks/syntax.rb +325 -0
- data/lib/rspec/mocks/targets.rb +124 -0
- data/lib/rspec/mocks/test_double.rb +171 -0
- data/lib/rspec/mocks/verifying_double.rb +129 -0
- data/lib/rspec/mocks/verifying_message_expectation.rb +54 -0
- data/lib/rspec/mocks/verifying_proxy.rb +220 -0
- data/lib/rspec/mocks/version.rb +9 -0
- metadata +221 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Mocks
|
3
|
+
# @private
|
4
|
+
class InstanceMethodStasher
|
5
|
+
def initialize(object, method)
|
6
|
+
@object = object
|
7
|
+
@method = method
|
8
|
+
@klass = (class << object; self; end)
|
9
|
+
|
10
|
+
@original_method = nil
|
11
|
+
@method_is_stashed = false
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :original_method
|
15
|
+
|
16
|
+
if RUBY_VERSION.to_f < 1.9
|
17
|
+
# @private
|
18
|
+
def method_is_stashed?
|
19
|
+
@method_is_stashed
|
20
|
+
end
|
21
|
+
|
22
|
+
# @private
|
23
|
+
def stash
|
24
|
+
return if !method_defined_directly_on_klass? || @method_is_stashed
|
25
|
+
|
26
|
+
@klass.__send__(:alias_method, stashed_method_name, @method)
|
27
|
+
@method_is_stashed = true
|
28
|
+
end
|
29
|
+
|
30
|
+
# @private
|
31
|
+
def stashed_method_name
|
32
|
+
"obfuscated_by_rspec_mocks__#{@method}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
def restore
|
37
|
+
return unless @method_is_stashed
|
38
|
+
|
39
|
+
if @klass.__send__(:method_defined?, @method)
|
40
|
+
@klass.__send__(:undef_method, @method)
|
41
|
+
end
|
42
|
+
@klass.__send__(:alias_method, @method, stashed_method_name)
|
43
|
+
@klass.__send__(:remove_method, stashed_method_name)
|
44
|
+
@method_is_stashed = false
|
45
|
+
end
|
46
|
+
else
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def method_is_stashed?
|
50
|
+
!!@original_method
|
51
|
+
end
|
52
|
+
|
53
|
+
# @private
|
54
|
+
def stash
|
55
|
+
return unless method_defined_directly_on_klass?
|
56
|
+
@original_method ||= ::RSpec::Support.method_handle_for(@object, @method)
|
57
|
+
@klass.__send__(:undef_method, @method)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @private
|
61
|
+
def restore
|
62
|
+
return unless @original_method
|
63
|
+
|
64
|
+
if @klass.__send__(:method_defined?, @method)
|
65
|
+
@klass.__send__(:undef_method, @method)
|
66
|
+
end
|
67
|
+
|
68
|
+
handle_restoration_failures do
|
69
|
+
@klass.__send__(:define_method, @method, @original_method)
|
70
|
+
end
|
71
|
+
|
72
|
+
@original_method = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if RUBY_DESCRIPTION.include?('2.0.0p247') || RUBY_DESCRIPTION.include?('2.0.0p195')
|
77
|
+
# ruby 2.0.0-p247 and 2.0.0-p195 both have a bug that we can't work around :(.
|
78
|
+
# https://bugs.ruby-lang.org/issues/8686
|
79
|
+
def handle_restoration_failures
|
80
|
+
yield
|
81
|
+
rescue TypeError
|
82
|
+
RSpec.warn_with(
|
83
|
+
"RSpec failed to properly restore a partial double (#{@object.inspect}) " \
|
84
|
+
"to its original state due to a known bug in MRI 2.0.0-p195 & p247 " \
|
85
|
+
"(https://bugs.ruby-lang.org/issues/8686). This object may remain " \
|
86
|
+
"screwed up for the rest of this process. Please upgrade to 2.0.0-p353 or above.",
|
87
|
+
:call_site => nil, :use_spec_location_as_call_site => true
|
88
|
+
)
|
89
|
+
end
|
90
|
+
else
|
91
|
+
def handle_restoration_failures
|
92
|
+
# No known reasons for restoration to fail on other rubies.
|
93
|
+
yield
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# @private
|
100
|
+
def method_defined_directly_on_klass?
|
101
|
+
method_defined_on_klass? && method_owned_by_klass?
|
102
|
+
end
|
103
|
+
|
104
|
+
# @private
|
105
|
+
def method_defined_on_klass?(klass=@klass)
|
106
|
+
MethodReference.method_defined_at_any_visibility?(klass, @method)
|
107
|
+
end
|
108
|
+
|
109
|
+
def method_owned_by_klass?
|
110
|
+
owner = @klass.instance_method(@method).owner
|
111
|
+
|
112
|
+
# On Ruby 2.0.0+ the owner of a method on a class which has been
|
113
|
+
# `prepend`ed may actually be an instance, e.g.
|
114
|
+
# `#<MyClass:0x007fbb94e3cd10>`, rather than the expected `MyClass`.
|
115
|
+
owner = owner.class unless Module === owner
|
116
|
+
|
117
|
+
# On some 1.9s (e.g. rubinius) aliased methods
|
118
|
+
# can report the wrong owner. Example:
|
119
|
+
# class MyClass
|
120
|
+
# class << self
|
121
|
+
# alias alternate_new new
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# MyClass.owner(:alternate_new) returns `Class` when incorrect,
|
126
|
+
# but we need to consider the owner to be `MyClass` because
|
127
|
+
# it is not actually available on `Class` but is on `MyClass`.
|
128
|
+
# Hence, we verify that the owner actually has the method defined.
|
129
|
+
# If the given owner does not have the method defined, we assume
|
130
|
+
# that the method is actually owned by @klass.
|
131
|
+
#
|
132
|
+
# On 1.8, aliased methods can also report the wrong owner. Example:
|
133
|
+
# module M
|
134
|
+
# def a; end
|
135
|
+
# module_function :a
|
136
|
+
# alias b a
|
137
|
+
# module_function :b
|
138
|
+
# end
|
139
|
+
# The owner of M.b is the raw Module object, instead of the expected
|
140
|
+
# singleton class of the module
|
141
|
+
return true if RUBY_VERSION < '1.9' && owner == @object
|
142
|
+
owner == @klass || !(method_defined_on_klass?(owner))
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Mocks
|
3
|
+
# Support for `patch_marshal_to_support_partial_doubles` configuration.
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
class MarshalExtension
|
7
|
+
def self.patch!
|
8
|
+
return if Marshal.respond_to?(:dump_with_rspec_mocks)
|
9
|
+
|
10
|
+
Marshal.instance_eval do
|
11
|
+
class << self
|
12
|
+
def dump_with_rspec_mocks(object, *rest)
|
13
|
+
if !::RSpec::Mocks.space.registered?(object) || NilClass === object
|
14
|
+
dump_without_rspec_mocks(object, *rest)
|
15
|
+
else
|
16
|
+
dump_without_rspec_mocks(object.dup, *rest)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :dump_without_rspec_mocks, :dump
|
21
|
+
undef_method :dump
|
22
|
+
alias_method :dump, :dump_with_rspec_mocks
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.unpatch!
|
28
|
+
return unless Marshal.respond_to?(:dump_with_rspec_mocks)
|
29
|
+
|
30
|
+
Marshal.instance_eval do
|
31
|
+
class << self
|
32
|
+
undef_method :dump_with_rspec_mocks
|
33
|
+
undef_method :dump
|
34
|
+
alias_method :dump, :dump_without_rspec_mocks
|
35
|
+
undef_method :dump_without_rspec_mocks
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Mocks
|
3
|
+
module Matchers
|
4
|
+
# @private
|
5
|
+
class ExpectationCustomization
|
6
|
+
attr_accessor :block
|
7
|
+
|
8
|
+
def initialize(method_name, args, block)
|
9
|
+
@method_name = method_name
|
10
|
+
@args = args
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def playback_onto(expectation)
|
15
|
+
expectation.__send__(@method_name, *@args, &@block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Mocks
|
3
|
+
module Matchers
|
4
|
+
# @private
|
5
|
+
class HaveReceived
|
6
|
+
include Matcher
|
7
|
+
|
8
|
+
COUNT_CONSTRAINTS = %w[exactly at_least at_most times once twice thrice]
|
9
|
+
ARGS_CONSTRAINTS = %w[with]
|
10
|
+
CONSTRAINTS = COUNT_CONSTRAINTS + ARGS_CONSTRAINTS + %w[ordered]
|
11
|
+
|
12
|
+
def initialize(method_name, &block)
|
13
|
+
@method_name = method_name
|
14
|
+
@block = block
|
15
|
+
@constraints = []
|
16
|
+
@subject = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
"have_received"
|
21
|
+
end
|
22
|
+
|
23
|
+
def matches?(subject, &block)
|
24
|
+
@block ||= block
|
25
|
+
@subject = subject
|
26
|
+
@expectation = expect
|
27
|
+
mock_proxy.ensure_implemented(@method_name)
|
28
|
+
|
29
|
+
expected_messages_received_in_order?
|
30
|
+
end
|
31
|
+
|
32
|
+
def does_not_match?(subject)
|
33
|
+
@subject = subject
|
34
|
+
ensure_count_unconstrained
|
35
|
+
@expectation = expect.never
|
36
|
+
mock_proxy.ensure_implemented(@method_name)
|
37
|
+
expected_messages_received_in_order?
|
38
|
+
end
|
39
|
+
|
40
|
+
def failure_message
|
41
|
+
capture_failure_message
|
42
|
+
end
|
43
|
+
|
44
|
+
def failure_message_when_negated
|
45
|
+
capture_failure_message
|
46
|
+
end
|
47
|
+
|
48
|
+
def description
|
49
|
+
(@expectation ||= expect).description_for("have received")
|
50
|
+
end
|
51
|
+
|
52
|
+
CONSTRAINTS.each do |expectation|
|
53
|
+
define_method expectation do |*args|
|
54
|
+
@constraints << [expectation, *args]
|
55
|
+
self
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def setup_expectation(subject, &block)
|
60
|
+
notify_failure_message unless matches?(subject, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_negative_expectation(subject, &block)
|
64
|
+
notify_failure_message unless does_not_match?(subject, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def setup_allowance(_subject, &_block)
|
68
|
+
disallow("allow", " as it would have no effect")
|
69
|
+
end
|
70
|
+
|
71
|
+
def setup_any_instance_allowance(_subject, &_block)
|
72
|
+
disallow("allow_any_instance_of")
|
73
|
+
end
|
74
|
+
|
75
|
+
def setup_any_instance_expectation(_subject, &_block)
|
76
|
+
disallow("expect_any_instance_of")
|
77
|
+
end
|
78
|
+
|
79
|
+
def setup_any_instance_negative_expectation(_subject, &_block)
|
80
|
+
disallow("expect_any_instance_of")
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def disallow(type, reason="")
|
86
|
+
RSpec::Mocks.error_generator.raise_have_received_disallowed(type, reason)
|
87
|
+
end
|
88
|
+
|
89
|
+
def expect
|
90
|
+
expectation = mock_proxy.build_expectation(@method_name)
|
91
|
+
apply_constraints_to expectation
|
92
|
+
expectation
|
93
|
+
end
|
94
|
+
|
95
|
+
def apply_constraints_to(expectation)
|
96
|
+
@constraints.each do |constraint|
|
97
|
+
expectation.send(*constraint)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def ensure_count_unconstrained
|
102
|
+
return unless count_constraint
|
103
|
+
RSpec::Mocks.error_generator.raise_cant_constrain_count_for_negated_have_received_error(count_constraint)
|
104
|
+
end
|
105
|
+
|
106
|
+
def count_constraint
|
107
|
+
@constraints.map(&:first).find do |constraint|
|
108
|
+
COUNT_CONSTRAINTS.include?(constraint)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def capture_failure_message
|
113
|
+
RSpec::Support.with_failure_notifier(Proc.new { |err, _opt| return err.message }) do
|
114
|
+
notify_failure_message
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def notify_failure_message
|
119
|
+
mock_proxy.check_for_unexpected_arguments(@expectation)
|
120
|
+
@expectation.generate_error
|
121
|
+
end
|
122
|
+
|
123
|
+
def expected_messages_received_in_order?
|
124
|
+
mock_proxy.replay_received_message_on @expectation, &@block
|
125
|
+
@expectation.expected_messages_received? && @expectation.ensure_expected_ordering_received!
|
126
|
+
end
|
127
|
+
|
128
|
+
def mock_proxy
|
129
|
+
RSpec::Mocks.space.proxy_for(@subject)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
RSpec::Support.require_rspec_mocks 'matchers/expectation_customization'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Mocks
|
5
|
+
module Matchers
|
6
|
+
# @private
|
7
|
+
class Receive
|
8
|
+
include Matcher
|
9
|
+
|
10
|
+
def initialize(message, block)
|
11
|
+
@message = message
|
12
|
+
@block = block
|
13
|
+
@recorded_customizations = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
"receive"
|
18
|
+
end
|
19
|
+
|
20
|
+
def description
|
21
|
+
describable.description_for("receive")
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup_expectation(subject, &block)
|
25
|
+
warn_if_any_instance("expect", subject)
|
26
|
+
@describable = setup_mock_proxy_method_substitute(subject, :add_message_expectation, block)
|
27
|
+
end
|
28
|
+
alias matches? setup_expectation
|
29
|
+
|
30
|
+
def setup_negative_expectation(subject, &block)
|
31
|
+
# ensure `never` goes first for cases like `never.and_return(5)`,
|
32
|
+
# where `and_return` is meant to raise an error
|
33
|
+
@recorded_customizations.unshift ExpectationCustomization.new(:never, [], nil)
|
34
|
+
|
35
|
+
warn_if_any_instance("expect", subject)
|
36
|
+
|
37
|
+
setup_expectation(subject, &block)
|
38
|
+
end
|
39
|
+
alias does_not_match? setup_negative_expectation
|
40
|
+
|
41
|
+
def setup_allowance(subject, &block)
|
42
|
+
warn_if_any_instance("allow", subject)
|
43
|
+
setup_mock_proxy_method_substitute(subject, :add_stub, block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def setup_any_instance_expectation(subject, &block)
|
47
|
+
setup_any_instance_method_substitute(subject, :should_receive, block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def setup_any_instance_negative_expectation(subject, &block)
|
51
|
+
setup_any_instance_method_substitute(subject, :should_not_receive, block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def setup_any_instance_allowance(subject, &block)
|
55
|
+
setup_any_instance_method_substitute(subject, :stub, block)
|
56
|
+
end
|
57
|
+
|
58
|
+
MessageExpectation.public_instance_methods(false).each do |method|
|
59
|
+
next if method_defined?(method)
|
60
|
+
|
61
|
+
define_method(method) do |*args, &block|
|
62
|
+
@recorded_customizations << ExpectationCustomization.new(method, args, block)
|
63
|
+
self
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def describable
|
70
|
+
@describable ||= DefaultDescribable.new(@message)
|
71
|
+
end
|
72
|
+
|
73
|
+
def warn_if_any_instance(expression, subject)
|
74
|
+
return unless AnyInstance::Proxy === subject
|
75
|
+
|
76
|
+
RSpec.warning(
|
77
|
+
"`#{expression}(#{subject.klass}.any_instance).to` " \
|
78
|
+
"is probably not what you meant, it does not operate on " \
|
79
|
+
"any instance of `#{subject.klass}`. " \
|
80
|
+
"Use `#{expression}_any_instance_of(#{subject.klass}).to` instead."
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def setup_mock_proxy_method_substitute(subject, method, block)
|
85
|
+
proxy = ::RSpec::Mocks.space.proxy_for(subject)
|
86
|
+
setup_method_substitute(proxy, method, block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def setup_any_instance_method_substitute(subject, method, block)
|
90
|
+
proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject)
|
91
|
+
setup_method_substitute(proxy, method, block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def setup_method_substitute(host, method, block, *args)
|
95
|
+
args << @message.to_sym
|
96
|
+
block = move_block_to_last_customization(block)
|
97
|
+
|
98
|
+
expectation = host.__send__(method, *args, &(@block || block))
|
99
|
+
|
100
|
+
@recorded_customizations.each do |customization|
|
101
|
+
customization.playback_onto(expectation)
|
102
|
+
end
|
103
|
+
expectation
|
104
|
+
end
|
105
|
+
|
106
|
+
def move_block_to_last_customization(block)
|
107
|
+
last = @recorded_customizations.last
|
108
|
+
return block unless last
|
109
|
+
|
110
|
+
last.block ||= block
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# MessageExpectation objects are able to describe themselves in detail.
|
115
|
+
# We use this as a fall back when a MessageExpectation is not available.
|
116
|
+
# @private
|
117
|
+
class DefaultDescribable
|
118
|
+
def initialize(message)
|
119
|
+
@message = message
|
120
|
+
end
|
121
|
+
|
122
|
+
# This is much simpler for the `any_instance` case than what the
|
123
|
+
# user may want, but I'm not up for putting a bunch of effort
|
124
|
+
# into full descriptions for `any_instance` expectations at this point :(.
|
125
|
+
def description_for(verb)
|
126
|
+
"#{verb} #{@message}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|