rspec-mocks 3.0.0.beta1 → 3.0.0.beta2
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.
- data.tar.gz.sig +1 -1
- data/Changelog.md +95 -3
- data/README.md +27 -13
- data/features/README.md +15 -7
- data/features/argument_matchers/README.md +5 -5
- data/features/argument_matchers/explicit.feature +6 -6
- data/features/argument_matchers/general_matchers.feature +4 -4
- data/features/argument_matchers/type_matchers.feature +2 -2
- data/features/message_expectations/README.md +29 -27
- data/features/message_expectations/call_original.feature +0 -1
- data/features/message_expectations/message_chains_using_expect.feature +49 -0
- data/features/method_stubs/README.md +2 -1
- data/features/method_stubs/{any_instance.feature → allow_any_instance_of.feature} +12 -12
- data/features/method_stubs/{stub_chain.feature → receive_message_chain.feature} +3 -3
- data/features/method_stubs/to_ary.feature +1 -1
- data/features/mutating_constants/stub_defined_constant.feature +0 -1
- data/features/outside_rspec/standalone.feature +1 -1
- data/features/spies/spy_pure_mock_method.feature +1 -1
- data/features/test_frameworks/test_unit.feature +21 -10
- data/features/verifying_doubles/README.md +17 -0
- data/features/verifying_doubles/class_doubles.feature +1 -16
- data/features/verifying_doubles/dynamic_classes.feature +0 -1
- data/features/verifying_doubles/{introduction.feature → instance_doubles.feature} +41 -23
- data/features/verifying_doubles/partial_doubles.feature +2 -2
- data/lib/rspec/mocks.rb +69 -82
- data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +35 -0
- data/lib/rspec/mocks/any_instance/expectation_chain.rb +1 -2
- data/lib/rspec/mocks/any_instance/recorder.rb +52 -18
- data/lib/rspec/mocks/any_instance/stub_chain.rb +1 -1
- data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +4 -0
- data/lib/rspec/mocks/argument_list_matcher.rb +10 -44
- data/lib/rspec/mocks/argument_matchers.rb +132 -163
- data/lib/rspec/mocks/configuration.rb +28 -4
- data/lib/rspec/mocks/error_generator.rb +46 -13
- data/lib/rspec/mocks/example_methods.rb +13 -12
- data/lib/rspec/mocks/extensions/marshal.rb +1 -1
- data/lib/rspec/mocks/framework.rb +3 -4
- data/lib/rspec/mocks/instance_method_stasher.rb +2 -3
- data/lib/rspec/mocks/matchers/have_received.rb +8 -6
- data/lib/rspec/mocks/matchers/receive.rb +28 -20
- data/lib/rspec/mocks/matchers/receive_message_chain.rb +65 -0
- data/lib/rspec/mocks/matchers/receive_messages.rb +3 -2
- data/lib/rspec/mocks/message_chain.rb +91 -0
- data/lib/rspec/mocks/message_expectation.rb +86 -80
- data/lib/rspec/mocks/method_double.rb +2 -11
- data/lib/rspec/mocks/method_reference.rb +82 -23
- data/lib/rspec/mocks/method_signature_verifier.rb +207 -0
- data/lib/rspec/mocks/mutate_const.rb +34 -50
- data/lib/rspec/mocks/object_reference.rb +0 -1
- data/lib/rspec/mocks/proxy.rb +70 -13
- data/lib/rspec/mocks/ruby_features.rb +24 -0
- data/lib/rspec/mocks/space.rb +105 -31
- data/lib/rspec/mocks/standalone.rb +2 -2
- data/lib/rspec/mocks/syntax.rb +43 -8
- data/lib/rspec/mocks/targets.rb +16 -7
- data/lib/rspec/mocks/test_double.rb +41 -15
- data/lib/rspec/mocks/verifying_double.rb +51 -4
- data/lib/rspec/mocks/verifying_message_expecation.rb +12 -12
- data/lib/rspec/mocks/verifying_proxy.rb +32 -19
- data/lib/rspec/mocks/version.rb +1 -1
- data/spec/rspec/mocks/and_call_original_spec.rb +28 -7
- data/spec/rspec/mocks/and_return_spec.rb +23 -0
- data/spec/rspec/mocks/and_yield_spec.rb +1 -2
- data/spec/rspec/mocks/any_instance_spec.rb +33 -17
- data/spec/rspec/mocks/array_including_matcher_spec.rb +6 -6
- data/spec/rspec/mocks/before_all_spec.rb +132 -0
- data/spec/rspec/mocks/block_return_value_spec.rb +12 -1
- data/spec/rspec/mocks/combining_implementation_instructions_spec.rb +9 -11
- data/spec/rspec/mocks/configuration_spec.rb +14 -1
- data/spec/rspec/mocks/double_spec.rb +867 -24
- data/spec/rspec/mocks/example_methods_spec.rb +13 -0
- data/spec/rspec/mocks/extensions/marshal_spec.rb +17 -17
- data/spec/rspec/mocks/failing_argument_matchers_spec.rb +29 -1
- data/spec/rspec/mocks/hash_excluding_matcher_spec.rb +12 -12
- data/spec/rspec/mocks/hash_including_matcher_spec.rb +21 -17
- data/spec/rspec/mocks/instance_method_stasher_spec.rb +2 -3
- data/spec/rspec/mocks/matchers/have_received_spec.rb +7 -0
- data/spec/rspec/mocks/matchers/receive_message_chain_spec.rb +198 -0
- data/spec/rspec/mocks/matchers/receive_messages_spec.rb +2 -2
- data/spec/rspec/mocks/matchers/receive_spec.rb +19 -6
- data/spec/rspec/mocks/method_signature_verifier_spec.rb +272 -0
- data/spec/rspec/mocks/methods_spec.rb +0 -1
- data/spec/rspec/mocks/multiple_return_value_spec.rb +2 -2
- data/spec/rspec/mocks/mutate_const_spec.rb +24 -1
- data/spec/rspec/mocks/nil_expectation_warning_spec.rb +6 -22
- data/spec/rspec/mocks/null_object_mock_spec.rb +13 -7
- data/spec/rspec/mocks/options_hash_spec.rb +3 -3
- data/spec/rspec/mocks/{partial_mock_spec.rb → partial_double_spec.rb} +5 -2
- data/spec/rspec/mocks/{partial_mock_using_mocks_directly_spec.rb → partial_double_using_mocks_directly_spec.rb} +1 -1
- data/spec/rspec/mocks/passing_argument_matchers_spec.rb +18 -0
- data/spec/rspec/mocks/serialization_spec.rb +1 -0
- data/spec/rspec/mocks/space_spec.rb +218 -7
- data/spec/rspec/mocks/stub_chain_spec.rb +6 -0
- data/spec/rspec/mocks/stub_spec.rb +0 -6
- data/spec/rspec/mocks/syntax_spec.rb +19 -0
- data/spec/rspec/mocks/test_double_spec.rb +0 -1
- data/spec/rspec/mocks/verifying_double_spec.rb +281 -18
- data/spec/rspec/mocks/verifying_message_expecation_spec.rb +7 -6
- data/spec/rspec/mocks_spec.rb +168 -42
- data/spec/spec_helper.rb +34 -22
- metadata +94 -63
- metadata.gz.sig +0 -0
- checksums.yaml +0 -15
- checksums.yaml.gz.sig +0 -2
- data/features/outside_rspec/configuration.feature +0 -60
- data/lib/rspec/mocks/arity_calculator.rb +0 -66
- data/lib/rspec/mocks/errors.rb +0 -12
- data/lib/rspec/mocks/mock.rb +0 -7
- data/lib/rspec/mocks/proxy_for_nil.rb +0 -37
- data/lib/rspec/mocks/stub_chain.rb +0 -51
- data/spec/rspec/mocks/argument_expectation_spec.rb +0 -32
- data/spec/rspec/mocks/arity_calculator_spec.rb +0 -95
- data/spec/rspec/mocks/mock_space_spec.rb +0 -113
- data/spec/rspec/mocks/mock_spec.rb +0 -788
@@ -35,15 +35,7 @@ module RSpec
|
|
35
35
|
|
36
36
|
# @private
|
37
37
|
def visibility
|
38
|
-
|
39
|
-
'public'
|
40
|
-
elsif object_singleton_class.private_method_defined?(@method_name)
|
41
|
-
'private'
|
42
|
-
elsif object_singleton_class.protected_method_defined?(@method_name)
|
43
|
-
'protected'
|
44
|
-
else
|
45
|
-
'public'
|
46
|
-
end
|
38
|
+
@proxy.visibility_for(@method_name)
|
47
39
|
end
|
48
40
|
|
49
41
|
# @private
|
@@ -98,8 +90,7 @@ module RSpec
|
|
98
90
|
# @private
|
99
91
|
def restore_original_visibility
|
100
92
|
return unless @original_visibility &&
|
101
|
-
|
102
|
-
object_singleton_class.private_method_defined?(@method_name))
|
93
|
+
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
|
103
94
|
|
104
95
|
object_singleton_class.__send__(*@original_visibility)
|
105
96
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module RSpec
|
2
2
|
module Mocks
|
3
|
-
# Represents a method on
|
3
|
+
# Represents a method on an object that may or may not be defined.
|
4
|
+
# The method may be an instance method on a module or a method on
|
5
|
+
# any object.
|
4
6
|
#
|
5
7
|
# @private
|
6
8
|
class MethodReference
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
+
def initialize(object_reference, method_name)
|
10
|
+
@object_reference = object_reference
|
9
11
|
@method_name = method_name
|
10
12
|
end
|
11
13
|
|
@@ -13,43 +15,92 @@ module RSpec
|
|
13
15
|
# a `NoMethodError`. It might be dynamically implemented by
|
14
16
|
# `method_missing`.
|
15
17
|
def implemented?
|
16
|
-
@
|
18
|
+
@object_reference.when_loaded do |m|
|
17
19
|
method_implemented?(m)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
23
|
+
# Returns true if we definitively know that sending the method
|
24
|
+
# will result in a `NoMethodError`.
|
25
|
+
#
|
26
|
+
# This is not simply the inverse of `implemented?`: there are
|
27
|
+
# cases when we don't know if a method is implemented and
|
28
|
+
# both `implemented?` and `unimplemented?` will return false.
|
29
|
+
def unimplemented?
|
30
|
+
@object_reference.when_loaded do |m|
|
31
|
+
return !implemented?
|
32
|
+
end
|
33
|
+
|
34
|
+
# If it's not loaded, then it may be implemented but we can't check.
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
21
38
|
# A method is defined if we are able to get a `Method` object for it.
|
22
39
|
# In that case, we can assert against metadata like the arity.
|
23
40
|
def defined?
|
24
|
-
@
|
41
|
+
@object_reference.when_loaded do |m|
|
25
42
|
method_defined?(m)
|
26
43
|
end
|
27
44
|
end
|
28
45
|
|
29
|
-
def
|
46
|
+
def with_signature
|
30
47
|
if original = original_method
|
31
|
-
yield original
|
48
|
+
yield MethodSignature.new(original)
|
32
49
|
end
|
33
50
|
end
|
34
51
|
|
35
|
-
|
36
|
-
|
37
|
-
|
52
|
+
def visibility
|
53
|
+
@object_reference.when_loaded do |m|
|
54
|
+
return visibility_from(m)
|
55
|
+
end
|
56
|
+
|
57
|
+
# When it's not loaded, assume it's public. We don't want to
|
58
|
+
# wrongly treat the method as private.
|
59
|
+
:public
|
38
60
|
end
|
39
61
|
|
40
62
|
private
|
63
|
+
|
41
64
|
def original_method
|
42
|
-
@
|
65
|
+
@object_reference.when_loaded do |m|
|
43
66
|
self.defined? && find_method(m)
|
44
67
|
end
|
45
68
|
end
|
69
|
+
|
70
|
+
def self.instance_method_visibility_for(klass, method_name)
|
71
|
+
if klass.public_method_defined?(method_name)
|
72
|
+
:public
|
73
|
+
elsif klass.private_method_defined?(method_name)
|
74
|
+
:private
|
75
|
+
elsif klass.protected_method_defined?(method_name)
|
76
|
+
:protected
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class << self
|
81
|
+
alias method_defined_at_any_visibility? instance_method_visibility_for
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.method_visibility_for(object, method_name)
|
85
|
+
instance_method_visibility_for(class << object; self; end, method_name).tap do |vis|
|
86
|
+
# If the method is not defined on the class, `instance_method_visibility_for`
|
87
|
+
# returns `nil`. However, it may be handled dynamically by `method_missing`,
|
88
|
+
# so here we check `respond_to` (passing false to not check private methods).
|
89
|
+
#
|
90
|
+
# This only considers the public case, but I don't think it's possible to
|
91
|
+
# write `method_missing` in such a way that it handles a dynamic message
|
92
|
+
# with private or protected visibility. Ruby doesn't provide you with
|
93
|
+
# the caller info.
|
94
|
+
return :public if vis.nil? && object.respond_to?(method_name, false)
|
95
|
+
end
|
96
|
+
end
|
46
97
|
end
|
47
98
|
|
48
99
|
# @private
|
49
100
|
class InstanceMethodReference < MethodReference
|
50
101
|
private
|
51
|
-
def method_implemented?(
|
52
|
-
|
102
|
+
def method_implemented?(mod)
|
103
|
+
MethodReference.method_defined_at_any_visibility?(mod, @method_name)
|
53
104
|
end
|
54
105
|
|
55
106
|
# Ideally, we'd use `respond_to?` for `method_implemented?` but we need a
|
@@ -66,29 +117,37 @@ module RSpec
|
|
66
117
|
# This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
|
67
118
|
# https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
|
68
119
|
if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
|
69
|
-
def find_method(
|
70
|
-
|
120
|
+
def find_method(mod)
|
121
|
+
mod.dup.instance_method(@method_name)
|
71
122
|
end
|
72
123
|
else
|
73
|
-
def find_method(
|
74
|
-
|
124
|
+
def find_method(mod)
|
125
|
+
mod.instance_method(@method_name)
|
75
126
|
end
|
76
127
|
end
|
128
|
+
|
129
|
+
def visibility_from(mod)
|
130
|
+
MethodReference.instance_method_visibility_for(mod, @method_name)
|
131
|
+
end
|
77
132
|
end
|
78
133
|
|
79
134
|
# @private
|
80
135
|
class ObjectMethodReference < MethodReference
|
81
136
|
private
|
82
|
-
def method_implemented?(
|
83
|
-
|
137
|
+
def method_implemented?(object)
|
138
|
+
object.respond_to?(@method_name, true)
|
139
|
+
end
|
140
|
+
|
141
|
+
def method_defined?(object)
|
142
|
+
(class << object; self; end).method_defined?(@method_name)
|
84
143
|
end
|
85
144
|
|
86
|
-
def
|
87
|
-
|
145
|
+
def find_method(object)
|
146
|
+
object.method(@method_name)
|
88
147
|
end
|
89
148
|
|
90
|
-
def
|
91
|
-
|
149
|
+
def visibility_from(object)
|
150
|
+
MethodReference.method_visibility_for(object, @method_name)
|
92
151
|
end
|
93
152
|
end
|
94
153
|
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'rspec/mocks/ruby_features'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Mocks
|
5
|
+
# Extracts info about the number of arguments and allowed/required
|
6
|
+
# keyword args of a given method.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class MethodSignature
|
10
|
+
attr_reader :min_non_kw_args, :max_non_kw_args
|
11
|
+
|
12
|
+
def initialize(method)
|
13
|
+
@method = method
|
14
|
+
classify_parameters
|
15
|
+
end
|
16
|
+
|
17
|
+
def non_kw_args_arity_description
|
18
|
+
case max_non_kw_args
|
19
|
+
when min_non_kw_args then min_non_kw_args.to_s
|
20
|
+
when INFINITY then "#{min_non_kw_args} or more"
|
21
|
+
else "#{min_non_kw_args} to #{max_non_kw_args}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
if RubyFeatures.optional_and_splat_args_supported?
|
26
|
+
def description
|
27
|
+
@description ||= begin
|
28
|
+
parts = []
|
29
|
+
|
30
|
+
unless non_kw_args_arity_description == "0"
|
31
|
+
parts << "arity of #{non_kw_args_arity_description}"
|
32
|
+
end
|
33
|
+
|
34
|
+
if @optional_kw_args.any?
|
35
|
+
parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})"
|
36
|
+
end
|
37
|
+
|
38
|
+
if @required_kw_args.any?
|
39
|
+
parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})"
|
40
|
+
end
|
41
|
+
|
42
|
+
if @allows_any_kw_args
|
43
|
+
parts << "any additional keyword args"
|
44
|
+
end
|
45
|
+
|
46
|
+
parts.join(" and ")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def missing_kw_args_from(given_kw_args)
|
51
|
+
@required_kw_args - given_kw_args
|
52
|
+
end
|
53
|
+
|
54
|
+
def invalid_kw_args_from(given_kw_args)
|
55
|
+
return [] if @allows_any_kw_args
|
56
|
+
given_kw_args - @allowed_kw_args
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_kw_args_in?(args)
|
60
|
+
return false unless Hash === args.last
|
61
|
+
return false if args.count <= min_non_kw_args
|
62
|
+
|
63
|
+
@allows_any_kw_args || @allowed_kw_args.any?
|
64
|
+
end
|
65
|
+
|
66
|
+
def classify_parameters
|
67
|
+
optional_non_kw_args = @min_non_kw_args = 0
|
68
|
+
@optional_kw_args, @required_kw_args = [], []
|
69
|
+
@allows_any_kw_args = false
|
70
|
+
|
71
|
+
@method.parameters.each do |(type, name)|
|
72
|
+
case type
|
73
|
+
# def foo(a:)
|
74
|
+
when :keyreq then @required_kw_args << name
|
75
|
+
# def foo(a: 1)
|
76
|
+
when :key then @optional_kw_args << name
|
77
|
+
# def foo(**kw_args)
|
78
|
+
when :keyrest then @allows_any_kw_args = true
|
79
|
+
# def foo(a)
|
80
|
+
when :req then @min_non_kw_args += 1
|
81
|
+
# def foo(a = 1)
|
82
|
+
when :opt then optional_non_kw_args += 1
|
83
|
+
# def foo(*a)
|
84
|
+
when :rest then optional_non_kw_args = INFINITY
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@max_non_kw_args = @min_non_kw_args + optional_non_kw_args
|
89
|
+
@allowed_kw_args = @required_kw_args + @optional_kw_args
|
90
|
+
end
|
91
|
+
else
|
92
|
+
def description
|
93
|
+
"arity of #{non_kw_args_arity_description}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def missing_kw_args_from(given_kw_args)
|
97
|
+
[]
|
98
|
+
end
|
99
|
+
|
100
|
+
def invalid_kw_args_from(given_kw_args)
|
101
|
+
[]
|
102
|
+
end
|
103
|
+
|
104
|
+
def has_kw_args_in?(args)
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
def classify_parameters
|
109
|
+
arity = @method.arity
|
110
|
+
if arity < 0
|
111
|
+
# `~` inverts the one's complement and gives us the
|
112
|
+
# number of required args
|
113
|
+
@min_non_kw_args = ~arity
|
114
|
+
@max_non_kw_args = INFINITY
|
115
|
+
else
|
116
|
+
@min_non_kw_args = arity
|
117
|
+
@max_non_kw_args = arity
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
INFINITY = 1/0.0
|
123
|
+
end
|
124
|
+
|
125
|
+
# Deals with the slightly different semantics of block arguments.
|
126
|
+
# For methods, arguments are required unless a default value is provided.
|
127
|
+
# For blocks, arguments are optional, even if no default value is provided.
|
128
|
+
#
|
129
|
+
# However, we want to treat block args as required since you virtually always
|
130
|
+
# want to pass a value for each received argument and our `and_yield` has
|
131
|
+
# treated block args as required for many years.
|
132
|
+
#
|
133
|
+
# @api private
|
134
|
+
class BlockSignature < MethodSignature
|
135
|
+
if RubyFeatures.optional_and_splat_args_supported?
|
136
|
+
def classify_parameters
|
137
|
+
super
|
138
|
+
@min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Figures out wheter a given method can accept various arguments.
|
144
|
+
# Surprisingly non-trivial.
|
145
|
+
#
|
146
|
+
# @api private
|
147
|
+
class MethodSignatureVerifier
|
148
|
+
# @api private
|
149
|
+
attr_reader :non_kw_args, :kw_args
|
150
|
+
|
151
|
+
def initialize(signature, args)
|
152
|
+
@signature = signature
|
153
|
+
@non_kw_args, @kw_args = split_args(*args)
|
154
|
+
end
|
155
|
+
|
156
|
+
# @api private
|
157
|
+
def valid?
|
158
|
+
missing_kw_args.empty? &&
|
159
|
+
invalid_kw_args.empty? &&
|
160
|
+
valid_non_kw_args?
|
161
|
+
end
|
162
|
+
|
163
|
+
# @api private
|
164
|
+
def error_message
|
165
|
+
if missing_kw_args.any?
|
166
|
+
"Missing required keyword arguments: %s" % [
|
167
|
+
missing_kw_args.join(", ")
|
168
|
+
]
|
169
|
+
elsif invalid_kw_args.any?
|
170
|
+
"Invalid keyword arguments provided: %s" % [
|
171
|
+
invalid_kw_args.join(", ")
|
172
|
+
]
|
173
|
+
elsif !valid_non_kw_args?
|
174
|
+
"Wrong number of arguments. Expected %s, got %s." % [
|
175
|
+
@signature.non_kw_args_arity_description,
|
176
|
+
non_kw_args.length
|
177
|
+
]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def valid_non_kw_args?
|
184
|
+
actual = non_kw_args.length
|
185
|
+
@signature.min_non_kw_args <= actual && actual <= @signature.max_non_kw_args
|
186
|
+
end
|
187
|
+
|
188
|
+
def missing_kw_args
|
189
|
+
@signature.missing_kw_args_from(kw_args)
|
190
|
+
end
|
191
|
+
|
192
|
+
def invalid_kw_args
|
193
|
+
@signature.invalid_kw_args_from(kw_args)
|
194
|
+
end
|
195
|
+
|
196
|
+
def split_args(*args)
|
197
|
+
kw_args = if @signature.has_kw_args_in?(args)
|
198
|
+
args.pop.keys
|
199
|
+
else
|
200
|
+
[]
|
201
|
+
end
|
202
|
+
|
203
|
+
[args, kw_args]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -138,7 +138,6 @@ module RSpec
|
|
138
138
|
|
139
139
|
const
|
140
140
|
end
|
141
|
-
private_class_method :unmutated
|
142
141
|
|
143
142
|
# Queries rspec-mocks to find out information about the named constant.
|
144
143
|
#
|
@@ -146,7 +145,7 @@ module RSpec
|
|
146
145
|
# @return [Constant] an object contaning information about the named
|
147
146
|
# constant.
|
148
147
|
def self.original(name)
|
149
|
-
mutator =
|
148
|
+
mutator = ::RSpec::Mocks.space.constant_mutator_for(name)
|
150
149
|
mutator ? mutator.to_constant : unmutated(name)
|
151
150
|
end
|
152
151
|
end
|
@@ -190,8 +189,6 @@ module RSpec
|
|
190
189
|
# so you can hide constants in other contexts (e.g. helper
|
191
190
|
# classes).
|
192
191
|
def self.hide(constant_name)
|
193
|
-
return unless recursive_const_defined?(constant_name)
|
194
|
-
|
195
192
|
mutate(ConstantHider.new(constant_name, nil, { }))
|
196
193
|
nil
|
197
194
|
end
|
@@ -210,6 +207,7 @@ module RSpec
|
|
210
207
|
@transfer_nested_constants = transfer_nested_constants
|
211
208
|
@context_parts = @full_constant_name.split('::')
|
212
209
|
@const_name = @context_parts.pop
|
210
|
+
@reset_performed = false
|
213
211
|
end
|
214
212
|
|
215
213
|
def to_constant
|
@@ -218,6 +216,11 @@ module RSpec
|
|
218
216
|
|
219
217
|
const
|
220
218
|
end
|
219
|
+
|
220
|
+
def idempotently_reset
|
221
|
+
reset unless @reset_performed
|
222
|
+
@reset_performed = true
|
223
|
+
end
|
221
224
|
end
|
222
225
|
|
223
226
|
# Hides a defined constant for the duration of an example.
|
@@ -225,6 +228,7 @@ module RSpec
|
|
225
228
|
# @api private
|
226
229
|
class ConstantHider < BaseMutator
|
227
230
|
def mutate
|
231
|
+
return unless @defined = recursive_const_defined?(full_constant_name)
|
228
232
|
@context = recursive_const_get(@context_parts.join('::'))
|
229
233
|
@original_value = get_const_defined_on(@context, @const_name)
|
230
234
|
|
@@ -232,6 +236,8 @@ module RSpec
|
|
232
236
|
end
|
233
237
|
|
234
238
|
def to_constant
|
239
|
+
return Constant.unmutated(full_constant_name) unless @defined
|
240
|
+
|
235
241
|
const = super
|
236
242
|
const.hidden = true
|
237
243
|
const.previously_defined = true
|
@@ -239,7 +245,8 @@ module RSpec
|
|
239
245
|
const
|
240
246
|
end
|
241
247
|
|
242
|
-
def
|
248
|
+
def reset
|
249
|
+
return unless @defined
|
243
250
|
@context.const_set(@const_name, @original_value)
|
244
251
|
end
|
245
252
|
end
|
@@ -268,7 +275,7 @@ module RSpec
|
|
268
275
|
const
|
269
276
|
end
|
270
277
|
|
271
|
-
def
|
278
|
+
def reset
|
272
279
|
@context.__send__(:remove_const, @const_name)
|
273
280
|
@context.const_set(@const_name, @original_value)
|
274
281
|
end
|
@@ -315,19 +322,15 @@ module RSpec
|
|
315
322
|
# @api private
|
316
323
|
class UndefinedConstantSetter < BaseMutator
|
317
324
|
def mutate
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
|
326
|
-
klass.const_set(name, Module.new)
|
325
|
+
@parent = @context_parts.inject(Object) do |klass, name|
|
326
|
+
if const_defined_on?(klass, name)
|
327
|
+
get_const_defined_on(klass, name)
|
328
|
+
else
|
329
|
+
ConstantMutator.stub(name_for(klass, name), Module.new)
|
330
|
+
end
|
327
331
|
end
|
328
332
|
|
329
|
-
@
|
330
|
-
context.const_set(@const_name, @mutated_value)
|
333
|
+
@parent.const_set(@const_name, @mutated_value)
|
331
334
|
end
|
332
335
|
|
333
336
|
def to_constant
|
@@ -338,8 +341,19 @@ module RSpec
|
|
338
341
|
const
|
339
342
|
end
|
340
343
|
|
341
|
-
def
|
342
|
-
@
|
344
|
+
def reset
|
345
|
+
@parent.__send__(:remove_const, @const_name)
|
346
|
+
end
|
347
|
+
|
348
|
+
private
|
349
|
+
|
350
|
+
def name_for(parent, name)
|
351
|
+
root = if parent == Object
|
352
|
+
''
|
353
|
+
else
|
354
|
+
parent.name
|
355
|
+
end
|
356
|
+
root + '::' + name
|
343
357
|
end
|
344
358
|
end
|
345
359
|
|
@@ -349,40 +363,10 @@ module RSpec
|
|
349
363
|
#
|
350
364
|
# @api private
|
351
365
|
def self.mutate(mutator)
|
352
|
-
|
366
|
+
::RSpec::Mocks.space.register_constant_mutator(mutator)
|
353
367
|
mutator.mutate
|
354
368
|
end
|
355
369
|
|
356
|
-
# Resets all stubbed constants. This is called automatically
|
357
|
-
# by rspec-mocks when an example finishes.
|
358
|
-
#
|
359
|
-
# @api private
|
360
|
-
def self.reset_all
|
361
|
-
# We use reverse order so that if the same constant
|
362
|
-
# was stubbed multiple times, the original value gets
|
363
|
-
# properly restored.
|
364
|
-
mutators.reverse.each { |s| s.rspec_reset }
|
365
|
-
|
366
|
-
mutators.clear
|
367
|
-
end
|
368
|
-
|
369
|
-
# The list of constant mutators that have been used for
|
370
|
-
# the current example.
|
371
|
-
#
|
372
|
-
# @api private
|
373
|
-
def self.mutators
|
374
|
-
@mutators ||= []
|
375
|
-
end
|
376
|
-
|
377
|
-
# @api private
|
378
|
-
def self.register_mutator(mutator)
|
379
|
-
mutators << mutator
|
380
|
-
end
|
381
|
-
|
382
|
-
def self.find(name)
|
383
|
-
mutators.find { |s| s.full_constant_name == name }
|
384
|
-
end
|
385
|
-
|
386
370
|
# Used internally by the constant stubbing to raise a helpful
|
387
371
|
# error when a constant like "A::B::C" is stubbed and A::B is
|
388
372
|
# not a module (and thus, it's impossible to define "A::B::C"
|