opal-rspec-cj 0.4.4
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
- data/.gitignore +3 -0
- data/.gitmodules +15 -0
- data/.travis.yml +13 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +8 -0
- data/README.md +147 -0
- data/Rakefile +26 -0
- data/config.ru +10 -0
- data/example/Gemfile +4 -0
- data/example/README.md +13 -0
- data/example/Rakefile +8 -0
- data/example/opal/user.rb +11 -0
- data/example/spec/user_spec.rb +15 -0
- data/lib/opal-rspec.rb +2 -0
- data/lib/opal/rspec.rb +20 -0
- data/lib/opal/rspec/rake_task.rb +63 -0
- data/lib/opal/rspec/version.rb +5 -0
- data/opal-rspec.gemspec +21 -0
- data/opal/opal-rspec.rb +1 -0
- data/opal/opal/rspec.rb +25 -0
- data/opal/opal/rspec/async.rb +289 -0
- data/opal/opal/rspec/browser_formatter.rb +188 -0
- data/opal/opal/rspec/fixes.rb +116 -0
- data/opal/opal/rspec/requires.rb +45 -0
- data/opal/opal/rspec/runner.rb +69 -0
- data/opal/opal/rspec/sprockets_runner.rb.erb +11 -0
- data/opal/opal/rspec/text_formatter.rb +74 -0
- data/spec/async_spec.rb +38 -0
- data/spec/example_spec.rb +163 -0
- data/spec/matchers_spec.rb +201 -0
- data/spec/mock_spec.rb +63 -0
- data/spec/named_subject_spec.rb +11 -0
- data/spec/should_syntax_spec.rb +17 -0
- data/vendor/spec_runner.js +50 -0
- data/vendor_lib/rspec-expectations.rb +1 -0
- data/vendor_lib/rspec.rb +3 -0
- data/vendor_lib/rspec/autorun.rb +2 -0
- data/vendor_lib/rspec/core.rb +203 -0
- data/vendor_lib/rspec/core/backport_random.rb +302 -0
- data/vendor_lib/rspec/core/backtrace_formatter.rb +65 -0
- data/vendor_lib/rspec/core/command_line.rb +36 -0
- data/vendor_lib/rspec/core/configuration.rb +1129 -0
- data/vendor_lib/rspec/core/configuration_options.rb +143 -0
- data/vendor_lib/rspec/core/drb_command_line.rb +26 -0
- data/vendor_lib/rspec/core/drb_options.rb +87 -0
- data/vendor_lib/rspec/core/dsl.rb +26 -0
- data/vendor_lib/rspec/core/example.rb +312 -0
- data/vendor_lib/rspec/core/example_group.rb +540 -0
- data/vendor_lib/rspec/core/filter_manager.rb +224 -0
- data/vendor_lib/rspec/core/flat_map.rb +17 -0
- data/vendor_lib/rspec/core/formatters.rb +54 -0
- data/vendor_lib/rspec/core/formatters/base_formatter.rb +291 -0
- data/vendor_lib/rspec/core/formatters/base_text_formatter.rb +307 -0
- data/vendor_lib/rspec/core/formatters/deprecation_formatter.rb +193 -0
- data/vendor_lib/rspec/core/formatters/documentation_formatter.rb +67 -0
- data/vendor_lib/rspec/core/formatters/helpers.rb +82 -0
- data/vendor_lib/rspec/core/formatters/html_formatter.rb +155 -0
- data/vendor_lib/rspec/core/formatters/html_printer.rb +408 -0
- data/vendor_lib/rspec/core/formatters/json_formatter.rb +99 -0
- data/vendor_lib/rspec/core/formatters/progress_formatter.rb +32 -0
- data/vendor_lib/rspec/core/formatters/snippet_extractor.rb +101 -0
- data/vendor_lib/rspec/core/hooks.rb +535 -0
- data/vendor_lib/rspec/core/memoized_helpers.rb +431 -0
- data/vendor_lib/rspec/core/metadata.rb +313 -0
- data/vendor_lib/rspec/core/mocking/with_absolutely_nothing.rb +11 -0
- data/vendor_lib/rspec/core/mocking/with_flexmock.rb +27 -0
- data/vendor_lib/rspec/core/mocking/with_mocha.rb +52 -0
- data/vendor_lib/rspec/core/mocking/with_rr.rb +27 -0
- data/vendor_lib/rspec/core/mocking/with_rspec.rb +27 -0
- data/vendor_lib/rspec/core/option_parser.rb +234 -0
- data/vendor_lib/rspec/core/ordering.rb +154 -0
- data/vendor_lib/rspec/core/pending.rb +110 -0
- data/vendor_lib/rspec/core/project_initializer.rb +88 -0
- data/vendor_lib/rspec/core/rake_task.rb +128 -0
- data/vendor_lib/rspec/core/reporter.rb +132 -0
- data/vendor_lib/rspec/core/ruby_project.rb +44 -0
- data/vendor_lib/rspec/core/runner.rb +97 -0
- data/vendor_lib/rspec/core/shared_context.rb +53 -0
- data/vendor_lib/rspec/core/shared_example_group.rb +146 -0
- data/vendor_lib/rspec/core/shared_example_group/collection.rb +27 -0
- data/vendor_lib/rspec/core/version.rb +7 -0
- data/vendor_lib/rspec/core/warnings.rb +22 -0
- data/vendor_lib/rspec/core/world.rb +131 -0
- data/vendor_lib/rspec/expectations.rb +75 -0
- data/vendor_lib/rspec/expectations/differ.rb +154 -0
- data/vendor_lib/rspec/expectations/errors.rb +9 -0
- data/vendor_lib/rspec/expectations/expectation_target.rb +87 -0
- data/vendor_lib/rspec/expectations/extensions.rb +1 -0
- data/vendor_lib/rspec/expectations/extensions/object.rb +29 -0
- data/vendor_lib/rspec/expectations/fail_with.rb +79 -0
- data/vendor_lib/rspec/expectations/handler.rb +68 -0
- data/vendor_lib/rspec/expectations/syntax.rb +182 -0
- data/vendor_lib/rspec/expectations/version.rb +8 -0
- data/vendor_lib/rspec/matchers.rb +633 -0
- data/vendor_lib/rspec/matchers/built_in.rb +39 -0
- data/vendor_lib/rspec/matchers/built_in/base_matcher.rb +68 -0
- data/vendor_lib/rspec/matchers/built_in/be.rb +213 -0
- data/vendor_lib/rspec/matchers/built_in/be_instance_of.rb +15 -0
- data/vendor_lib/rspec/matchers/built_in/be_kind_of.rb +11 -0
- data/vendor_lib/rspec/matchers/built_in/be_within.rb +55 -0
- data/vendor_lib/rspec/matchers/built_in/change.rb +141 -0
- data/vendor_lib/rspec/matchers/built_in/cover.rb +21 -0
- data/vendor_lib/rspec/matchers/built_in/eq.rb +22 -0
- data/vendor_lib/rspec/matchers/built_in/eql.rb +23 -0
- data/vendor_lib/rspec/matchers/built_in/equal.rb +48 -0
- data/vendor_lib/rspec/matchers/built_in/exist.rb +26 -0
- data/vendor_lib/rspec/matchers/built_in/has.rb +48 -0
- data/vendor_lib/rspec/matchers/built_in/include.rb +61 -0
- data/vendor_lib/rspec/matchers/built_in/match.rb +17 -0
- data/vendor_lib/rspec/matchers/built_in/match_array.rb +51 -0
- data/vendor_lib/rspec/matchers/built_in/raise_error.rb +154 -0
- data/vendor_lib/rspec/matchers/built_in/respond_to.rb +74 -0
- data/vendor_lib/rspec/matchers/built_in/satisfy.rb +30 -0
- data/vendor_lib/rspec/matchers/built_in/start_and_end_with.rb +48 -0
- data/vendor_lib/rspec/matchers/built_in/throw_symbol.rb +94 -0
- data/vendor_lib/rspec/matchers/built_in/yield.rb +297 -0
- data/vendor_lib/rspec/matchers/compatibility.rb +14 -0
- data/vendor_lib/rspec/matchers/configuration.rb +113 -0
- data/vendor_lib/rspec/matchers/dsl.rb +23 -0
- data/vendor_lib/rspec/matchers/generated_descriptions.rb +35 -0
- data/vendor_lib/rspec/matchers/matcher.rb +301 -0
- data/vendor_lib/rspec/matchers/method_missing.rb +12 -0
- data/vendor_lib/rspec/matchers/operator_matcher.rb +99 -0
- data/vendor_lib/rspec/matchers/pretty.rb +70 -0
- data/vendor_lib/rspec/matchers/test_unit_integration.rb +11 -0
- data/vendor_lib/rspec/mocks.rb +100 -0
- data/vendor_lib/rspec/mocks/any_instance/chain.rb +92 -0
- data/vendor_lib/rspec/mocks/any_instance/expectation_chain.rb +47 -0
- data/vendor_lib/rspec/mocks/any_instance/message_chains.rb +75 -0
- data/vendor_lib/rspec/mocks/any_instance/recorder.rb +200 -0
- data/vendor_lib/rspec/mocks/any_instance/stub_chain.rb +45 -0
- data/vendor_lib/rspec/mocks/any_instance/stub_chain_chain.rb +23 -0
- data/vendor_lib/rspec/mocks/argument_list_matcher.rb +104 -0
- data/vendor_lib/rspec/mocks/argument_matchers.rb +264 -0
- data/vendor_lib/rspec/mocks/arity_calculator.rb +66 -0
- data/vendor_lib/rspec/mocks/configuration.rb +111 -0
- data/vendor_lib/rspec/mocks/error_generator.rb +203 -0
- data/vendor_lib/rspec/mocks/errors.rb +12 -0
- data/vendor_lib/rspec/mocks/example_methods.rb +201 -0
- data/vendor_lib/rspec/mocks/extensions/marshal.rb +17 -0
- data/vendor_lib/rspec/mocks/framework.rb +36 -0
- data/vendor_lib/rspec/mocks/instance_method_stasher.rb +112 -0
- data/vendor_lib/rspec/mocks/matchers/have_received.rb +99 -0
- data/vendor_lib/rspec/mocks/matchers/receive.rb +112 -0
- data/vendor_lib/rspec/mocks/matchers/receive_messages.rb +72 -0
- data/vendor_lib/rspec/mocks/message_expectation.rb +643 -0
- data/vendor_lib/rspec/mocks/method_double.rb +209 -0
- data/vendor_lib/rspec/mocks/method_reference.rb +95 -0
- data/vendor_lib/rspec/mocks/mock.rb +7 -0
- data/vendor_lib/rspec/mocks/mutate_const.rb +406 -0
- data/vendor_lib/rspec/mocks/object_reference.rb +90 -0
- data/vendor_lib/rspec/mocks/order_group.rb +82 -0
- data/vendor_lib/rspec/mocks/proxy.rb +269 -0
- data/vendor_lib/rspec/mocks/proxy_for_nil.rb +37 -0
- data/vendor_lib/rspec/mocks/space.rb +95 -0
- data/vendor_lib/rspec/mocks/standalone.rb +3 -0
- data/vendor_lib/rspec/mocks/stub_chain.rb +51 -0
- data/vendor_lib/rspec/mocks/syntax.rb +374 -0
- data/vendor_lib/rspec/mocks/targets.rb +90 -0
- data/vendor_lib/rspec/mocks/test_double.rb +109 -0
- data/vendor_lib/rspec/mocks/verifying_double.rb +77 -0
- data/vendor_lib/rspec/mocks/verifying_message_expecation.rb +60 -0
- data/vendor_lib/rspec/mocks/verifying_proxy.rb +151 -0
- data/vendor_lib/rspec/mocks/version.rb +7 -0
- data/vendor_lib/rspec/support.rb +6 -0
- data/vendor_lib/rspec/support/caller_filter.rb +56 -0
- data/vendor_lib/rspec/support/spec.rb +14 -0
- data/vendor_lib/rspec/support/spec/deprecation_helpers.rb +29 -0
- data/vendor_lib/rspec/support/spec/in_sub_process.rb +40 -0
- data/vendor_lib/rspec/support/spec/stderr_splitter.rb +50 -0
- data/vendor_lib/rspec/support/version.rb +7 -0
- data/vendor_lib/rspec/support/warnings.rb +41 -0
- data/vendor_lib/rspec/version.rb +5 -0
- metadata +268 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Mocks
|
|
3
|
+
# @private
|
|
4
|
+
class MethodDouble
|
|
5
|
+
# @private
|
|
6
|
+
attr_reader :method_name, :object, :expectations, :stubs
|
|
7
|
+
|
|
8
|
+
# @private
|
|
9
|
+
def initialize(object, method_name, proxy)
|
|
10
|
+
@method_name = method_name
|
|
11
|
+
@object = object
|
|
12
|
+
@proxy = proxy
|
|
13
|
+
|
|
14
|
+
@original_visibility = nil
|
|
15
|
+
@method_stasher = InstanceMethodStasher.new(object, method_name)
|
|
16
|
+
@method_is_proxied = false
|
|
17
|
+
@expectations = []
|
|
18
|
+
@stubs = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def original_method
|
|
22
|
+
# If original method is not present, uses the `method_missing`
|
|
23
|
+
# handler of the object. This accounts for cases where the user has not
|
|
24
|
+
# correctly defined `respond_to?`, and also 1.8 which does not provide
|
|
25
|
+
# method handles for missing methods even if `respond_to?` is correct.
|
|
26
|
+
@original_method ||=
|
|
27
|
+
@method_stasher.original_method ||
|
|
28
|
+
@proxy.method_handle_for(method_name) ||
|
|
29
|
+
Proc.new do |*args, &block|
|
|
30
|
+
@object.__send__(:method_missing, @method_name, *args, &block)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
alias_method :save_original_method!, :original_method
|
|
35
|
+
|
|
36
|
+
# @private
|
|
37
|
+
def visibility
|
|
38
|
+
if TestDouble === @object
|
|
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
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @private
|
|
50
|
+
def object_singleton_class
|
|
51
|
+
class << @object; self; end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @private
|
|
55
|
+
def configure_method
|
|
56
|
+
@original_visibility = [visibility, method_name]
|
|
57
|
+
@method_stasher.stash unless @method_is_proxied
|
|
58
|
+
define_proxy_method
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @private
|
|
62
|
+
def define_proxy_method
|
|
63
|
+
return if @method_is_proxied
|
|
64
|
+
|
|
65
|
+
save_original_method!
|
|
66
|
+
|
|
67
|
+
object_singleton_class.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
|
|
68
|
+
define_method(method_name) do |*args, &block|
|
|
69
|
+
method_double.proxy_method_invoked(self, *args, &block)
|
|
70
|
+
end
|
|
71
|
+
self.__send__ visibility, method_name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@method_is_proxied = true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The implementation of the proxied method. Subclasses may override this
|
|
78
|
+
# method to perform additional operations.
|
|
79
|
+
#
|
|
80
|
+
# @private
|
|
81
|
+
def proxy_method_invoked(obj, *args, &block)
|
|
82
|
+
@proxy.message_received method_name, *args, &block
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @private
|
|
86
|
+
def restore_original_method
|
|
87
|
+
return unless @method_is_proxied
|
|
88
|
+
|
|
89
|
+
object_singleton_class.__send__(:remove_method, @method_name)
|
|
90
|
+
if @method_stasher.method_is_stashed?
|
|
91
|
+
@method_stasher.restore
|
|
92
|
+
end
|
|
93
|
+
restore_original_visibility
|
|
94
|
+
|
|
95
|
+
@method_is_proxied = false
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @private
|
|
99
|
+
def restore_original_visibility
|
|
100
|
+
return unless @original_visibility &&
|
|
101
|
+
(object_singleton_class.method_defined?(@method_name) ||
|
|
102
|
+
object_singleton_class.private_method_defined?(@method_name))
|
|
103
|
+
|
|
104
|
+
object_singleton_class.__send__(*@original_visibility)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @private
|
|
108
|
+
def verify
|
|
109
|
+
expectations.each {|e| e.verify_messages_received}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @private
|
|
113
|
+
def reset
|
|
114
|
+
restore_original_method
|
|
115
|
+
clear
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @private
|
|
119
|
+
def clear
|
|
120
|
+
expectations.clear
|
|
121
|
+
stubs.clear
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The type of message expectation to create has been extracted to its own
|
|
125
|
+
# method so that subclasses can override it.
|
|
126
|
+
#
|
|
127
|
+
# @private
|
|
128
|
+
def message_expectation_class
|
|
129
|
+
MessageExpectation
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @private
|
|
133
|
+
def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
|
|
134
|
+
configure_method
|
|
135
|
+
expectation = message_expectation_class.new(error_generator, expectation_ordering,
|
|
136
|
+
expected_from, self, 1, opts, &implementation)
|
|
137
|
+
expectations << expectation
|
|
138
|
+
expectation
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @private
|
|
142
|
+
def build_expectation(error_generator, expectation_ordering)
|
|
143
|
+
expected_from = IGNORED_BACKTRACE_LINE
|
|
144
|
+
message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @private
|
|
148
|
+
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
|
|
149
|
+
configure_method
|
|
150
|
+
stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
|
|
151
|
+
self, :any, opts, &implementation)
|
|
152
|
+
stubs.unshift stub
|
|
153
|
+
stub
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# A simple stub can only return a concrete value for a message, and
|
|
157
|
+
# cannot match on arguments. It is used as an optimization over
|
|
158
|
+
# `add_stub` / `add_expectation` where it is known in advance that this
|
|
159
|
+
# is all that will be required of a stub, such as when passing attributes
|
|
160
|
+
# to the `double` example method. They do not stash or restore existing method
|
|
161
|
+
# definitions.
|
|
162
|
+
#
|
|
163
|
+
# @private
|
|
164
|
+
def add_simple_stub(method_name, response)
|
|
165
|
+
setup_simple_method_double method_name, response, stubs
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @private
|
|
169
|
+
def add_simple_expectation(method_name, response, error_generator, backtrace_line)
|
|
170
|
+
setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @private
|
|
174
|
+
def setup_simple_method_double(method_name, response, collection, error_generator = nil, backtrace_line = nil)
|
|
175
|
+
define_proxy_method
|
|
176
|
+
|
|
177
|
+
me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
|
|
178
|
+
collection.unshift me
|
|
179
|
+
me
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @private
|
|
183
|
+
def add_default_stub(*args, &implementation)
|
|
184
|
+
return if stubs.any?
|
|
185
|
+
add_stub(*args, &implementation)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# @private
|
|
189
|
+
def remove_stub
|
|
190
|
+
raise_method_not_stubbed_error if stubs.empty?
|
|
191
|
+
expectations.empty? ? reset : stubs.clear
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# @private
|
|
195
|
+
def remove_single_stub(stub)
|
|
196
|
+
stubs.delete(stub)
|
|
197
|
+
restore_original_method if stubs.empty? && expectations.empty?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @private
|
|
201
|
+
def raise_method_not_stubbed_error
|
|
202
|
+
raise MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# @private
|
|
206
|
+
IGNORED_BACKTRACE_LINE = 'this backtrace line is ignored'
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Mocks
|
|
3
|
+
# Represents a method on a module that may or may not be defined.
|
|
4
|
+
#
|
|
5
|
+
# @private
|
|
6
|
+
class MethodReference
|
|
7
|
+
def initialize(module_reference, method_name)
|
|
8
|
+
@module_reference = module_reference
|
|
9
|
+
@method_name = method_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# A method is implemented if sending the message does not result in
|
|
13
|
+
# a `NoMethodError`. It might be dynamically implemented by
|
|
14
|
+
# `method_missing`.
|
|
15
|
+
def implemented?
|
|
16
|
+
@module_reference.when_loaded do |m|
|
|
17
|
+
method_implemented?(m)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# A method is defined if we are able to get a `Method` object for it.
|
|
22
|
+
# In that case, we can assert against metadata like the arity.
|
|
23
|
+
def defined?
|
|
24
|
+
@module_reference.when_loaded do |m|
|
|
25
|
+
method_defined?(m)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def when_defined
|
|
30
|
+
if original = original_method
|
|
31
|
+
yield original
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Yields to the block if the method is not implemented.
|
|
36
|
+
def when_unimplemented
|
|
37
|
+
yield unless implemented?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
def original_method
|
|
42
|
+
@module_reference.when_loaded do |m|
|
|
43
|
+
self.defined? && find_method(m)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @private
|
|
49
|
+
class InstanceMethodReference < MethodReference
|
|
50
|
+
private
|
|
51
|
+
def method_implemented?(m)
|
|
52
|
+
m.method_defined?(@method_name)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Ideally, we'd use `respond_to?` for `method_implemented?` but we need a
|
|
56
|
+
# reference to an instance to do that and we don't have one. Note that
|
|
57
|
+
# we may get false negatives: if the method is implemented via
|
|
58
|
+
# `method_missing`, we'll return `false` even though it meets our
|
|
59
|
+
# definition of "implemented". However, it's the best we can do.
|
|
60
|
+
alias method_defined? method_implemented?
|
|
61
|
+
|
|
62
|
+
# works around the fact that repeated calls for method parameters will
|
|
63
|
+
# falsely return empty arrays on JRuby in certain circumstances, this
|
|
64
|
+
# is necessary here because we can't dup/clone UnboundMethods.
|
|
65
|
+
#
|
|
66
|
+
# This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
|
|
67
|
+
# https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
|
|
68
|
+
if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
|
|
69
|
+
def find_method(m)
|
|
70
|
+
m.dup.instance_method(@method_name)
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
def find_method(m)
|
|
74
|
+
m.instance_method(@method_name)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @private
|
|
80
|
+
class ObjectMethodReference < MethodReference
|
|
81
|
+
private
|
|
82
|
+
def method_implemented?(m)
|
|
83
|
+
m.respond_to?(@method_name)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def method_defined?(m)
|
|
87
|
+
(class << m; self; end).method_defined?(@method_name)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def find_method(m)
|
|
91
|
+
m.method(@method_name)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Mocks
|
|
3
|
+
# Provides recursive constant lookup methods useful for
|
|
4
|
+
# constant stubbing.
|
|
5
|
+
# @api private
|
|
6
|
+
module RecursiveConstMethods
|
|
7
|
+
# We only want to consider constants that are defined directly on a
|
|
8
|
+
# particular module, and not include top-level/inherited constants.
|
|
9
|
+
# Unfortunately, the constant API changed between 1.8 and 1.9, so
|
|
10
|
+
# we need to conditionally define methods to ignore the top-level/inherited
|
|
11
|
+
# constants.
|
|
12
|
+
#
|
|
13
|
+
# Given:
|
|
14
|
+
# class A; B = 1; end
|
|
15
|
+
# class C < A; end
|
|
16
|
+
#
|
|
17
|
+
# On 1.8:
|
|
18
|
+
# - C.const_get("Hash") # => ::Hash
|
|
19
|
+
# - C.const_defined?("Hash") # => false
|
|
20
|
+
# - C.constants # => ["B"]
|
|
21
|
+
# - None of these methods accept the extra `inherit` argument
|
|
22
|
+
# On 1.9:
|
|
23
|
+
# - C.const_get("Hash") # => ::Hash
|
|
24
|
+
# - C.const_defined?("Hash") # => true
|
|
25
|
+
# - C.const_get("Hash", false) # => raises NameError
|
|
26
|
+
# - C.const_defined?("Hash", false) # => false
|
|
27
|
+
# - C.constants # => [:B]
|
|
28
|
+
# - C.constants(false) #=> []
|
|
29
|
+
if Module.method(:const_defined?).arity == 1
|
|
30
|
+
def const_defined_on?(mod, const_name)
|
|
31
|
+
mod.const_defined?(const_name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get_const_defined_on(mod, const_name)
|
|
35
|
+
if const_defined_on?(mod, const_name)
|
|
36
|
+
return mod.const_get(const_name)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def constants_defined_on(mod)
|
|
43
|
+
mod.constants.select { |c| const_defined_on?(mod, c) }
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
def const_defined_on?(mod, const_name)
|
|
47
|
+
mod.const_defined?(const_name, false)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def get_const_defined_on(mod, const_name)
|
|
51
|
+
mod.const_get(const_name, false)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def constants_defined_on(mod)
|
|
55
|
+
mod.constants(false)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def recursive_const_get(const_name)
|
|
60
|
+
normalize_const_name(const_name).split('::').inject(Object) do |mod, name|
|
|
61
|
+
get_const_defined_on(mod, name)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def recursive_const_defined?(const_name)
|
|
66
|
+
normalize_const_name(const_name).split('::').inject([Object, '']) do |(mod, full_name), name|
|
|
67
|
+
yield(full_name, name) if block_given? && !(Module === mod)
|
|
68
|
+
return false unless const_defined_on?(mod, name)
|
|
69
|
+
[get_const_defined_on(mod, name), [mod, name].join('::')]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def normalize_const_name(const_name)
|
|
74
|
+
const_name.sub(/\A::/, '')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Provides information about constants that may (or may not)
|
|
79
|
+
# have been mutated by rspec-mocks.
|
|
80
|
+
class Constant
|
|
81
|
+
extend RecursiveConstMethods
|
|
82
|
+
|
|
83
|
+
# @api private
|
|
84
|
+
def initialize(name)
|
|
85
|
+
@name = name
|
|
86
|
+
@previously_defined = false
|
|
87
|
+
@stubbed = false
|
|
88
|
+
@hidden = false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [String] The fully qualified name of the constant.
|
|
92
|
+
attr_reader :name
|
|
93
|
+
|
|
94
|
+
# @return [Object, nil] The original value (e.g. before it
|
|
95
|
+
# was mutated by rspec-mocks) of the constant, or
|
|
96
|
+
# nil if the constant was not previously defined.
|
|
97
|
+
attr_accessor :original_value
|
|
98
|
+
|
|
99
|
+
# @api private
|
|
100
|
+
attr_writer :previously_defined, :stubbed, :hidden
|
|
101
|
+
|
|
102
|
+
# @return [Boolean] Whether or not the constant was defined
|
|
103
|
+
# before the current example.
|
|
104
|
+
def previously_defined?
|
|
105
|
+
@previously_defined
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @return [Boolean] Whether or not rspec-mocks has mutated
|
|
109
|
+
# (stubbed or hidden) this constant.
|
|
110
|
+
def mutated?
|
|
111
|
+
@stubbed || @hidden
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @return [Boolean] Whether or not rspec-mocks has stubbed
|
|
115
|
+
# this constant.
|
|
116
|
+
def stubbed?
|
|
117
|
+
@stubbed
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @return [Boolean] Whether or not rspec-mocks has hidden
|
|
121
|
+
# this constant.
|
|
122
|
+
def hidden?
|
|
123
|
+
@hidden
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def to_s
|
|
127
|
+
"#<#{self.class.name} #{name}>"
|
|
128
|
+
end
|
|
129
|
+
alias inspect to_s
|
|
130
|
+
|
|
131
|
+
# @api private
|
|
132
|
+
def self.unmutated(name)
|
|
133
|
+
const = new(name)
|
|
134
|
+
const.previously_defined = recursive_const_defined?(name)
|
|
135
|
+
const.stubbed = false
|
|
136
|
+
const.hidden = false
|
|
137
|
+
const.original_value = recursive_const_get(name) if const.previously_defined?
|
|
138
|
+
|
|
139
|
+
const
|
|
140
|
+
end
|
|
141
|
+
private_class_method :unmutated
|
|
142
|
+
|
|
143
|
+
# Queries rspec-mocks to find out information about the named constant.
|
|
144
|
+
#
|
|
145
|
+
# @param [String] name the name of the constant
|
|
146
|
+
# @return [Constant] an object contaning information about the named
|
|
147
|
+
# constant.
|
|
148
|
+
def self.original(name)
|
|
149
|
+
mutator = ConstantMutator.find(name)
|
|
150
|
+
mutator ? mutator.to_constant : unmutated(name)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Provides a means to stub constants.
|
|
155
|
+
class ConstantMutator
|
|
156
|
+
extend RecursiveConstMethods
|
|
157
|
+
|
|
158
|
+
# Stubs a constant.
|
|
159
|
+
#
|
|
160
|
+
# @param (see ExampleMethods#stub_const)
|
|
161
|
+
# @option (see ExampleMethods#stub_const)
|
|
162
|
+
# @return (see ExampleMethods#stub_const)
|
|
163
|
+
#
|
|
164
|
+
# @see ExampleMethods#stub_const
|
|
165
|
+
# @note It's recommended that you use `stub_const` in your
|
|
166
|
+
# examples. This is an alternate public API that is provided
|
|
167
|
+
# so you can stub constants in other contexts (e.g. helper
|
|
168
|
+
# classes).
|
|
169
|
+
def self.stub(constant_name, value, options = {})
|
|
170
|
+
mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
|
|
171
|
+
DefinedConstantReplacer
|
|
172
|
+
else
|
|
173
|
+
UndefinedConstantSetter
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
mutate(mutator.new(constant_name, value, options.fetch(
|
|
177
|
+
:transfer_nested_constants,
|
|
178
|
+
RSpec::Mocks.configuration.transfer_nested_constants?
|
|
179
|
+
)))
|
|
180
|
+
value
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Hides a constant.
|
|
184
|
+
#
|
|
185
|
+
# @param (see ExampleMethods#hide_const)
|
|
186
|
+
#
|
|
187
|
+
# @see ExampleMethods#hide_const
|
|
188
|
+
# @note It's recommended that you use `hide_const` in your
|
|
189
|
+
# examples. This is an alternate public API that is provided
|
|
190
|
+
# so you can hide constants in other contexts (e.g. helper
|
|
191
|
+
# classes).
|
|
192
|
+
def self.hide(constant_name)
|
|
193
|
+
return unless recursive_const_defined?(constant_name)
|
|
194
|
+
|
|
195
|
+
mutate(ConstantHider.new(constant_name, nil, { }))
|
|
196
|
+
nil
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Contains common functionality used by all of the constant mutators.
|
|
200
|
+
#
|
|
201
|
+
# @api private
|
|
202
|
+
class BaseMutator
|
|
203
|
+
include RecursiveConstMethods
|
|
204
|
+
|
|
205
|
+
attr_reader :original_value, :full_constant_name
|
|
206
|
+
|
|
207
|
+
def initialize(full_constant_name, mutated_value, transfer_nested_constants)
|
|
208
|
+
@full_constant_name = normalize_const_name(full_constant_name)
|
|
209
|
+
@mutated_value = mutated_value
|
|
210
|
+
@transfer_nested_constants = transfer_nested_constants
|
|
211
|
+
@context_parts = @full_constant_name.split('::')
|
|
212
|
+
@const_name = @context_parts.pop
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def to_constant
|
|
216
|
+
const = Constant.new(full_constant_name)
|
|
217
|
+
const.original_value = original_value
|
|
218
|
+
|
|
219
|
+
const
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Hides a defined constant for the duration of an example.
|
|
224
|
+
#
|
|
225
|
+
# @api private
|
|
226
|
+
class ConstantHider < BaseMutator
|
|
227
|
+
def mutate
|
|
228
|
+
@context = recursive_const_get(@context_parts.join('::'))
|
|
229
|
+
@original_value = get_const_defined_on(@context, @const_name)
|
|
230
|
+
|
|
231
|
+
@context.__send__(:remove_const, @const_name)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def to_constant
|
|
235
|
+
const = super
|
|
236
|
+
const.hidden = true
|
|
237
|
+
const.previously_defined = true
|
|
238
|
+
|
|
239
|
+
const
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def rspec_reset
|
|
243
|
+
@context.const_set(@const_name, @original_value)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Replaces a defined constant for the duration of an example.
|
|
248
|
+
#
|
|
249
|
+
# @api private
|
|
250
|
+
class DefinedConstantReplacer < BaseMutator
|
|
251
|
+
def mutate
|
|
252
|
+
@context = recursive_const_get(@context_parts.join('::'))
|
|
253
|
+
@original_value = get_const_defined_on(@context, @const_name)
|
|
254
|
+
|
|
255
|
+
constants_to_transfer = verify_constants_to_transfer!
|
|
256
|
+
|
|
257
|
+
@context.__send__(:remove_const, @const_name)
|
|
258
|
+
@context.const_set(@const_name, @mutated_value)
|
|
259
|
+
|
|
260
|
+
transfer_nested_constants(constants_to_transfer)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def to_constant
|
|
264
|
+
const = super
|
|
265
|
+
const.stubbed = true
|
|
266
|
+
const.previously_defined = true
|
|
267
|
+
|
|
268
|
+
const
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def rspec_reset
|
|
272
|
+
@context.__send__(:remove_const, @const_name)
|
|
273
|
+
@context.const_set(@const_name, @original_value)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def transfer_nested_constants(constants)
|
|
277
|
+
constants.each do |const|
|
|
278
|
+
@mutated_value.const_set(const, get_const_defined_on(original_value, const))
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def verify_constants_to_transfer!
|
|
283
|
+
return [] unless @transfer_nested_constants
|
|
284
|
+
|
|
285
|
+
{ @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
|
|
286
|
+
unless value.respond_to?(:constants)
|
|
287
|
+
raise ArgumentError,
|
|
288
|
+
"Cannot transfer nested constants for #{@full_constant_name} " +
|
|
289
|
+
"since #{description} is not a class or module and only classes " +
|
|
290
|
+
"and modules support nested constants."
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
if Array === @transfer_nested_constants
|
|
295
|
+
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
|
|
296
|
+
undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
|
|
297
|
+
|
|
298
|
+
if undefined_constants.any?
|
|
299
|
+
available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
|
|
300
|
+
raise ArgumentError,
|
|
301
|
+
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
|
|
302
|
+
"for #{@full_constant_name} since they are not defined. Did you mean " +
|
|
303
|
+
"#{available_constants.join(' or ')}?"
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
@transfer_nested_constants
|
|
307
|
+
else
|
|
308
|
+
constants_defined_on(@original_value)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Sets an undefined constant for the duration of an example.
|
|
314
|
+
#
|
|
315
|
+
# @api private
|
|
316
|
+
class UndefinedConstantSetter < BaseMutator
|
|
317
|
+
def mutate
|
|
318
|
+
remaining_parts = @context_parts.dup
|
|
319
|
+
@deepest_defined_const = @context_parts.inject(Object) do |klass, name|
|
|
320
|
+
break klass unless const_defined_on?(klass, name)
|
|
321
|
+
remaining_parts.shift
|
|
322
|
+
get_const_defined_on(klass, name)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
|
|
326
|
+
klass.const_set(name, Module.new)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
@const_to_remove = remaining_parts.first || @const_name
|
|
330
|
+
context.const_set(@const_name, @mutated_value)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def to_constant
|
|
334
|
+
const = super
|
|
335
|
+
const.stubbed = true
|
|
336
|
+
const.previously_defined = false
|
|
337
|
+
|
|
338
|
+
const
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def rspec_reset
|
|
342
|
+
@deepest_defined_const.__send__(:remove_const, @const_to_remove)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Uses the mutator to mutate (stub or hide) a constant. Ensures that
|
|
347
|
+
# the mutator is correctly registered so it can be backed out at the end
|
|
348
|
+
# of the test.
|
|
349
|
+
#
|
|
350
|
+
# @api private
|
|
351
|
+
def self.mutate(mutator)
|
|
352
|
+
register_mutator(mutator)
|
|
353
|
+
mutator.mutate
|
|
354
|
+
end
|
|
355
|
+
|
|
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
|
+
# Used internally by the constant stubbing to raise a helpful
|
|
387
|
+
# error when a constant like "A::B::C" is stubbed and A::B is
|
|
388
|
+
# not a module (and thus, it's impossible to define "A::B::C"
|
|
389
|
+
# since only modules can have nested constants).
|
|
390
|
+
#
|
|
391
|
+
# @api private
|
|
392
|
+
def self.raise_on_invalid_const
|
|
393
|
+
lambda do |const_name, failed_name|
|
|
394
|
+
raise "Cannot stub constant #{failed_name} on #{const_name} " +
|
|
395
|
+
"since #{const_name} is not a module."
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Keeps backwards compatibility since we had released an rspec-mocks that
|
|
401
|
+
# only supported stubbing. Later, we released the hide_const feature and
|
|
402
|
+
# decided that the term "mutator" was a better term to wrap up the concept
|
|
403
|
+
# of both stubbing and hiding.
|
|
404
|
+
ConstantStubber = ConstantMutator
|
|
405
|
+
end
|
|
406
|
+
end
|