Custom Matcher for "calls method on object" - ruby

Given a method like:
class MyClass
def method_that_calls_stuff
method2("some value")
end
end
I'd like to define an expectation like:
my_object = MyClass.new
expect{ my_object.method_that_calls_stuff }.to call(:method2).on(my_object).with("some value")
I know I can achieve the same thing using rspec-mocks, but I don't like that syntax as well.
How would I define a matcher like that (or even better, has someone already written one)?

With the new syntax though you can get
instance = MyClass.new
expect(instance).to receive(:method2).with("some value")
instance.method_that_calls_stuff
but if you really want that matcher, you can do
RSpec::Matchers.define(:call) do |method|
match do |actual|
expectation = expect(#obj).to receive(method)
if #args
expectation.with(#args)
end
actual.call
true
end
chain(:on) do |obj|
#obj = obj
end
chain(:with) do |args|
#args = args
end
def supports_block_expectations?
true
end
end
Notice that with is optional as you may want to call a method without any args.
You can get full info on how to build custom matchers here, and fluent interface/chaining here and the block support here. If you browse around you can find how to add nice error messages and such, which always come in handy.

I don't see that method2 is being called on some object(is it being called implicitly?). But I usually write it like this:
it 'should call method2 with some value' do
MyClass.should_receive(:method2).with("some value")
MyClass.method_that_calls_stuff
# or
# #my_object.should_receive(:method2).with("some value")
# #my_object.method_that_calls_stuff
end

Related

caller_method returns not the value, that i expected

I want to know, what method calls another method (I'm just trying to create simple expect("string").to eq("string") model (just like in RSpect, but more easier).
But i get "main", what is that? (I see that "main" for first time)
public
def expect(message)
message.to_s
end
def to
caller_method = caller_locations.first.label
puts caller_method
end
expect("test").to #=> <main>
#what output i expected:
expect("test").to #=> expect
My goal:
#first i need to do something like that:
expect("test").to eq("test") #=> true
#final must look like this:
expect(expect("test").to eq("test")).to eq(true) #=> true
I would recommend against using caller_method in this case. Rather, make a class whose methods return self - that way they will be chainable:
module Expectation
attr_accessor :caller_method
def expect(arg)
self.caller_method = "expect"
self
end
def to
caller_method
end
end
include Expectation
expect("foo").to
# => "expect"
Obviously this is only a starting point, and this doesn't actually do any comparisons / validations yet. But hopefully you can understand this pattern. The key thing is returning self to make a chainable API, and storing internal state using something like attr_accessor

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

Chaining Rspec Custom Matchers

For testing reasons I've recently moved some of my RSpec matchers to use the class form rather than the DSL. Is there a way to easily get chaining behaviour when they are in this form.
E.g.
class BeInZone
def initialize(expected)
#expected = expected
end
def matches?(target)
#target = target
#target.current_zone.eql?(Zone.new(#expected))
end
def failure_message
"expected #{#target.inspect} to be in Zone #{#expected}"
end
def negative_failure_message
"expected #{#target.inspect} not to be in Zone #{#expected}"
end
# chain methods here
end
Many thanks
Add a new method with the name of chain, which normally should return self. Typically you save the provided chained state. Which you then update the matches? method to use. This state can also be used in the various output message methods too.
So for your example:
class BeInZone
# Your code
def matches?(target)
#target = target
matches_zone? && matches_name?
end
def with_name(name)
#target_name = name
self
end
private
def matches_zone?
#target.current_zone.eql?(Zone.new(#expected))
end
def matches_name?
true unless #target_name
#target =~ #target_name
end
end
Then to use it: expect(zoneA_1).to be_in_zone(zoneA).with_name('1')
The reason this works is that you are building the object that you are passing to either the should or expect(object).to methods. These methods then call matches? on the provided object.
So it's no different than other ruby code like puts "hi there".reverse.upcase.gsub('T', '7'). here the string "hi there" is your matcher and the chained methods are called on it, passing the final object returned from gsub to puts.
The built-in expect change matcher is a good example to review.

Ruby Metaprogramming

I'm trying to write a DSL that allows me to do
Policy.name do
author "Foo"
reviewed_by "Bar"
end
The following code can almost process it:
class Policy
include Singleton
def self.method_missing(name,&block)
puts name
puts "#{yield}"
end
def self.author(name)
puts name
end
def self.reviewed_by(name)
puts name
end
end
Defining my method as class methods (self.method_name) i can access it using the following syntax:
Policy.name do
Policy.author "Foo"
Policy.reviewed_by "Bar"
end
If i remove the "self" from the method names, and try to use my desired syntax, then i receive an error "Method not Found" in the Main so it could not find my function until the module Kernel. Its ok, i understand the error. But how can i fix it? How can i fix my class to make it work with my desired syntax that?
In order to control what self is in the scope of the block (since author resolves to self.author), you can use instance_eval.
class Policy
def self.name(&block)
PolicyNameScope.new(block)
end
class PolicyNameScope
def initialize(block)
instance_eval(&block)
end
def author(author)
#author = author
end
def reviewed_by(reviewed_by)
#reviewed_by = reviewed_by
end
end
end
policy = Policy.name do
author "Dawg"
reviewed_by "Dude"
end
p policy
# => #<Policy::PolicyNameScope:0x7fb81ef9f910 #reviewed_by="Dude", #author="Dawg">
The PolicyNameScope class has the instance methods that are allowed in the name block. This is so that methods from Policy isn't available inside the block, making the DSL a whole lot tighter.
Since your example is out of context I can't help you any further - this code by itself doesn't seem very useful.
The above answer suggested by August is correct, however if you want to use your Constructor for some other purpose, then the above method fails. Therefore, in that situation you have to use some other method other than that described above. Here's the solution that I have prepared without using the class constructor.
class Policy
def self.method_missing(name,&block)
self.class_eval(&block)
end
def self.author(name)
p name
end
def self.reviewed_by(name)
p name
end
end
Policy.name do
author "Foo"
reviewed_by "Bar"
end
This is not an optimized solution but a solution to your stated problem.

How do I "fake" C# style attributes in Ruby?

EDIT: I slightly changed the spec, to better match what I imagined this to do.
Well, I don't really want to fake C# attributes, I want to one-up-them and support AOP as well.
Given the program:
class Object
def Object.profile
# magic code here
end
end
class Foo
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
Foo.new.bar("test")
Foo.new.barbar("test")
puts Foo.get_comment(:snafu)
Desired output:
Foo.bar was called with param: b = "test"
test
Foo.bar call finished, duration was 1ms
test
This really should be fixed
Is there any way to achieve this?
I have a somewhat different approach:
class Object
def self.profile(method_name)
return_value = nil
time = Benchmark.measure do
return_value = yield
end
puts "#{method_name} finished in #{time.real}"
return_value
end
end
require "benchmark"
module Profiler
def method_added(name)
profile_method(name) if #method_profiled
super
end
def profile_method(method_name)
#method_profiled = nil
alias_method "unprofiled_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
name = "\#{self.class}##{method_name}"
msg = "\#{name} was called with \#{args.inspect}"
msg << " and a block" if block_given?
puts msg
Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
end
ruby_eval
end
def profile
#method_profiled = true
end
end
module Comment
def method_added(name)
comment_method(name) if #method_commented
super
end
def comment_method(method_name)
comment = #method_commented
#method_commented = nil
alias_method "uncommented_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
puts #{comment.inspect}
uncommented_#{method_name}(*args, &blk)
end
ruby_eval
end
def comment(text)
#method_commented = text
end
end
class Foo
extend Profiler
extend Comment
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
A few points about this solution:
I provided the additional methods via modules which could be extended into new classes as needed. This avoids polluting the global namespace for all modules.
I avoided using alias_method, since module includes allow AOP-style extensions (in this case, for method_added) without the need for aliasing.
I chose to use class_eval rather than define_method to define the new method in order to be able to support methods that take blocks. This also necessitated the use of alias_method.
Because I chose to support blocks, I also added a bit of text to the output in case the method takes a block.
There are ways to get the actual parameter names, which would be closer to your original output, but they don't really fit in a response here. You can check out merb-action-args, where we wrote some code that required getting the actual parameter names. It works in JRuby, Ruby 1.8.x, Ruby 1.9.1 (with a gem), and Ruby 1.9 trunk (natively).
The basic technique here is to store a class instance variable when profile or comment is called, which is then applied when a method is added. As in the previous solution, the method_added hook is used to track when the new method is added, but instead of removing the hook each time, the hook checks for an instance variable. The instance variable is removed after the AOP is applied, so it only applies once. If this same technique was used multiple time, it could be further abstracted.
In general, I tried to stick as close to your "spec" as possible, which is why I included the Object.profile snippet instead of implementing it inline.
Great question. This is my quick attempt at an implementation (I did not try to optimise the code). I took the liberty of adding the profile method to the
Module class. In this way it will be available in every class and module definition. It would be even better
to extract it into a module and mix it into the class Module whenever you need it.
I also didn't know if the point was to make the profile method behave like Ruby's public/protected/private keywords,
but I implemented it like that anyway. All methods defined after calling profile are profiled, until noprofile is called.
class Module
def profile
require "benchmark"
#profiled_methods ||= []
class << self
# Save any original method_added callback.
alias_method :__unprofiling_method_added, :method_added
# Create new callback.
def method_added(method)
# Possible infinite loop if we do not check if we already replaced this method.
unless #profiled_methods.include?(method)
#profiled_methods << method
unbound_method = instance_method(method)
define_method(method) do |*args|
puts "#{self.class}##{method} was called with params #{args.join(", ")}"
bench = Benchmark.measure do
unbound_method.bind(self).call(*args)
end
puts "#{self.class}##{method} finished in %.5fs" % bench.real
end
# Call the original callback too.
__unprofiling_method_added(method)
end
end
end
end
def noprofile # What's the opposite of profile?
class << self
# Remove profiling callback and restore previous one.
alias_method :method_added, :__unprofiling_method_added
end
end
end
You can now use it as follows:
class Foo
def self.method_added(method) # This still works.
puts "Method '#{method}' has been added to '#{self}'."
end
profile
def foo(arg1, arg2, arg3 = nil)
puts "> body of foo"
sleep 1
end
def bar(arg)
puts "> body of bar"
end
noprofile
def baz(arg)
puts "> body of baz"
end
end
Call the methods as you would normally:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
And get benchmarked output (and the result of the original method_added callback just to show that it still works):
Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
One thing to note is that it is impossible to dynamically get the name of the arguments with Ruby meta-programming.
You'd have to parse the original Ruby file, which is certainly possible but a little more complex. See the parse_tree and ruby_parser
gems for details.
A fun improvement would be to be able to define this kind of behaviour with a class method in the Module class. It would be cool to be able to do something like:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
I'll leave this meta-meta-programming exercise for another time. :-)

Resources