Skip to content

Instantly share code, notes, and snippets.

@fledman
Created March 16, 2015 22:27
Show Gist options
  • Save fledman/66b37c0a532617bc57e3 to your computer and use it in GitHub Desktop.
Save fledman/66b37c0a532617bc57e3 to your computer and use it in GitHub Desktop.
Mocha Callable Returns
=begin
Extends Mocha to support dynamic return values.
Now you can set the return value of your stub or
expectation to a proc; the proc is evaluated each
time the method is called. For example, you can
now do Foo.stubs(:when).returns(->{Time.now}) and
each call to Foo.when will return the current time.
Note that this behaviour is only available while inside
of a block passed to Mocha.with_callable_returns.
Otherwise, passing a proc to an expectation will
return the the proc itself, as it would any object.
Since we evaluate the proc each time the method is
called it can be slow; don't overuse this functionality.
This behavior is useful when used in metaprogramming.
Additionally, we can use this functionality to
create expectations that stub their results:
Mocha.with_callable_returns do
Customer.any_instance.stubs(:compute_foo).returns(->{
Foo.new.tap{ |f| f.stubs(thing_1: 'a', thing_2: 'b') }
})
end
foo = Customer.find(some_id).compute_foo
assert_equal foo.thing_1, 'a'
If we want we can remove the stub on Customer; doing so
does not change the stubs on the existing foo instance.
This may be useful for preventing code pollution.
Customer.any_instance.unstub(:compute_foo)
assert_equal foo.thing_2, 'b'
Finally, please note that the arity of your procs must
be zero e.g. they cannot expect any arguments.
=end
module Mocha
def self.callable_returns?
!!@callable_returns
end
def self.with_callable_returns
was = callable_returns?
@callable_returns = true
yield
ensure
@callable_returns = was
end
class SingleReturnValue
def is_callable?
@value.respond_to?(:call) &&
@value.respond_to?(:arity) && @value.arity.zero?
end
end
module CallableReturnBuilder
def build(*values)
super.tap do |rets|
if Mocha.callable_returns?
rets.values.each do |val|
def val.evaluate
is_callable? ? @value.call : @value
end
end
end
end
end
end
ReturnValues.singleton_class.send(:prepend, CallableReturnBuilder)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment