rspec stubbing passing the same arguments - ruby

Sample code:
class Foo
def initialize(abc)
#abc = abc
#bind = bar
end
def bar
SomeClass.new(#abc)
end
end
Now I want to stub bar using rspec and custom stub:
allow('Foo').to receive(:bar).and_return(FakeBar.new)
The issue is that the FakeBar.new has to be initialize with the same arguments :bar receives. Is it possible to get a copy of params passed to :bar at the time we are stubbing and reuse them in the stub class?

Not sure why you want to do what you're doing (probably there is a simpler way), but for what it's worth:
allow("Foo").to receive(:bar) { |arg1, arg2| FakeBar.new(arg1, arg2) }
RSpec docs, block stub implementation

Related

Easily create an Enumerator

When creating methods that yield, sometimes we want it to return an Enumerator if no block is given. The recommended way is basically return to_enum(:name_of_method, [args]) unless block_given?. However, it's a pain to have to type that for every method that does this. Ruby being ruby, I decided to create a make_enum method, similar to attr_accessor, which does this for me:
class Module # Put this in a mixin, but for the purposes of this experiment, it's in Module
def make_enum *args
args.each do |name|
old_method = instance_method(name)
define_method(name) do |*args, &block|
next to_enum(name, *args) unless block
old_method.bind(self).call(*args, &block)
end
end
end
end
Now I can use it like so:
class Test
def test
yield 1
yield 2
end
make_enum :test
end
t = Test.new
t.test { |n| puts n }
# 1
# 2
t.test.to_a #=> [1, 2]
And it works! But it doesn't work if make_enum is before the method definition.
How can I get this method to work before defining a method, so that the following works? Perhaps I need to make use of method_added?
class Test
make_enum :test
def test
yield 1
yield 2
end
end
I don't know if it's a bad idea for it to be before the method, but my reason for thinking that it would be nice to do that is that it better matches the way we use attr_accessor and the like.
Whereas attr_ methods create instance methods newly, your make_enum modifies an existing method, which is rather similar to protected, private, and public methods. Note that these visibility methods are used either in the form:
protected
def foo; ... end
or
protected def foo; ... end
or
def foo; ... end
protected :foo
The latter two ways are already available with your make_enum. Especially, the second form is already possible (which Stefan also notes in the comment). You can do:
make_enum def test; ... end
If you want to do the first form, you should try to implement that in your make_enum definition.

Ruby + Rspec + OpenStruct weird behavior

So i'm experiencing this weird behavior while testing a ruby class. I'm using rspec 3 to test it by the way.
Class Foo has a method 'fetch_object' which calls the 'find' method from class Bar to retrieve an object and than calls the method 'fail' from the fetched object.
The so called weird behavior happens when i expect to receive the method 'fail' once and receive none but if I change the method name for 'faill' it works like a charm :S
here is the drama:
require 'ostruct'
class Foo
def fetch_object
foobar = Bar.find
foobar.fail
end
end
class Bar
def self.find
OpenStruct.new(name: 'Foo Bar')
end
end
describe Foo do
subject { Foo.new }
let(:foo) { OpenStruct.new() }
before do
expect(Bar).to receive(:find).and_return(foo)
end
it 'fetch object with name' do
expect(foo).to receive(:fail)
subject.fetch_object
end
end
I suspect it's because you are setting an expectation on object, which behaviour depends on method_missing (OpenStruct).
For that reason I wouldn't want it as a mock, I would use regular mock (and spec will pass):
let(:foo) { double('foobar') }
You are testing here, if returned object (result of Bar.find) will receive an expected message, without going into implementation details.
Setting expectations on Dynamic classes like ostruct may lead to strange results. It seems that at some point a Kernel#fail method is invoked, thus, changing a name to faill or any other that is not already "taken" by Kernel will make it work.
Other solution would be monkeypatching OpenStruct to avoid method_missing beeing called:
class OpenStruct
def fail
true
end
end
class Foo
def fetch_object
foobar = Bar.find
foobar.fail
end
end
class Bar
def self.find
OpenStruct.new(name: 'Foo Bar')
end
end
describe Foo do
subject { Foo.new }
let(:foo) { OpenStruct.new }
before do
expect(Bar).to receive(:find).and_return(foo)
end
it 'fetch object with name' do
expect(foo).to receive(:fail)
subject.fetch_object
end
end
But I don't know why would you want to do that ;)
More info: Doubles and dynamic classess

How to assert certain method is called with Ruby minitest framework?

I want to test whether a function invokes other functions properly with minitest Ruby, but I cannot find a proper assert to test from the doc.
The source code
class SomeClass
def invoke_function(name)
name == "right" ? right () : wrong ()
end
def right
#...
end
def wrong
#...
end
end
The test code:
describe SomeClass do
it "should invoke right function" do
# assert right() is called
end
it "should invoke other function" do
# assert wrong() is called
end
end
Minitest has a special .expect :call for checking if some method is called.
describe SomeClass do
it "should invoke right function" do
mocked_method = MiniTest::Mock.new
mocked_method.expect :call, return_value, []
some_instance = SomeClass.new
some_instance.stub :right, mocked_method do
some_instance.invoke_function("right")
end
mocked_method.verify
end
end
Unfortunately this feature is not documented very well. I found about it from here: https://github.com/seattlerb/minitest/issues/216
With minitest you use expect method to set the expectation for a method to be called on a mock object like so
obj = MiniTest::Mock.new
obj.expect :right
If you want to set expectation with parameters and return values then:
obj.expect :right, return_value, parameters
And for the concrete object like so:
obj = SomeClass.new
assert_send([obj, :right, *parameters])
According to the given example, there is no other delegate class, and you want to make sure the method is called properly from the same class. Then below code snippet should work:
class SomeTest < Minitest::Test
def setup
#obj = SomeClass.new
end
def test_right_method_is_called
#obj.stub :right, true do
#obj.stub :wrong, false do
assert(#obj.invoke_function('right'))
end
end
end
def test_wrong_method_is_called
#obj.stub :right, false do
#obj.stub :wrong, true do
assert(#obj.invoke_function('other'))
end
end
end
end
The idea is to stub [method_expect_to_be_called] by returning a simple true value, and in the stub block assert it's indeed being called and returning the true value. To stub the other unexpected method is just to make sure that it's not being called.
Note: assert_send won't work correctly. Please refer to official doc.
In fact, below statement will pass, but doesn't mean it's working as expected:
assert_send([obj, :invoke_function, 'right'])
# it's just calling invoke_function method, but not verifying any method is being called
To stub and assert method calls, you use MiniTest::Mock. There are 2 ways to use this:
stub an object's method to return a mock object, which has a stubbed method
stub an object's method to call the mock method
test "return the mock object when calling the stubbed method" do
# the object you want to stub
obj = Book.new
mock = MiniTest::Mock.new
mock.expect :the_method_to_stub, "my cool return value"
obj.stub :method_that_gives_you_a_mock, mock do
x = obj.method_that_gives_you_a_mock
assert_equal x.the_method_to_stub, "my cool return value"
end
# assert that the method was called once
mock.verify
end
test "call the mock method when calling the stubbed method" do
# the object you want to stub
obj = Book.new
mock = MiniTest::Mock.new
# use :call to make the mock a callable
mock.expect :call, "my cool return value"
obj.stub :method_that_calls_the_mock, mock do
assert_equal obj.method_that_calls_the_mock, "my cool return value"
end
# assert that the method was called once
mock.verify
end
To use MiniTest::Mock, you may need to add require 'minitest/autorun' to load the MiniTest constants.
Recently I've created a gem for easing this kind of assertions called 'stubberry'.
Here how you can manage the needed behaviour using it.
First you need to answer the questions:
do you have an access to the original object before the test sequence
execution?
is there any indirect way you can sure call happened? i.e. there should be some methods invocations on some other object you have access to.
do you need the method to be actually called or could it be stubbed with the prooper object or callable?
If you have access to the object, and you can stub the method, with your callable:
obj.stub_must( :right, -> { stub_response } ) {
... do something
}
If you have access to the object but you don't want to stub the method and you just want to ensure that method was invoked:
assert_method_called( obj, :right ) {
.... do something with obj
}
If you don't have the access to the object you want to test against.
But you can do indirect check with some other object method invocation, Lets say 'right' method will end with API call execution:
API.stub_must( :get, -> (path, params) {
assert_equal( path, :expected_path )
assert_equal( params, {} )
} ) do
... do something
end
If you can't do an indirect check:
stunt_boudle = Obj.new
stunt_boudle.stub_must( :right, -> () {
#do needed assertions
} ) do
Obj.stub_must(:new, stunt_boudle) do
# do some integration testing
end
end
# OR use assert_method_called the same way
Also there is a cool set of stubbing ActiveRecord object by id, you can use them in this case when you can't have access to the object at the start of the testing actions and its an ActiveRecord object.

Mocking a dynamically-generated class in ruby metaprogramming with rspec

I'm new to TDD and metaprogramming so bear with me!
I have a Reporter class (to wrap the Garb ruby gem) that will generate a new report class on-the-fly and assign it to a GoogleAnalyticsReport module when I hit method_missing. The main gist is as follows:
# Reporter.rb
def initialize(profile)
#profile = profile
end
def method_missing(method, *args)
method_name = method.to_s
super unless valid_method_name?(method_name)
class_name = build_class_name(method_name)
klass = existing_report_class(class_name) ||
build_new_report_class(method_name, class_name)
klass.results(#profile)
end
def build_new_report_class(method_name, class_name)
klass = GoogleAnalyticsReports.const_set(class_name, Class.new)
klass.extend Garb::Model
klass.metrics << metrics(method_name)
klass.dimensions << dimensions(method_name)
return klass
end
The type of 'profile' that the Reporter expects is a Garb::Management::Profile.
In order to test some of my private methods on this Reporter class (such as valid_method_name? or build_class_name), I believe I want to mock the profile with rspec as it's not a detail that I'm interested in.
However, the call to klass.results(#profile) - is executing and killing me, so I haven't stubbed the Garb::Model that I'm extending in my meta part.
Here's how I'm mocking and stubbing so far... the spec implementation is of course not important:
describe GoogleAnalyticsReports::Reporter do
before do
#mock_model = mock('Garb::Model')
#mock_model.stub(:results) # doesn't work!
#mock_profile = mock('Garb::Management::Profile')
#mock_profile.stub!(:session)
#reporter = GoogleAnalyticsReports::Reporter.new(#mock_profile)
end
describe 'valid_method_name' do
it 'should not allow bla' do
#reporter.valid_method_name?('bla').should be_false
end
end
end
Does anyone know how I can stub the call to the results method on my newly created class?
Any pointers will be greatly appreciated!
~ Stu
Instead of:
#mock_model = mock('Garb::Model')
#mock_model.stub(:results) # doesn't work!
I think you want to do:
Garb::Model.any_instance.stub(:results)
This will stub out any instance of Garb::Model to return results. You need to do this because you are not actually passing #mock_model into any class/method that will use it so you have to be a bit more general.

How to test a method call in a constructor with rspec

i have a constructor like this:
class Foo
def initialize(options)
#options = options
initialize_some_other_stuff
end
end
and want to test the call to initialize_some_other_stuff if a instantiate a new Foo object.
I found this question rspec: How to stub an instance method called by constructor? but the suggested solution to call Foo.any_instance(:initialize_some_other_stuff) does not work in my rspec version (2.5.0).
Can anyone help me to test this constructor call?
In you spec, you could have the following:
class Foo
attr_reader :initializer_called
def initialize_some_other_stuff
#initializer_called = true
end
end
foo = Foo.new
foo.initializer_called.should == true
If the constructor calls the initiaize_some_other_stuff method, foo.initializer_called should be true.
Here you go:
stub_model(Foo).should_receive(:some_method_call).with(optional_argument)
Since the initialize_some_other_stuff method is private to the class, you should not care if it executes or not. That said, if that method performs some expensive operation that you don't want your test waiting for, then it is quite okay to mock that operation.
So, if Foo looked like this:
class Foo
attr_reader :options, :other_stuff
def initialize(options)
#options = options
initialize_some_other_stuff
end
def initialize_some_other_stuff
#other_stuff = Bar.new.long_running_operation
end
end
Then you could mock out the call to Bar#long_running_operation like this:
describe Foo do
subject(:foo) { described_class.new(options) }
let(:options) { 'options' }
let(:bar) { instance_double(Bar, long_running_operation: 42) }
before do
allow(Bar).to receive(:new).and_return(bar)
foo
end
it 'initializes options' do
expect(foo.options).to eq(options)
end
it 'initializes other stuff' do
expect(foo.other_stuff).to eq(bar.long_running_operation)
end
end
Now, you're testing the assignments. But, you're not waiting on the expensive operation to complete.

Resources