File: double_definition.rb

package info (click to toggle)
ruby-rr 3.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,424 kB
  • sloc: ruby: 11,405; makefile: 7
file content (396 lines) | stat: -rw-r--r-- 13,978 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
module RR
  module DoubleDefinitions
    class DoubleDefinition #:nodoc:
      ORIGINAL_METHOD = Object.new

      attr_accessor(
        :argument_expectation,
        :times_matcher,
        :implementation,
        :after_call_proc,
        :yields_value,
        :double,
        :double_definition_create
      )

      include Space::Reader

      def initialize(double_definition_create)
        @implementation = nil
        @argument_expectation = nil
        @times_matcher = nil
        @after_call_proc = nil
        @yields_value = nil
        @double_definition_create = double_definition_create
        @ordered = false
        @verbose = false
        @verify_method_signature = false
      end

      def subject
        double_definition_create.subject
      end

      def root_subject
        double_definition_create.root_subject
      end

      module ArgumentDefinitionConstructionMethods
        if KeywordArguments.fully_supported?
          # Double#with sets the expectation that the Double will receive
          # the passed in arguments.
          #
          # Passing in a block sets the return value.
          #
          #   mock(subject).method_name.with(1, 2) {:return_value}
          def with(*args, **kwargs, &return_value_block)
            @argument_expectation =
              Expectations::ArgumentEqualityExpectation.new(args, kwargs)
            install_method_callback return_value_block
            self
          end
        else
          def with(*args, &return_value_block)
            @argument_expectation =
              Expectations::ArgumentEqualityExpectation.new(args, {})
            install_method_callback return_value_block
            self
          end
        end

        # Double#with_any_args sets the expectation that the Double can receive
        # any arguments.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.with_any_args {:return_value}
        def with_any_args(&return_value_block)
          @argument_expectation = Expectations::AnyArgumentExpectation.new
          install_method_callback return_value_block
          self
        end

        # Double#with_no_args sets the expectation that the Double will receive
        # no arguments.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.with_no_args {:return_value}
        def with_no_args(&return_value_block)
          @argument_expectation =
            Expectations::ArgumentEqualityExpectation.new([], {})
          install_method_callback return_value_block
          self
        end
      end
      include ArgumentDefinitionConstructionMethods

      module TimesDefinitionConstructionMethods
        # Double#never sets the expectation that the Double will never be
        # called.
        #
        # This method does not accept a block because it will never be called.
        #
        #   mock(subject).method_name.never
        def never
          @times_matcher = TimesCalledMatchers::NeverMatcher.new
          self
        end

        # Double#once sets the expectation that the Double will be called
        # 1 time.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.once {:return_value}
        def once(&return_value_block)
          @times_matcher = TimesCalledMatchers::IntegerMatcher.new(1)
          install_method_callback return_value_block
          self
        end

        # Double#twice sets the expectation that the Double will be called
        # 2 times.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.twice {:return_value}
        def twice(&return_value_block)
          @times_matcher = TimesCalledMatchers::IntegerMatcher.new(2)
          install_method_callback return_value_block
          self
        end

        # Double#at_least sets the expectation that the Double
        # will be called at least n times.
        # It works by creating a TimesCalledExpectation.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.at_least(4) {:return_value}
        def at_least(number, &return_value_block)
          @times_matcher = TimesCalledMatchers::AtLeastMatcher.new(number)
          install_method_callback return_value_block
          self
        end

        # Double#at_most allows sets the expectation that the Double
        # will be called at most n times.
        # It works by creating a TimesCalledExpectation.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.at_most(4) {:return_value}
        def at_most(number, &return_value_block)
          @times_matcher = TimesCalledMatchers::AtMostMatcher.new(number)
          install_method_callback return_value_block
          self
        end

        # Double#any_number_of_times sets an that the Double will be called
        # any number of times. This effectively removes the times called expectation
        # from the Doublen
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.any_number_of_times
        def any_number_of_times(&return_value_block)
          @times_matcher = TimesCalledMatchers::AnyTimesMatcher.new
          install_method_callback return_value_block
          self
        end
        alias_method :any_times, :any_number_of_times

        # Double#times creates an TimesCalledExpectation of the passed
        # in number.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.times(4) {:return_value}
        def times(matcher_value, &return_value_block)
          @times_matcher = TimesCalledMatchers::TimesCalledMatcher.create(matcher_value)
          install_method_callback return_value_block
          self
        end
      end
      include TimesDefinitionConstructionMethods

      module DefinitionConstructionMethods
        # Double#ordered sets the Double to have an ordered
        # expectation.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.ordered {return_value}
        def ordered(&return_value_block)
          raise(
            Errors::DoubleDefinitionError,
            "Double Definitions must have a dedicated Double to be ordered. " <<
            "For example, using instance_of does not allow ordered to be used. " <<
            "proxy the class's #new method instead."
          ) unless @double
          @ordered = true
          space.register_ordered_double(@double)
          install_method_callback return_value_block
          DoubleDefinitionCreateBlankSlate.new(double_definition_create)
        end
        alias_method :then, :ordered

        # Double#yields sets the Double to invoke a passed in block when
        # the Double is called.
        # An Expection will be raised if no block is passed in when the
        # Double is called.
        #
        # Passing in a block sets the return value.
        #
        #   mock(subject).method_name.yields(yield_arg1, yield_arg2) {return_value}
        #   subject.method_name {|yield_arg1, yield_arg2|}
        def yields(*args, &return_value_block)
          @yields_value = args
          install_method_callback return_value_block
          self
        end

        # Double#after_call creates a callback that occurs after call
        # is called. The passed in block receives the return value of
        # the Double being called.
        # An Expection will be raised if no block is passed in.
        #
        #   mock(subject).method_name {return_value}.after_call {|return_value|}
        #   subject.method_name # return_value
        #
        # This feature is built into proxies.
        #   mock.proxy(User).find('1') {|user| mock(user).valid? {false}}
        def after_call(&after_call_proc)
          raise ArgumentError, "after_call expects a block" unless after_call_proc
          @after_call_proc = after_call_proc
          self
        end

        # Double#verbose sets the Double to print out each method call it receives.
        #
        # Passing in a block sets the return value
        def verbose(&after_call_proc)
          @verbose = true
          @after_call_proc = after_call_proc
          self
        end

        # Double#returns accepts an argument value or a block.
        # It will raise an ArgumentError if both are passed in.
        #
        # Passing in a block causes Double to return the return value of
        # the passed in block.
        #
        # Passing in an argument causes Double to return the argument.
        def returns(*args, &implementation)
          if !args.empty? && implementation
            raise ArgumentError, "returns cannot accept both an argument and a block"
          end
          if implementation
            install_method_callback implementation
          else
            install_method_callback(lambda do |*lambda_args|
              args.first
            end)
          end
          self
        end

        def implemented_by_original_method
          implemented_by ORIGINAL_METHOD
          self
        end

        # Double#implemented_by sets the implementation of the Double.
        # This method takes a Proc or a Method. Passing in a Method allows
        # the Double to accept blocks.
        #
        #   obj = Object.new
        #   def obj.foobar
        #     yield(1)
        #   end
        #   mock(obj).method_name.implemented_by(obj.method(:foobar))
        def implemented_by(implementation)
          @implementation = implementation
          self
        end

        def verify_method_signature
          @verify_method_signature = true
          self
        end
        alias_method :strong, :verify_method_signature

      protected
        def install_method_callback(block)
          if block
            if implementation_is_original_method?
              after_call(&block)
            else
              implemented_by block
            end
          end
        end
      end
      include DefinitionConstructionMethods

      module StateQueryMethods
        # Double#ordered? returns true when the Double is ordered.
        #
        #   mock(subject).method_name.ordered?
        def ordered?
          @ordered
        end

        # Double#verbose? returns true when verbose has been called on it. It returns
        # true when the double is set to print each method call it receives.
        def verbose?
          @verbose ? true : false
        end

        def exact_match?(arguments, keyword_arguments)
          unless @argument_expectation
            raise RR::Errors.build_error(:DoubleDefinitionError, "#argument_expectation must be defined on #{inspect}")
          end
          @argument_expectation.exact_match?(arguments, keyword_arguments)
        end

        def wildcard_match?(arguments, keyword_arguments)
          unless @argument_expectation
            raise RR::Errors.build_error(:DoubleDefinitionError, "#argument_expectation must be defined on #{inspect}")
          end
          @argument_expectation.wildcard_match?(arguments, keyword_arguments)
        end

        def terminal?
          unless @times_matcher
            raise RR::Errors.build_error(:DoubleDefinitionError, "#argument_expectation must be defined on #{inspect}")
          end
          @times_matcher.terminal?
        end

        def expected_arguments
          if argument_expectation
            argument_expectation.expected_arguments
          else
            []
          end
        end

        def expected_keyword_arguments
          if argument_expectation
            argument_expectation.expected_keyword_arguments
          else
            {}
          end
        end

        def implementation_is_original_method?
          implementation_strategy.is_a?(Strategies::Implementation::Proxy)
        end

        def verify_method_signature?
          !!@verify_method_signature
        end
        alias_method :strong?, :verify_method_signature?

      protected
        def implementation_strategy
          double_definition_create.implementation_strategy
        end
      end
      include StateQueryMethods

      include ::RR::DoubleDefinitions::Strategies::StrategyMethods

      def mock(subject=DoubleDefinitionCreate::NO_SUBJECT, method_name=nil, &definition_eval_block)
        ChildDoubleDefinitionCreate.new(self).mock(subject, method_name, &definition_eval_block)
      end

      def stub(subject=DoubleDefinitionCreate::NO_SUBJECT, method_name=nil, &definition_eval_block)
        ChildDoubleDefinitionCreate.new(self).stub(subject, method_name, &definition_eval_block)
      end

      def dont_allow(subject=DoubleDefinitionCreate::NO_SUBJECT, method_name=nil, &definition_eval_block)
        ChildDoubleDefinitionCreate.new(self).dont_allow(subject, method_name, &definition_eval_block)
      end

      def proxy(subject=DoubleDefinitionCreate::NO_SUBJECT, method_name=nil, &definition_eval_block)
        ChildDoubleDefinitionCreate.new(self).proxy(subject, method_name, &definition_eval_block)
      end

      def strong(subject=DoubleDefinitionCreate::NO_SUBJECT, method_name=nil, &definition_eval_block)
        ChildDoubleDefinitionCreate.new(self).strong(subject, method_name, &definition_eval_block)
      end

      def any_instance_of(subject=DoubleDefinitionCreate::NO_SUBJECT, method_name=nil, &definition_eval_block)
        ChildDoubleDefinitionCreate.new(self).any_instance_of(subject, method_name, &definition_eval_block)
      end

      def instance_of(subject=DoubleDefinitionCreate::NO_SUBJECT, method_name=nil, &definition_eval_block)
        ChildDoubleDefinitionCreate.new(self).instance_of(subject, method_name, &definition_eval_block)
      end
    end
  end
end