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
Related
I am new to ruby and I'm hoping someone can help me. I am writing tests using test::unit and within my tests I needed to run some code before an assert was called so I overrided the assert methods like so:
class TestSomething < Test::Unit::TestCase
def assert_equal(expected, actual, message = nil)
mycode ..
super(expected, actual, message)
end
def assert(object, message)
my code ...
super(object, message)
end
def assert_not_nil(object, message = "")
my code ...
super(object, message)
end
def setup
end
def test_case1
end
def test_case1
end
def teardown
end
end
The above structure works fine and the asserts call my code. The thing is I have 100s of test classes. The assert overriding will be the same for all of them. Do I have to copy the assert overrides to the top of every class or is there a way for all of them to get the assert overrides in one go?
One more question. Is there a way of catching an error if it occurs anywhere within the entire class?
A
I am not sure why you do not want to use before filter, but the question as it was stated has an answer: since ruby classes are open, one might do the following.
class Test::Unit::TestCase
# store the original
alias_method :assert_equal_original, :assert_equal
# override
def assert_equal *args
# mycode ..
assert_equal_original *args
end
# the same for other methods
end
Once it is done, any derived class will call my_code before calling the original method.
To catch an error within the scope of the class is impossible, AFAIK.
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
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.
First, for the short version:
Isn't a method definition just a block? Why can't I do something like:
obj.instance_exec(&other_obj.method(:my_method))
with the goal of running some module method in the context of an instance of a separate class? The method is called, but it doesn't seem to be executed in the context of 'obj', despite the 'instance_exec' call.
The only way I can figure out how to accomplish this is to wrap all of the code of 'my_method' in a proc, then call in the following manner instead:
obj.instance_eval(&other_obj.my_method)
but I'd like to avoid encapsulating all of my module methods in procs.
Now, for the long version:
I'm attempting to create a modularized external provider system, where for any given class/method (generally controller methods,) I can call a corresponding method for a given provider (e.g. facebook).
Since there could be multiple providers, the provider methods need to be namespaced, but instead of simply including a bunch of methods like, for example, 'facebook_invitation_create', I'd like my InvitationsController instance to have a facebook member containing a create method - e.g.
class InvitationsController < ApplicationController
def create
...
# e.g. self.facebook.create
self.send(params[:provider]).create
...
end
end
Furthermore, I'd like the provider methods to not only function as if they were part of the controller itself - meaning they should have access to things like controller instance variables, params, session, etc. - but also to be (mostly) written as if they were part of the controller itself - meaning without any complex additional code as a result of being modularized.
I've created a simplified example below, in which MyClass has a greet method, which if called with a valid provider name (:facebook in this case), will call that providers greet method instead. In turn, the provider greet method accesses the message method of the including class, as if it were part of the class itself.
module Providers
def facebook
#facebook ||= FacebookProvider
end
module FacebookProvider
class << self
def greet
proc {
"#{message} from facebook!"
}
end
end
end
end
class MyClass
include Providers
attr_accessor :message
def initialize(message="hello")
self.message = message
end
def greet(provider=nil)
(provider.nil? or !self.respond_to?(provider)) ? message : instance_exec(&self.send(provider).greet)
end
end
This actually accomplishes almost everything I've previously stated, but I'm hung up on the fact that my provider functions need to be encapsulated in procs. I thought maybe I could simply call instance_exec on the method instead (after removing the proc encapsulation):
instance_exec(&self.send(provider).method(:greet))
...but then it seems like the instance_exec is ignored, as I get the error:
NameError: undefined local variable or method `message' for Providers::FacebookProvider:Module
Is there any way to call instance_exec on a defined method?
(I'm open to suggestions on how to better implement this as well...)
I think this is simpler than you might expect (and I realize that my answer is 2 years after you asked)
You can use instance methods from modules and bind them to any object.
module Providers
def facebook
#facebook ||= FacebookProvider
end
module FacebookProvider
def greet
"#{message} from facebook!"
end
end
end
class MyClass
include Providers
attr_accessor :message
def initialize(message="hello")
self.message = message
end
def greet(provider=nil)
if provider
provider.instance_method(:greet).bind(self).call
else
message
end
end
end
If your provider is a module, you can user instance_method to create an UnboundMethod and bind it to the current self.
This is delegation.
It's the basis for the casting gem which would work like this:
delegate(:greet, provider)
Or, if you opt-in to using method_missing from casting, your code could just look like this:
greet
But you'd need to set your delegate first:
class MyClass
include Providers
include Casting::Client
delegate_missing_methods
attr_accessor :message
def initialize(message="hello", provider=facebook)
cast_as(provider)
self.message = message
end
end
MyClass.new.greet # => "hello from facebook!"
I wrote about what delegation is and is not on my blog which is relevant to understanding DCI and what I wrote about in Clean Ruby
Maybe I'm not following along, but it seems like you are making this harder than it needs to be.
Why not implement a "dispatch" pattern in your class, where you have a hash of provider names and provider methods {:facebook=>"facebook_greet"} and then just "send" the incoming call to the correct handler via "Object#send" (http://ruby-doc.org/core-1.9.3/Object.html#method-i-send)? Send is very fast for dispatching methods, so unlike eval, you should get great performance.
Here's some code to demonstrate the way I'd solve it (assuming I am following along with what you're trying to accomplish):
module TwitterProvider
def providerInit(providers)
#providers[:twitter]="twitter_greet"
super(providers) if defined?(super)
end
def twitter_greet
"Hello Twitter User"
end
end
module FacebookProvider
def providerInit(providers)
providers[:facebook]="facebook_greet"
super(providers) if defined?(super)
end
def facebook_greet
"Hello Facebook User"
end
end
class MyClass
include FacebookProvider
include TwitterProvider
attr_accessor :message
def providerInit(providers)
super(providers) if defined?(super)
end
def initialize(message="hello")
#providers = {}
self.message = message
providerInit(#providers)
end
def greet(provider=nil)
if provider.nil? or !self.respond_to?(#providers[provider])
self.message
else
self.send(#providers[provider])
end
end
end
my_class = MyClass.new
puts my_class.greet
puts my_class.greet(:twitter)
puts my_class.greet(:facebook)
# Output:
# hello
# Hello Twitter User
# Hello Facebook User
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. :-)