I'm using Ruby 2.3.4 and rspec 3.6.0.
I'm writing a test for an object that uses rand(10000..99999). I can't find any docs on rand to see what object it's a part of. I tried stubbing Kernel, Object, and Random (see below) but none of my attempts resulted in rand being stubbed for the object.
allow(Kernel).to receive(rand).and_return(12345)
allow(Object).to receive(rand).and_return(12345)
allow(Random).to receive(rand).and_return(12345)
Any help is appreciated.
rand is indeed implemented in the Kernel module. However, when calling the method inside your code, the method receiver is actually your own object.
Assume the following class:
class MyRandom
def random
rand(10000..99999)
end
end
my_random = MyRandom.new
my_random.random
# => 56789
When calling my_random.random, the receiver (i.e. the object on which the method is called on) of the rand method is again the my_random instance since this is the object being self in the MyRandom#random method.
When testing this, you can this stub the rand method on this instance:
describe MyRandom do
let(:subject) { described_class.new }
describe '#random' do
it 'calls rand' do
expect(subject).to receive(:rand).and_return(12345)
expect(subject.random).to eq 12345
end
end
end
This works:
allow_any_instance_of(Object).to receive(:rand).and_return(12345)
Sometimes it can be hard to stub objects that are deep within another. So I find this approach helps simplify things:
class Worker
def self.rand(*args)
# Calls the actual rand method
Kernel.rand(*args)
end
def perform
# Calls private method rand -> Worker.rand
rand(1..5)
end
private
def rand(*args)
self.class.rand(*args)
end
end
This allows us to stub with ease:
allow(Worker).to receive(:rand).and_return(2)
expect(Worker.new.perform).to eq 2
Related
Let's say you have some class like
class Foo
...
public def methodA
x = methodB(true)
# other operations (assume x is not the return value of methodA)
end
private def methodB(arg)
if arg
return 1
else
return 0
end
end
end
When you're writing a unit test for methodA, you want to check that x was assigned the right value, which means you have to check that the call to methodB returned 1 like you expected.
How would you test that in rspec?
Right now I have (with help from How to test if method is called in RSpec but do not override the return value)
#foo = Foo.new
expect(#foo).to receive(:methodB).with(true).and_call_original
But I don't know how to verify the actual return value of methodB.
I think instead of mocking you can just call the private method?
#foo = Foo.new
result = foo.send(:methodB, true)
expect(result).to eq 1
This is basically testing the private method directly. Some people frown upon doing that but if it has lots of logic it's sometimes easier to test it directly. I agree with #spickermann that it's usually best to test the public method and leave the implementation details of the private methods out of the specs.
Don't Test Multiple Methods in a Single Unit Test
You're going about this wrong, because you're creating an unnecessary dependency between two different methods. Instead, you should refactor your code so that:
x is an argument to #methodA,
x, #x, or ##x is a variable accessible to methodA that you can set to whatever expected value you want, or
stub out #methodB for this unit test.
As an example of the last:
describe Foo do
describe 'methodA' do
it 'does something when methodB returns 1' do
# stub the default subject, which is Foo.new
allow(subject).to receive(:methodB) { 1 }
expect(subject.methodA).to eq('foo bar baz')
end
end
end
This code is untested, because your posted code is not a complete, verifiable example. While you'd really be better off refactoring your code so you aren't testing nested depedencies at all, using test doubles or method stubs are certainly viable options.
First of all, this is really just a golf question. My code works fine as it is. But I feel like there is probably a better (i.e. cooler) way to do this.
So I've got a class that acts a lot like a hash. However, it really internally generates a hash for each call to its hash-ish methods. The private method for generating that hash is calculated(). So my code currently has a lot of method definitions like this:
def each(&block)
return calculated.each(&block)
end
def length()
return calculated.length
end
Is there a concise way to delegate all those method calls to the calculated method?
I figured it out and it's incredibly simple. Just delegate to the name of the method. Here's a working example:
class MyClass
extend Forwardable
delegate %w([] []=) => :build_hash
def build_hash
return {'a'=>1}
end
end
edit: don't do this; I forgot Forwardable existed
You can write a "macro" for this. Well, Ruby doesn't technically have actual "macros" but it's a fairly common pattern nonetheless. Rails in particular uses it extensively - stuff like belongs_to, validates, etc are all class methods which are being used to generate instance-level functionality.
module DelegateToFunc
def delegate_to_func(delegate, delegators)
delegators.each do |func_name|
# Note: in Ruby 2.7 you can use |...| instead of |*args, &blk|
define_method(func_name) do |*args, &blk|
send(delegate).send(func_name, *args, &blk)
end
end
end
end
class SequenceBuilder
extend DelegateToFunc
delegate_to_func(:calculated, [:length, :each])
attr_accessor :min, :max
def initialize(min:, max:)
#min, #max = min, max
end
def calculated
min.upto(max).to_a
end
end
SequenceBuilder.new(min: 5, max: 10).length # => 6
SequenceBuilder.new(min: 1, max: 4).each { |num| print num } # => 1234
I will say, though, that methods generated by metaprogramming can sometimes be hard to track down and can make a program confusing, so try and use them tastefully ...
For example, do you really need your object to expose these hash-like methods? Why not just let the caller read the hash via calculated, and then call the hash methods directly on that?
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.
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.
I have a situation for Ruby, where an object is possibly necessary to be created, but it is not sure. And as the creation of the object might be costly I am not too eager creating it. I think this is a clear case for lazy loading. How can I define an object which is not created only when someone sends a message to it? The object would be created in a block. Is there a way for simple lazy loading/initialisation in Ruby? Are these things supported by some gems, which provide different solutions for various cases of lazy initialisation of objects? Thanks for your suggestions!
There are two ways.
The first is to let the caller handle lazy object creation. This is the simplest solution, and it is a very common pattern in Ruby code.
class ExpensiveObject
def initialize
# Expensive stuff here.
end
end
class Caller
def some_method
my_object.do_something
end
def my_object
# Expensive object is created when my_object is called. Subsequent calls
# will return the same object.
#my_object ||= ExpensiveObject.new
end
end
The second option is to let the object initialise itself lazily. We create a delegate object around our actual object to achieve this. This approach is a little more tricky and not recommended unless you have existing calling code that you can't modify, for example.
class ExpensiveObject # Delegate
class RealExpensiveObject # Actual object
def initialize
# Expensive stuff here.
end
# More methods...
end
def initialize(*args)
#init_args = args
end
def method_missing(method, *args)
# Delegate to expensive object. __object method will create the expensive
# object if necessary.
__object__.send(method, *args)
end
def __object__
#object ||= RealExpensiveObject.new(*#init_args)
end
end
# This will only create the wrapper object (cheap).
obj = ExpensiveObject.new
# Only when the first message is sent will the internal object be initialised.
obj.do_something
You could also use the stdlib delegate to build this on top of.
If you want to lazily evaluate pieces of code, use a proxy:
class LazyProxy
# blank slate... (use BasicObject in Ruby 1.9)
instance_methods.each do |method|
undef_method(method) unless method =~ /^__/
end
def initialize(&lazy_proxy_block)
#lazy_proxy_block = lazy_proxy_block
end
def method_missing(method, *args, &block)
#lazy_proxy_obj ||= #lazy_proxy_block.call # evaluate the real receiver
#lazy_proxy_obj.send(method, *args, &block) # delegate unknown methods to the real receiver
end
end
You then use it like this:
expensive_object = LazyProxy.new { ExpensiveObject.new }
expensive_object.do_something
You can use this code to do arbitrarily complex initialization of expensive stuff:
expensive_object = LazyProxy.new do
expensive_helper = ExpensiveHelper.new
do_really_expensive_stuff_with(expensive_helper)
ExpensiveObject.new(:using => expensive_helper)
end
expensive_object.do_something
How does it work? You instantiate a LazyProxy object that holds instructions on how to build some expensive object in a Proc. If you then call some method on the proxy object, it first instantiates the expensive object and then delegates the method call to it.