Class: MockProxy

Inherits:
Object
  • Object
show all
Defined in:
lib/mock_proxy.rb,
lib/mock_proxy/version.rb

Overview

A non-opinionated proxy object that has multiple uses. It can be used for mocking, spying, stubbing. Use as a dummy, double, fake, etc. Every test double type possible. How? Let's see

Example, say you want to stub this scenario: Model.new.generate_email.validate!.send(to: email) That would have be 5-6 lines of stubbing. If this sounds like stub_chain, you're on the right track. It was removed in RSpec 3 (or 2?). It's similar to that but it does things differently First, it doesn't require you to use it in a stub Second, it's use of procs means you can define anything, a stub or a mock (expectation) or a spy or whatever you want

To use MockProxy, initialize it with a hash. Each key is a method call. Each call either returns a new proxy or calls the proc. If the value is a proc, it calls it immediately with the args and block. If the value is a hash, it returns a new proxy with the value as the hash. MockProxy will warn if you don't use hashes or procs and will also warn if you did not define all the method calls (it won't automatically return itself for methods not defined in the hash)

Example use:

let(:model_proxy) { MockProxy.new(generate_email: { validate!: { send: proc { |to| email } } }) }
before { allow(Model).to receive(:new).and_return model_proxy }
# ...
describe 'Model' do
  it 'model also receives email' do
    callback = proc { |message| expect(message).to eq 'message' }
    MockProxy.update_proxy(model_proxy, receive_email: callback)
    run_system_under_test
  end
end

NOTE: You don't have to use only one mock proxy for all calls. You can break it up if you want to have more control over each method call

Example:

let(:model_proxy) do
  callback = proc do |type|
    MockProxy.update_proxy(generator_proxy, decorate: proc { |*args| method_call(type, *args) })
    generator_proxy
  end
  MockProxy.new(generate_email: callback)
end
let(:generator_proxy) { MockProxy.new(validate!: { send: proc { |to| email } }) }

Author:

  • Geoff Lee

Since:

  • 0.1.0

Constant Summary collapse

VERSION =

The version number

Since:

  • 0.1.0

'0.1.0'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(callback_hash) ⇒ MockProxy

Returns a new instance of MockProxy

Parameters:

  • callback_hash (Hash)

    the tree of chained method calls

Since:

  • 0.1.0


183
184
185
# File 'lib/mock_proxy.rb', line 183

def initialize(callback_hash)
  @callback_hash = callback_hash.deep_stringify_keys.freeze
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

Since:

  • 0.1.0


188
189
190
191
192
193
194
195
196
197
198
# File 'lib/mock_proxy.rb', line 188

def method_missing(name, *args, &block)
  current = @callback_hash[name.to_s]
  if current.is_a?(Proc)
    current.call(*args, &block)
  else
    if !current.is_a?(Proc) && !current.is_a?(Hash)
      fail "Missing method #{name}. Please add this definition to your mock proxy"
    end
    MockProxy.new(current.freeze)
  end
end

Class Method Details

.get(proxy, key_path) ⇒ Block, Hash

Retrieve the existing callback or callback tree at the specified key path

NOTE: We freeze the hash so you cannot modify it

Use case: Retrieve proc to mock or retrieve hash to reflect on what is currently being stubbed

Parameters:

  • proxy (MockProxy)

    existing proxy

  • key_path (String, Array<String>)

    the chain of methods or key path. Can be a dot delimited key path or an array of method names as strings or symbols

Returns:

  • (Block, Hash)

Since:

  • 0.1.0


178
179
180
# File 'lib/mock_proxy.rb', line 178

def self.get(proxy, key_path)

end

.merge(proxy, new_callback_hash) ⇒ MockProxy

Deep merges the callback tree, replacing existing values with new values

Use case: Reuse existing stub but with some different values

Parameters:

  • proxy (MockProxy)

    existing proxy

  • new_callback_hash (Hash)

    new partial callback tree

Returns:

Since:

  • 0.1.0


54
55
56
57
58
59
60
# File 'lib/mock_proxy.rb', line 54

def self.merge(proxy, new_callback_hash)
  existing_callback_hash = proxy.instance_variable_get('@callback_hash')
  new_callback_hash = new_callback_hash.deep_stringify_keys
  new_callback_hash = existing_callback_hash.deep_merge(new_callback_hash).freeze
  proxy.instance_variable_set('@callback_hash', new_callback_hash)
  proxy
end

.observe(proxy, key_path) {|args| ... } ⇒ MockProxy

Add an observer to an existing proxy

Use case: Observe method call without changing the existing callback's stubbed return value

Parameters:

  • proxy (MockProxy)

    existing proxy

  • key_path (String, Array<String>)

    the chain of methods or key path. Can be a dot delimited key path or an array of method names as strings or symbols

Yield Parameters:

  • args (*args)

Yield Returns:

  • (optional)

Returns:

Since:

  • 0.1.0


85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/mock_proxy.rb', line 85

def self.observe(proxy, key_path, &block)
  callback = get_callback(proxy, key_path)
  # Wrap existing callback, calling the provided block before it
  # Multiple calls to .observe will create a pyramid of callbacks, calling the observers before
  # eventually calling the existing callback
  new_callback = proc do |*args|
    block.call(*args)
    callback.call(*args)
  end
  set_callback(proxy, key_path, new_callback)
  proxy
end

.replace_at(proxy, key_path, &block) ⇒ MockProxy

Replaces the proc at the specified key path

Use case: Reuse existing stub but modify a proc

Parameters:

  • proxy (MockProxy)

    existing proxy

  • key_path (String, Array<String>)

    the chain of methods or key path. Can be a dot delimited key path or an array of method names as strings or symbols

Returns:

Since:

  • 0.1.0


70
71
72
73
# File 'lib/mock_proxy.rb', line 70

def self.replace_at(proxy, key_path, &block)
  set_callback(proxy, key_path, block)
  proxy
end

.wrap(proxy, key_path) {|args,| ... } ⇒ MockProxy

Wraps the existing callback with your block

Use case: Get full control of the existing proc while running custom code

Parameters:

  • proxy (MockProxy)

    existing proxy

  • key_path (String, Array<String>)

    the chain of methods or key path. Can be a dot delimited key path or an array of method names as strings or symbols

Yield Parameters:

  • args, (*args, &block)

    original callback

Yield Returns:

  • (optional)

Returns:

Since:

  • 0.1.0


108
109
110
111
112
113
114
115
116
117
118
# File 'lib/mock_proxy.rb', line 108

def self.wrap(proxy, key_path, &block)
  callback = get_callback(proxy, key_path)
  # Wrap existing callback, calling the provided block before it
  # Multiple calls to .observe will create a pyramid of callbacks, calling the observers before
  # eventually calling the existing callback
  new_callback = proc do |*args|
    block.call(*args, &callback)
  end
  set_callback(proxy, key_path, new_callback)
  proxy
end