How to assert block of a mock in mocha - ruby

This example is contrived, please don't take it verbatim as my code.
I have the need to assert something like the following:
def mymethod
Dir.chdir('/tmp') do
`ls`
end
end
In the end I want to assert that:
Dir.chdir is invoked with the appropriate parameters.
` is invoked with the appropriate parameters
I started off with...
Dir.expects(:chdir).with('/tmp')
but after that I'm not sure how to invoke the block passed to Dir.chdir.

You need to use the mocha yields method. Also, writing an expectation for the backtick method is rather interesting. You need to make an expectation like this:
expects("`")
But on what object? You might think on Kernel or Object, but that doesn't actually work.
As an example, given this module:
module MyMethod
def self.mymethod
Dir.chdir('/tmp') do
`ls`
end
end
end
I could write a test like this:
class MyMethodTest < Test::Unit::TestCase
def test_my_method
mock_block = mock
mock_directory_contents = mock
MyMethod.expects("`").with('ls').returns(mock_directory_contents)
Dir.expects(:chdir).yields(mock_block).returns(mock_directory_contents)
assert_equal mock_directory_contents, MyMethod.mymethod
end
end
Part of the trick is to figure out which object to expect the backtick method to be invoked on. It depends on the context - whatever self is when that method is invoked. Here it is the module MyMethod, but depending on where you define mymethod it will be different.

Related

How to test ruby module methods with block using Rspec?

I want to test a following method, which calls a module method with a block.
def test_target
MyModule.send do |payload|
payload.my_text = "payload text"
end
end
MyModule's structure is like following.
module MyModule
class Payload
attr_accessor :my_text
def send
# do things with my_text
end
end
class << self
def send
payload = Payload.new
yield payload
payload.send
end
end
How can I test whether MyModule receives send method with a block, which assigns "payload text" to payload.my_text?
Currently I'm only testing expect(MyModule).to receive(:send).once. I looked through and tried Rspec yield matchers but cannot get things done. (Maybe I've ben searching for wrong keywords..)
The easiest way is to insert a double as the yield argument, which you can make an assertion on.
payload = Payload.new
allow(Payload).to receive(:new).and_return(payload)
test_target
expect(payload.my_text).to eq 'payload text'
Alternatively you could also use expect_any_instance_of, but I'd always prefer to use a specific double instead.
I would mock MyModule to yield another mock, that would allow speccing that my_text= is called on the yielded object.
let(:payload) { instance_double('Payload') }
before do
allow(MyModule).to receive(:send).and_yield(payload)
allow(payload).to receive(:my_text=).and_return(nil)
end
# expectations
expect(MyModule).to have_received(:send).once
expect(payload).to have_received(:my_text=).with('payload text').once

How do write two methods with different number of arguments in Ruby

I am trying to write this inside my class:
class << self
def steps
#steps.call
end
def transitions
#transitions.call
end
def steps(&steps)
#steps = steps
end
def transitions(&transitions)
#transitions = transitions
end
end
That won't work since in Ruby, I can't do this kind of method overloading. Is there a way around this?
You can kind of do this with method aliasing and mixins, but the way you handle methods with different signatures in Ruby is with optional arguments:
def steps(&block)
block.present? ? #steps = block : #steps.call
end
This sort of delegation is a code smell, though. It usually means there's something awkward about the interface you've designed. In this case, something like this is probably better:
def steps
#steps.call
end
def steps=(&block)
#steps = block
end
This makes it clear to other objects in the system how to use this interface since it follows convention. It also allows for other cases, like passing a block into the steps method for some other use:
def steps(&block)
#steps.call(&block)
end
Ruby does not support method overloading (see "Why doesn't ruby support method overloading?" for the reason). You can, however, do something like:
def run(args*)
puts args
end
args will then be an array of the arguments passed in.
You can also pass in a hash of options to handle arguments, or you can pass in nil when you don't want to supply arguments and handle nil in your method body.

How do I make a Ruby method that lasts for the lifetime of a block?

Inside the body of a class, I'd like to pass a block to a method called with. For the lifetime of the block, I would like a with_value method to be available.
Otherwise, everything inside the block should behave as if it were outside the block.
Here's an example:
class C
extend M
with "some value" do
do_something_complicated
do_something_complicated
do_something_complicated
end
end
We can almost get this with:
module M
def with(str, &block)
Object.new.tap do |wrapper|
wrapper.define_singleton_method :with_value do # Here's our with_value
str # method.
end
end.instance_eval &block
end
def do_something_complicated # Push a value onto an
(#foo ||= []).push with_value # array.
end
end
but there's a problem: since we're evaluating the block passed to with inside the context of a different object, do_something_complicated isn't available.
What's the right way to pull this off?
This will make with_value available only within the block. However, _with_value will be defined within or outside of the block.
module M
def _with_value
...
end
def with(str, &block)
alias with_value _with_value
block.call
undef with_value
end
...
end
I cannot tell from the question whether this is a problem. If it is a problem, you need to further describe what you are trying to do.
Basically, the idea is to use method_missing to forward method calls from the dummy class to the calling class. If you also need to access instance variables, you can copy them from the calling class to your dummy class, and then back again after the block returns.
The Ruby gem docile is a very simple implementation of such a system. I suggest you read the source code in that repository (don't worry, it's a very small codebase) for a good example of how DSL methods like the one in your example work.
Here is a way that is closer to your attempt:
module M
def with(str, &block)
dup.tap do |wrapper|
wrapper.define_singleton_method :with_value do
...
end
end.instance_eval &block
end
...
end
dup will duplicate the class from where with is called as a class method.

Ruby any way to catch messages before method_missing?

I understand that method_missing is something of a last resort when Ruby is processing messages. My understanding is that it goes up the Object hierarchy looking for a declared method matching the symbol, then back down looking for the lowest declared method_missing. This is much slower than a standard method call.
Is it possible to intercept sent messages before this point? I tried overriding send, and this works when the call to send is explicit, but not when it is implicit.
Not that I know of.
The most performant bet is usually to use method_missing to dynamically add the method being to a called to the class so that the overhead is only ever incurred once. From then on it calls the method like any other method.
Such as:
class Foo
def method_missing(name, str)
# log something out when we call method_missing so we know it only happens once
puts "Defining method named: #{name}"
# Define the new instance method
self.class.class_eval <<-CODE
def #{name}(arg1)
puts 'you passed in: ' + arg1.to_s
end
CODE
# Run the instance method we just created to return the value on this first run
send name, str
end
end
# See if it works
f = Foo.new
f.echo_string 'wtf'
f.echo_string 'hello'
f.echo_string 'yay!'
Which spits out this when run:
Defining method named: echo_string
you passed in: wtf
you passed in: hello
you passed in: yay!

Ruby: Passing a block to a class macro that defines instance methods

I'm struggling with code that looks like the example below (but actually does something useful). The block that is passed to def_my_method is of course created in the context of the class, when I want to evaluate it in the context of the instance that has the instance method. How do I do this?
module Formatters
# Really useful methods, included in several classes
def format_with_stars(str)
return "*** #{str} ***"
end
end
class Test
include Formatters
STRINGS = ["aa", "bb"]
def self.def_my_method(method_name, another_parameter, &format_proc)
define_method(method_name) do
# In reality, some more complex code here, then...
return STRINGS.map(&format_proc)
end
end
def_my_method(:star_strings, "another_parameter") { |str| format_with_stars(str) }
# Define other methods
end
tt = Test.new
puts tt.star_strings
# Throws undefined method `format_with_stars' for Test:Class (NoMethodError)
You can use instance_exec to execute the passed block in the right context. Instead of passing &format_proc directly to the call to map, pass a block that calls it using instance exec.
Something like this:
def self.def_my_method(method_name, another_parameter, &format_proc)
define_method(method_name) do
# In reality, some more complex code here, then...
return STRINGS.map{|str| instance_exec(str, &format_proc)}
end
end
This produces this:
$ ruby tt.rb
*** aa ***
*** bb ***
for me (where tt.rb is the arbitary name I gave the file), which I think is what you want for this example.
...
class Test
- include Formatters
+ extend Formatters
...
should do the trick.

Resources