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.1'
Class Method Summary collapse
-
.get(proxy, key_path) ⇒ Block
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
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
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
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
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
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
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 |