Let's assume that i have this class
class Foo
def bar(param1=nil, param2=nil, param3=nil)
:bar1 if param1
:bar2 if param2
:bar3 if param3
end
end
I can stub whole bar method using:
Foo.any_instance.expects(:bar).at_least_once.returns(false)
However if I only want to stub when param1 of bar method is true, I couldn't find a way to do:
I also looked at with, and has_entry, and it seems it only applies to a single parameter.
I was expecting some function like this.
Foo.any_instance.expects(:bar).with('true',:any,:any).returns(:baz1)
Foo.any_instance.expects(:bar).with(any,'some',:any).returns(:baz2)
Thanks
................................................... EDITED THE FOLLOWING .............................................
Thanks, nash
Not familiar with rspec, so I tried with unit test with any_instance, and it seems work
require 'test/unit'
require 'mocha'
class FooTest < Test::Unit::TestCase
def test_bar_stub
foo = Foo.new
p foo.bar(1)
Foo.any_instance.stubs(:bar).with { |*args| args[0]=='hee' }.returns('hee')
Foo.any_instance.stubs(:bar).with { |*args| args[1]=='haa' }.returns('haa')
Foo.any_instance.stubs(:bar).with { |*args| args[2]!=nil }.returns('heehaa')
foo = Foo.new
p foo.bar('hee')
p foo.bar('sth', 'haa')
p foo.bar('sth', 'haa', 'sth')
end
end
If I got you right it can be something like:
class Foo
def bar(param1=nil, param2=nil, param3=nil)
:bar1 if param1
:bar2 if param2
:bar3 if param3
end
end
describe Foo do
it "returns 0 for all gutter game" do
foo = Foo.new
foo.stub(:bar).with { |*args| args[0] }.and_return(:bar1)
foo.stub(:bar).with { |*args| args[1] }.and_return(:bar2)
foo.stub(:bar).with { |*args| args[2] }.and_return(:bar3)
foo.bar(true).should == :bar1
foo.bar('blah', true).should == :bar2
foo.bar('blah', 'blah', true).should == :bar3
end
end
Related
require 'requiredclass'
class Test
def get_client()
return some_client
end
def intermediate_method()
res = nil
self.class
.get_client
.retry(tries:5, on: [RequiredClass::ClientTimeout]) do |myclient|
call_count += 1
res = myclient.dosomething()
end
return res
end
def method_to_test()
x = intermediate_method()
y = false
return x && y
end
end
How can I write rspec for method_to_test here. How can I mock get_client.retry as well as calls to get_client while also mocking res variable assignment so that gets assigned the value i would like it to assign.
As written, this code is difficult to test. That's a smell and a sign that the code should be restructured. Really any time you feel tempted to mock a method in the current class, that's a sign that the thing you want to mock does not belong in that class. It should be injected (passed in) instead. Like this:
require 'requiredclass'
class Test
attr_reader :client
def initialize(client)
#client = client
end
def method_to_test
x = intermediate_method
y = false
x && y
end
def intermediate_method
res = nil
client.retry(tries: 5, on: [RequiredClass:ClientTimeout]) do |my_client|
call_count += 1
res = my_client.do_something
end
res
end
end
Given this refactored code, the tests might look like this:
RSpec.describe Test do
subject(:test) { Test.new(client) }
let(:client) { instance_double(Client, retry: true, do_something: true) }
describe '#method_to_test'
subject(:method_to_test) { test.method_to_test }
it 'returns false' do
expect(method_to_test).to be_false
end
end
end
In this code I've passed a double with a stubbed retry method into the Test class on instantiation. You could optionally use a mock, instead. That would look like this:
RSpec.describe Test do
subject(:test) { Test.new(client) }
let(:client) { instance_double(Client) }
before do
allow(client).to receive(:retry)
allow(client).to receive(:do_something)
end
describe '#method_to_test'
subject(:method_to_test) { test.method_to_test }
it 'returns false' do
expect(method_to_test).to be_false
end
end
end
There's a good write up of mocks and doubles in the RSpec documentation.
If I have a class like
class MyClass
def initialize(&block)
#myBlock = block
end
def process
#...
#myBlock.call
#..
end
and I want to test a DifferentClass which needs to use MyClass and thus receive a block in the new.
How can I stub that block? What can I do in this situation?
describe DifferentClass do
it 'zomfg' do
allow(MyClass).to receive(:new) do |&block|
expect(block).to receive(:call)
my_object = double
allow(my_object).to receive(:process) { block.call }
my_object
end
foo = MyClass.new {}
# Here you should be passing the foo object to
# whatever method DifferentClass uses it in
foo.process
end
end
I had a code looking like this:
def my_function(obj)
if obj.type == 'a'
return [:something]
elsif obj.type == 'b'
return []
elsif obj.type == 'c'
return [obj]
elsif obj.type == 'd'
return [obj]*2
end
end
I want to separate all these if...elsif blocks into functions like this:
def my_function_with_a
return [:something]
end
def my_function_with_b
return []
end
def my_function_with_c(a_parameter)
return [a_parameter]
end
def my_function_with_d(a_parameter)
return [a_parameter] * 2
end
I call these functions with
def my_function(obj)
send(:"my_function_with_#{obj.type}", obj)
end
The problem is that some functions need parameters, others do not. I can easily define def my_function_with_a(nothing=nil), but I'm sure there is a better solution to do this.
#Dogbert had a great idea with arity. I have a solution like this:
def my_function(obj)
my_method = self.method("my_function_with_#{obj.type}")
return (method.arity.zero? ? method.call : method.call(obj))
end
Check how to call methods in Ruby, for that I will recommend you this two resources: wikibooks and enter link description here.
Take a special note on optional arguments where you can define a method like this:
def method(*args)
end
and then you call call it like this:
method
method(arg1)
method(arg1, arg2)
def foo(*args)
[ 'foo' ].push(*args)
end
>> foo
=> [ 'foo' ]
>> foo('bar')
=> [ 'foo', 'bar' ]
>> foo('bar', 'baz')
=> [ 'foo', 'bar', 'baz' ]
def my_function(obj)
method = method("my_function_with_#{obj.type}")
method.call(*[obj].first(method.arity))
end
Change your function to something like:
def my_function_with_foo(bar=nil)
if bar
return ['foo', bar]
else
return ['foo']
end
end
Now the following will both work:
send(:"my_function_with_#{foo_bar}")
=> ['foo']
send(:"my_function_with_#{foo_bar}", "bar")
=> ['foo', 'bar']
You can also write it like this if you don't want to use if/else and you're sure you'll never need nil in the array:
def my_function_with_foo(bar=nil)
return ['foo', bar].compact
end
You can use a default value
def fun(a_param = nil)
if a_param
return ['raboof',a_param]
else
return ['raboof']
end
end
or...
def fun(a_param : nil)
if a_param
return ['raboof',a_param]
else
return ['raboof']
end
end
The latter is useful if you have multiple parameters because now when you call it you can just pass in the ones that matter right now.
fun(a_param:"Hooray")
After reading the article http://jeffkreeftmeijer.com/2011/method-chaining-and-lazy-evaluation-in-ruby/, I started looking for a better solution for method chaining and lazy evaluation.
I think I've encapsulated the core problem with the five specs below; can anyone get them all passing?
Anything goes: subclassing, delegation, meta-programming, but discouraged for the latter.
It would be favourable to keep dependencies to a minimum:
require 'rspec'
class Foo
# Epic code here
end
describe Foo do
it 'should return an array corresponding to the reverse of the method chain' do
# Why the reverse? So that we're forced to evaluate something
Foo.bar.baz.should == ['baz', 'bar']
Foo.baz.bar.should == ['bar', 'baz']
end
it 'should be able to chain a new method after initial evaluation' do
foobar = Foo.bar
foobar.baz.should == ['baz', 'bar']
foobaz = Foo.baz
foobaz.bar.should == ['bar', 'baz']
end
it 'should not mutate instance data on method calls' do
foobar = Foo.bar
foobar.baz
foobar.baz.should == ['baz', 'bar']
end
it 'should behave as an array as much as possible' do
Foo.bar.baz.map(&:upcase).should == ['BAZ', 'BAR']
Foo.baz.bar.join.should == 'barbaz'
Foo.bar.baz.inject do |acc, str|
acc << acc << str
end.should == 'bazbazbar'
# === There will be cake! ===
# Foo.ancestors.should include Array
# Foo.new.should == []
# Foo.new.methods.should_not include 'method_missing'
end
it "should be a general solution to the problem I'm hoping to solve" do
Foo.bar.baz.quux.rab.zab.xuuq.should == ['xuuq', 'zab', 'rab', 'quux', 'baz', 'bar']
Foo.xuuq.zab.rab.quux.baz.bar.should == ['bar', 'baz', 'quux', 'rab', 'zab', 'xuuq']
foobarbaz = Foo.bar.baz
foobarbazquux = foobarbaz.quux
foobarbazquuxxuuq = foobarbazquux.xuuq
foobarbazquuxzab = foobarbazquux.zab
foobarbaz.should == ['baz', 'bar']
foobarbazquux.should == ['quux', 'baz', 'bar']
foobarbazquuxxuuq.should == ['xuuq', 'quux', 'baz', 'bar']
foobarbazquuxzab.should == ['zab', 'quux', 'baz', 'bar']
end
end
This is inspired by Amadan's answer but uses fewer lines of code:
class Foo < Array
def self.method_missing(message, *args)
new 1, message.to_s
end
def method_missing(message, *args)
dup.unshift message.to_s
end
end
Trivial, isn't it?
class Foo < Array
def self.bar
other = new
other << 'bar'
other
end
def self.baz
other = new
other << 'baz'
other
end
def bar
other = clone
other.unshift 'bar'
other
end
def baz
other = clone
other.unshift 'baz'
other
end
end
The to_s criterion fails because 1.9 has changed the way Array#to_s works. Change to this for compatibility:
Foo.baz.bar.to_s.should == ['bar', 'baz'].to_s
I want cake.
BTW - metaprogramming here would cut down the code size and increase flexibility tremendously:
class Foo < Array
def self.method_missing(message, *args)
other = new
other << message.to_s
other
end
def method_missing(message, *args)
other = clone
other.unshift message.to_s
other
end
end
I use a lot of iterations to define convenience methods in my models, stuff like:
PET_NAMES.each do |pn|
define_method(pn) do
...
...
end
but I've never been able to dynamically define setter methods, ie:
def pet_name=(name)
...
end
using define_method like so:
define_method("pet_name=(name)") do
...
end
Any ideas? Thanks in advance.
Here's a fairly full example of using define_method in a module that you use to extend your class:
module VerboseSetter
def make_verbose_setter(*names)
names.each do |name|
define_method("#{name}=") do |val|
puts "##{name} was set to #{val}"
instance_variable_set("##{name}", val)
end
end
end
end
class Foo
extend VerboseSetter
make_verbose_setter :bar, :quux
end
f = Foo.new
f.bar = 5
f.quux = 10
Output:
#bar was set to 5
#quux was set to 10
You were close, but you don't want to include the argument of the method inside the arguments of your call to define_method. The arguments go in the block you pass to define_method.
Shoertly if you need it inside one class/module:
I use hash but you can put there array of elements etc.
PETS = {
"cat" => "meyow",
"cow" => "moo",
"dog" => "ruff"
}
def do_smth1(v)
...
end
def do_smth(sound,v)
...
end
#getter
PETS.each{ |k,v| define_method(k){ do_smth1(v) } }
#setter
PETS.each{ |k,v| define_method("#{k}="){|sound| do_smth2(sound, v) }