Class: MockProxy
- Inherits:
-
Object
- Object
- MockProxy
- 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 { || expect().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 } }) }
Constant Summary collapse
- VERSION =
The version number
'0.1.0'
Class Method Summary collapse
-
.get(proxy, key_path) ⇒ Block, Hash
Retrieve the existing callback or callback tree at the specified key path.
-
.merge(proxy, new_callback_hash) ⇒ MockProxy
Deep merges the callback tree, replacing existing values with new values.
-
.observe(proxy, key_path) {|args| ... } ⇒ MockProxy
Add an observer to an existing proxy.
-
.replace_at(proxy, key_path, &block) ⇒ MockProxy
Replaces the proc at the specified key path.
-
.wrap(proxy, key_path) {|args,| ... } ⇒ MockProxy
Wraps the existing callback with your block.
Instance Method Summary collapse
-
#initialize(callback_hash) ⇒ MockProxy
constructor
A new instance of MockProxy.
- #method_missing(name, *args, &block) ⇒ Object
Constructor Details
#initialize(callback_hash) ⇒ MockProxy
Returns a new instance of MockProxy.
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
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
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
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
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
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
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 |