File: core.rb

package info (click to toggle)
ruby-flexmock 3.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 836 kB
  • sloc: ruby: 7,572; makefile: 6
file content (316 lines) | stat: -rw-r--r-- 9,169 bytes parent folder | download
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
#!/usr/bin/env ruby

#---
# Copyright 2003-2013 by Jim Weirich (jim.weirich@gmail.com).
# All rights reserved.
#
# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#+++

require 'flexmock/errors'
require 'flexmock/ordering'
require 'flexmock/argument_matching'
require 'flexmock/explicit_needed'
require 'flexmock/class_extensions'
require 'flexmock/expectation_builder'
require 'flexmock/call_validator'
require 'flexmock/call_record'

# FlexMock is a flexible mock object framework for creating and using
# test doubles (mocks, stubs and spies).
#
# Basic Usage:
#
#   m = flexmock("name")
#   m.should_receive(:upcase).with("stuff").
#     and_return("STUFF")
#   m.should_receive(:downcase).with(String).
#     and_return { |s| s.downcase }.once
#
# With Test::Unit Integration:
#
#   class TestSomething < Test::Unit::TestCase
#     def test_something
#       m = flexmock("name")
#       m.should_receive(:hi).and_return("Hello")
#       m.hi
#     end
#   end
#
# Note: Also, if you override +teardown+, make sure you call +super+.
#
class FlexMock
  include Ordering

  attr_reader :flexmock_name
  attr_reader :flexmock_container_stack

  class << self
    attr_accessor :partials_are_based
    attr_accessor :partials_verify_signatures
  end
  self.partials_are_based = false
  self.partials_verify_signatures = false

  # Null object for {#parent_mock}
  class NullParentMock
    def flexmock_expectations_for(sym)
    end
  end

  # Create a FlexMock object with the given name.  The name is used in
  # error messages.  If no container is given, create a new, one-off
  # container for this mock.
  def initialize(name="unknown", container=nil, parent: nil)
    @flexmock_name = name
    @flexmock_closed = false
    @flexmock_container_stack = Array.new
    @expectations = Hash.new
    @verified = false
    @calls = []
    @base_class = nil
    if parent
      @ignore_missing = parent.ignore_missing?
      @parent_mock = parent
    else
      @ignore_missing = false
      @parent_mock = NullParentMock.new
    end
    container = UseContainer.new if container.nil?
    container.flexmock_remember(self)
  end

  def ignore_missing?
    @ignore_missing
  end

  def flexmock_container
    flexmock_container_stack.last
  end

  def push_flexmock_container(container)
    flexmock_container_stack.push(container)
  end

  def pop_flexmock_container
    flexmock_container_stack.pop
  end

  # Return the inspection string for a mock.
  def inspect
    "<FlexMock:#{flexmock_name}>"
  end

  # Verify that each method that had an explicit expected count was
  # actually called that many times.
  def flexmock_verify
    return if @verified
    @verified = true
    flexmock_wrap do
      @expectations.each do |sym, handler|
        handler.flexmock_verify
      end
    end
  end

  # Teardown and infrastructure setup for this mock.
  def flexmock_teardown
    @flexmock_closed = true
  end

  def flexmock_closed?
    @flexmock_closed
  end

  # Ignore all undefined (missing) method calls.
  def should_ignore_missing
    @ignore_missing = true
    self
  end
  alias mock_ignore_missing should_ignore_missing

  def by_default
    @last_expectation.by_default
    self
  end

  # Handle missing methods by attempting to look up a handler.
  def method_missing(sym, *args, **kw, &block)
    FlexMock.verify_mocking_allowed!

    call_record = CallRecord.new(sym, args, kw, block)
    @calls << call_record
    flexmock_wrap do
      if flexmock_closed?
        FlexMock.undefined
      elsif exp = flexmock_expectations_for(sym)
        exp.call(args, kw, block, call_record)
      elsif @base_class && @base_class.flexmock_defined?(sym)
        FlexMock.undefined
      elsif @ignore_missing
        FlexMock.undefined
      else
        super(sym, *args, **kw, &block)
      end
    end
  end

  # Save the original definition of respond_to? for use a bit later.
  alias flexmock_respond_to? respond_to?

  # Override the built-in respond_to? to include the mocked methods.
  def respond_to?(sym, *args)
    super || (@expectations[sym] ? true : @ignore_missing)
  end

  # Find the mock expectation for method sym and arguments.
  def flexmock_find_expectation(method_name, *args, **kw, &block) # :nodoc:
    if exp = flexmock_expectations_for(method_name)
      exp.find_expectation(args, kw, block)
    end
  end

  # Return the expectation director for a method name.
  def flexmock_expectations_for(method_name) # :nodoc:
    @expectations[method_name] || @parent_mock.flexmock_expectations_for(method_name)
  end

  def flexmock_base_class
    @base_class
  end

  def flexmock_based_on(base_class)
    @base_class = base_class
    if base_class <= Kernel
      if self.class != base_class
        should_receive(:class => base_class)
        should_receive(:kind_of?).and_return { |against| base_class <= against }
      end
    end
  end

  CALL_VALIDATOR = CallValidator.new

  # True if the mock received the given method and arguments.
  def flexmock_received?(method_name, args, kw, options = {})
    CALL_VALIDATOR.received?(@calls, method_name, args, kw, options)
  end

  # Return the list of calls made on this mock. Used in formatting
  # error messages.
  def flexmock_calls
    @calls
  end

  # Invocke the original non-mocked functionality for the given
  # symbol.
  def flexmock_invoke_original(method_name, args, kw = {}, orig_block = nil)
    return FlexMock.undefined
  end

  # Override the built-in +method+ to include the mocked methods.
  def method(method_name)
    if (expectations = flexmock_expectations_for(method_name))
      ->(*args, **kw, &block) { expectations.call(args, kw, block) }
    else
      super
    end
  rescue NameError => ex
    if ignore_missing?
      proc { FlexMock.undefined }
    else
      raise ex
    end
  end

  # :call-seq:
  #    mock.should_receive(:method_name)
  #    mock.should_receive(:method1, method2, ...)
  #    mock.should_receive(:meth1 => result1, :meth2 => result2, ...)
  #
  # Declare that the mock object should receive a message with the given name.
  #
  # If more than one method name is given, then the mock object should expect
  # to receive all the listed melthods.  If a hash of method name/value pairs
  # is given, then the each method will return the associated result.  Any
  # expectations applied to the result of +should_receive+ will be applied to
  # all the methods defined in the argument list.
  #
  # An expectation object for the method name is returned as the result of
  # this method.  Further expectation constraints can be added by chaining to
  # the result.
  #
  # See Expectation for a list of declarators that can be used.
  #
  def should_receive(*args, **kw)
    flexmock_define_expectation(caller, *args, **kw)
  end

  ON_RUBY_20 = (RUBY_VERSION =~ /^2\.0\./)

  # Using +location+, define the expectations specified by +args+.
  def flexmock_define_expectation(location, *args, **kw)
    @last_expectation = EXP_BUILDER.parse_should_args(self, args, kw) do |method_name|
      exp = flexmock_expectations_for(method_name) || ExpectationDirector.new(method_name)
      @expectations[method_name] = exp
      result = Expectation.new(self, method_name, location)
      exp << result
      override_existing_method(method_name) if flexmock_respond_to?(method_name, true)

      if @base_class && !@base_class.flexmock_defined?(method_name)
        if !ON_RUBY_20 || !@base_class.ancestors.include?(Class)
          result = ExplicitNeeded.new(result, method_name, @base_class)
        end
      end
      result
    end
  end

  # Declare that the mock object should expect methods by providing a
  # recorder for the methods and having the user invoke the expected
  # methods in a block.  Further expectations may be applied the
  # result of the recording call.
  #
  # Example Usage:
  #
  #   mock.should_expect do |record|
  #     record.add(Integer, 4) { |a, b|
  #       a + b
  #     }.at_least.once
  #
  def should_expect
    yield Recorder.new(self)
  end

  private

  # Wrap a block of code so the any assertion errors are wrapped so
  # that the mock name is added to the error message .
  def flexmock_wrap(&block)
    yield
  rescue FlexMock.framework_adapter.assertion_failed_error, FlexMock.framework_adapter.check_failed_error => ex
    raise ex, "in mock '#{@flexmock_name}': #{ex.message}", ex.backtrace
  end

  # Override the existing definition of method +method_name+ in the
  # mock. Most methods depend on the method_missing trick to be
  # invoked. However, if the method already exists, it will not call
  # method_missing. This method defines a singleton method on the mock
  # to explicitly invoke the method_missing logic.
  def override_existing_method(method_name)
    sclass.class_eval <<-EOS
      def #{method_name}(*args, **kw, &block)
        method_missing(:#{method_name}, *args, **kw, &block)
      end
    EOS
  end

  # Return the singleton class of the mock object.
  def sclass
    class << self; self; end
  end
end

require 'flexmock/core_class_methods'