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.1'

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

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

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)

Since:

  • 0.1.0



57
58
59
# File 'lib/mock_proxy.rb', line 57

def self.get(proxy, key_path)
  get_callback(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



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

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



99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/mock_proxy.rb', line 99

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



84
85
86
87
# File 'lib/mock_proxy.rb', line 84

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



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/mock_proxy.rb', line 122

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