Class: Grift::MockMethod

Inherits:
Object
  • Object
show all
Defined in:
lib/grift/mock_method.rb,
lib/grift/mock_method/mock_executions.rb,
lib/grift/mock_method/mock_executions/mock_arguments.rb

Overview

A mock for a given class and method. This is the core of Grift. Mocking or spying usually returns a MockMethod.

Defined Under Namespace

Classes: MockExecutions

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass, method_name, watch: true) ⇒ Grift::MockMethod

A new instance of MockMethod. Should be initialized via Grift.mock or Grift.spy_on

Examples:

Grift.spy_on(MyClass, :my_method)
#=> MockMethod instance with the method being watched

Parameters:

  • klass (Class)

    the class to be mocked

  • method_name (Symbol)

    the method to be mocked

  • watch (Boolean) (defaults to: true)

    whether to start watching the method

Raises:

See Also:



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/grift/mock_method.rb', line 46

def initialize(klass, method_name, watch: true)
  if Grift.restricted_method?(klass, method_name)
    raise(Grift::Error, "Cannot mock restricted method #{method_name} for class #{klass}")
  end

  @klass = klass
  @method_name = method_name
  @true_method_cached = false
  @mock_executions = MockExecutions.new
  @cache_method_name = :"#{CACHE_METHOD_PREFIX}_#{method_name}"

  # class methods are really instance methods of the singleton class
  @class_method = klass.singleton_class.method_defined?(method_name, true) ||
    klass.singleton_class.private_method_defined?(method_name, true)

  @method_access, @inherited = method_access_definition
  raise(Grift::Error, "Cannot mock unknown method #{method_name} for class #{klass}") unless @method_access

  if class_instance.method_defined?(@cache_method_name)
    raise(Grift::Error, "Cannot mock already mocked method #{method_name} for class #{klass}")
  end

  watch_method if watch
end

Instance Attribute Details

#klassObject (readonly)

Returns the value of attribute klass.



9
10
11
# File 'lib/grift/mock_method.rb', line 9

def klass
  @klass
end

#method_accessObject (readonly)

Returns the value of attribute method_access.



9
10
11
# File 'lib/grift/mock_method.rb', line 9

def method_access
  @method_access
end

#method_nameObject (readonly)

Returns the value of attribute method_name.



9
10
11
# File 'lib/grift/mock_method.rb', line 9

def method_name
  @method_name
end

#true_method_cachedObject (readonly)

Returns the value of attribute true_method_cached.



9
10
11
# File 'lib/grift/mock_method.rb', line 9

def true_method_cached
  @true_method_cached
end

Class Method Details

.hash_key(klass, method_name) ⇒ String

Hashes the class and method for tracking mocks.

Examples:

Grift::MockMethod.hash_key(String, :upcase)
#=> 'String#upcase'

Parameters:

  • klass (Class)
  • method_name (Symbol)

Returns:

  • (String)

    the hash of the class and method



26
27
28
# File 'lib/grift/mock_method.rb', line 26

def self.hash_key(klass, method_name)
  "#{klass}##{method_name}"
end

Instance Method Details

#mockGrift::MockMethod::MockExecutions

Gets the data for the mock results and calls for this mock.

Examples:

my_mock = Grift.spy_on(String, :upcase)
"banana".upcase
#=> 'BANANA'
my_mock.mock.calls
#=> [[]]
my_mock.mock.results
#=> [['BANANA']]

Returns:

See Also:



88
89
90
# File 'lib/grift/mock_method.rb', line 88

def mock
  @mock_executions
end

#mock_clearGrift::MockMethod::MockExecutions

Clears the mock execution and calls data for this mock, but keep the method mocked as before.



98
99
100
# File 'lib/grift/mock_method.rb', line 98

def mock_clear
  @mock_executions = MockExecutions.new
end

#mock_implementationself

Accepts a block and mocks the method to execute that block instead of the original behavior whenever called while mocked.

Examples:

my_mock = Grift.spy_on(String, :downcase).mock_implementation do
    x = 3 + 4
    x.to_s
end
"Banana".downcase
#=> '7'
my_mock = Grift.spy_on(MyClass, :my_method).mock_implementation do |first, second, **kwargs|
    [second, kwargs[:third], first]
end
MyClass.my_method(1, 2, third: 3)
#=> [2, 3, 1]

Returns:

  • (self)

    the mock itself

Raises:



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/grift/mock_method.rb', line 152

def mock_implementation(*)
  raise(Grift::Error, 'Must provide a block for the new implementation') unless block_given?

  premock_setup
  mock_executions = @mock_executions # required to access inside class instance block

  class_instance.remove_method(@method_name) if !@inherited && method_defined?
  class_instance.define_method @method_name do |*args, **kwargs|
    return_value = yield(*args, **kwargs)

    # record the args passed in the call to the method and the result
    mock_executions.store(args: args, result: return_value)
    return_value
  end
  class_instance.send(@method_access, @method_name)

  self
end

#mock_implementation_n_times(n) ⇒ self

Accepts a number n and a block and mocks the method to execute that block instaead of the original behavior the next n times the method is called while mocked. After the method has been called once, it will return to its original behavior. The method will continue to be watched.

IMPORANT: Calling #mock_clear clears the method call history. If it is called before the nth execution of the mocked method, the method will remain mocked for an additonal n calls.

Examples:

my_mock = Grift.spy_on(String, :downcase).mock_implementation_n_times(3) do
    x = 3 + 4
    x.to_s
end
["Banana", "Apple", "Orange", "Guava"].map(&:downcase)
#=> ["7", "7", "7", "guava"]
my_mock = Grift.spy_on(String, :downcase).mock_implementation_n_times(5) do
    x = 3 + 4
    x.to_s
end
["Banana", "Apple", "Orange", "Guava"].map(&:downcase)
#=> ["7", "7", "7", "7"]
my_mock.mock_clear # clear mock history before 5th (nth) method call
["Banana", "Apple", "Orange", "Guava"].map(&:downcase)
#=> ["7", "7", "7", "7"]

Parameters:

  • n (Number)

    the number of times to mock the implementation

Returns:

  • (self)

    the mock itself

Raises:

See Also:



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
# File 'lib/grift/mock_method.rb', line 252

def mock_implementation_n_times(n, *)
  raise(Grift::Error, 'Must provide a block for the new implementation') unless block_given?

  premock_setup

  # required to access inside class instance block
  mock_executions = @mock_executions
  clean_mock = lambda do
    unmock_method
    watch_method
  end

  class_instance.remove_method(@method_name) if !@inherited && method_defined?
  class_instance.define_method @method_name do |*args, **kwargs|
    return_value = yield(*args, **kwargs)

    # record the args passed in the call to the method and the result
    mock_executions.store(args: args, result: return_value)

    clean_mock.call if mock_executions.count == n

    return_value
  end
  class_instance.send(@method_access, @method_name)

  self
end

#mock_implementation_onceself

Accepts a block and mocks the method to execute that block instead of the original behavior the next time the method is called while mocked. After the method has been called once, it will return to its original behavior. The method will continue to be watched.

Examples:

my_mock = Grift.spy_on(String, :downcase).mock_implementation_once do
    x = 3 + 4
    x.to_s
end
["Banana", "Apple"].map(&:downcase)
#=> ["7", "apple"]

Returns:

  • (self)

    the mock itself

Raises:

See Also:



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
# File 'lib/grift/mock_method.rb', line 189

def mock_implementation_once(*)
  raise(Grift::Error, 'Must provide a block for the new implementation') unless block_given?

  premock_setup

  # required to access inside class instance block
  mock_executions = @mock_executions
  clean_mock = lambda do
    unmock_method
    watch_method
  end

  class_instance.remove_method(@method_name) if !@inherited && method_defined?
  class_instance.define_method @method_name do |*args, **kwargs|
    return_value = yield(*args, **kwargs)

    # record the args passed in the call to the method and the result
    mock_executions.store(args: args, result: return_value)

    clean_mock.call

    return_value
  end
  class_instance.send(@method_access, @method_name)

  self
end

#mock_resetGrift::MockMethod::MockExecutions

Clears the mock execution and calls data for this mock, and mocks the method to return ‘nil`.



108
109
110
111
112
# File 'lib/grift/mock_method.rb', line 108

def mock_reset
  executions = mock_clear
  mock_return_value(nil)
  executions
end

#mock_restore(watch: false) ⇒ Grift::MockMethod::MockExecutions

Clears the mock execution and calls data for this mock, and restores the method to its original behavior. By default it also stops watching the method. This cleans up the mocking and restores expected behavior.

Parameters:

  • watch (Boolean) (defaults to: false)

    whether or not to keep watching the method

Returns:



124
125
126
127
128
129
# File 'lib/grift/mock_method.rb', line 124

def mock_restore(watch: false)
  executions = mock_clear
  unmock_method if @true_method_cached
  watch_method if watch
  executions
end

#mock_return_value(return_value = nil) ⇒ self

Accepts a value and mocks the method to return that value instead of executing its original behavior while mocked.

Examples:

my_mock = Grift.spy_on(String, :upcase).mock_return_value('BANANA')
"apple".upcase
#=> 'BANANA'

Parameters:

  • return_value (defaults to: nil)

    the value to return from the method

Returns:

  • (self)

    the mock itself

See Also:

  • Grift#mock


295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/grift/mock_method.rb', line 295

def mock_return_value(return_value = nil)
  premock_setup
  mock_executions = @mock_executions # required to access inside class instance block

  class_instance.remove_method(@method_name) if !@inherited && method_defined?
  class_instance.define_method @method_name do |*args, **kwargs|
    # record the args passed in the call to the method and the result
    mock_executions.store(args: args, kwargs: kwargs, result: return_value)
    return_value
  end
  class_instance.send(@method_access, @method_name)

  self
end

#mock_return_value_n_times(n, return_value = nil) ⇒ self

Accepts a value and mocks the method to return that value n times instead of executing its original behavior while mocked. After the method has been called n times, it will return to its original behavior. The method will continue to be watched.

IMPORANT: Calling #mock_clear clears the method call history. If it is called before the nth execution of the mocked method, the method will remain mocked for an additional n calls.

Examples:

my_mock = Grift.spy_on(String, :upcase).mock_return_value_n_times(2, "BANANA")
["apple", "apple", "apple"].map(&:upcase)
#=> ["BANANA", "BANANA", "APPLE"]
my_mock = Grift.spy_on(String, :upcase).mock_return_value_n_times(4, "BANANA")
["apple", "apple", "apple"].map(&:upcase)
#=> ["BANANA", "BANANA", "BANANA"]
my_mock.mock_clear # clear mock history before 4th (nth) method call
["apple", "apple", "apple"].map(&:upcase)
#=> ["BANANA", "BANANA", "BANANA"]

Parameters:

  • n (Number)

    the number of times to mock the return value

  • return_value (defaults to: nil)

    the value to return from the method n times

Returns:

  • (self)

    the mock itself



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/grift/mock_method.rb', line 377

def mock_return_value_n_times(n, return_value = nil)
  premock_setup

  # required to access mock inside class instance block
  mock_executions = @mock_executions
  clean_mock = lambda do
    unmock_method
    watch_method
  end

  class_instance.remove_method(@method_name) if !@inherited && method_defined?
  class_instance.define_method @method_name do |*args, **kwargs|
    # record the args passed in the call to the method and the result
    mock_executions.store(args: args, kwargs: kwargs, result: return_value)

    clean_mock.call if mock_executions.count == n

    return_value
  end
  class_instance.send(@method_access, @method_name)

  self
end

#mock_return_value_once(return_value = nil) ⇒ self

Accepts a value and mocks the method to return that value once instead of executing its original behavior while mocked. After the method has been called once, it will return to its original behavior. The method will continue to be watched.

Examples:

my_mock = Grift.spy_on(String, :upcase).mock_return_value_once("BANANA")
["apple", "apple"].map(&:upcase)
#=> ["BANANA", "APPLE"]

Parameters:

  • return_value (defaults to: nil)

    the value to return from the method once

Returns:

  • (self)

    the mock itself



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/grift/mock_method.rb', line 325

def mock_return_value_once(return_value = nil)
  premock_setup

  # required to access mock inside class instance block
  mock_executions = @mock_executions
  clean_mock = lambda do
    unmock_method
    watch_method
  end

  class_instance.remove_method(@method_name) if !@inherited && method_defined?
  class_instance.define_method @method_name do |*args, **kwargs|
    # record the args passed in the call to the method and the result
    mock_executions.store(args: args, kwargs: kwargs, result: return_value)

    clean_mock.call

    return_value
  end
  class_instance.send(@method_access, @method_name)

  self
end

#mock_return_values_in_order(return_values) ⇒ self

Accepts an array of values and mocks the method to return those values in order instead of executing its original behavior while mocked. After the method has been called enough times to return each of the values, it will return to its original behavior. The method continue to be watched.

Examples:

mock_values = ["APPLE", "BANANA", "ORANGE"]
my_mock = Grift.spy_on(String, :upcase).mock_return_values_in_order(mock_values)
["pineapple", "orange", "guava", "mango", "watermelon"].map(&:upcase)
#=> ["APPLE", "BANANA", "ORANGE", "MANGO", "WATERMELON"]

Parameters:

  • return_values (Array)

    the values to return from the method in order

Returns:

  • (self)

    the mock itself



417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/grift/mock_method.rb', line 417

def mock_return_values_in_order(return_values)
  unless return_values.is_a?(Array) && !return_values.empty?
    raise(Grift::Error, 'Must provide a non-empty array for the return values')
  end

  premock_setup

  # required to access mock inside class instance block
  mock_executions = @mock_executions
  clean_mock = lambda do
    unmock_method
    watch_method
  end
  return_values_internal = return_values.dup

  class_instance.remove_method(@method_name) if !@inherited && method_defined?
  class_instance.define_method @method_name do |*args, **kwargs|
    # record the args passed in the call to the method and the result
    return_value = return_values_internal.shift
    mock_executions.store(args: args, kwargs: kwargs, result: return_value)

    clean_mock.call if return_values_internal.empty?

    return_value
  end
  class_instance.send(@method_access, @method_name)

  self
end

#to_sString

String representation of the MockMethod

Returns:

  • (String)

See Also:



454
455
456
# File 'lib/grift/mock_method.rb', line 454

def to_s
  Grift::MockMethod.hash_key(@klass, @method_name)
end