rspec-mocks 3.0.0.beta1 → 3.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|