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.
Related
I'm trying to mock a class, so that I can expect it is instantiated and that a certain method is then called.
I tried:
expect(MyPolicy).
to receive(:new).
and_wrap_original do |method, *args|
expect(method.call(*args)).to receive(:show?).and_call_original
end
But all I'm getting is:
undefined method `show?' for #RSpec::Mocks::VerifyingMessageExpectation:0x0055e9ffd0b530
I've tried providing a block and calling the original methods first (both :new and :show?, which I had to bind first), but the error is always the same.
I know about expect_any_instance_of, but it's considered code-smell, so I'm trying to find another way to do it properly.
Context: I have pundit policies and I want to check whether or not it has been called
I also tried, with the same error:
ctor = policy_class.method(:new)
expect(policy_class).
to receive(:new).
with(user, record) do
expect(ctor.call(user, record)).to receive(query).and_call_original
end
You broke MyPolicy.new.
Your wrapper for new does not return a new MyPolicy object. It returns the result of expect(method.call(*args)).to receive(:show?).and_call_original which is a MessageExpectation.
Instead, you can ensure the new object is returned with tap.
# This is an allow. It's not a test, it's scaffolding for the test.
allow(MyPolicy).to receive(:new)
.and_wrap_original do |method, *args|
method.call(*args).tap do |obj|
expect(obj).to receive(:show?).and_call_original
end
end
Or do it the old fashioned way.
allow(MyPolicy).to receive(:new)
.and_wrap_original do |method, *args|
obj = method.call(*args)
expect(obj).to receive(:show?).and_call_original
obj
end
It is often simpler to separate the two steps. Mock MyPolicy.new to return a particular object and then expect the call to show? on that object.
let(:policy) do
# This calls the real MyPolicy.new because policy is referenced
# when setting up the MyPolicy.new mock.
MyPolicy.new
end
before do
allow(MyPolicy).to receive(:new).and_return(policy)
end
it 'shows' do
expect(policy).to receive(:show?).and_call_original
MyPolicy.new.show?
end
This does mean MyPolicy.new always returns the same object. That's an advantage for testing, but might break something. This is more flexible since it separates the scaffolding from what's being tested. The scaffolding can be reused.
RSpec.describe SomeClass do
let(:policy) {
MyPolicy.new
}
let(:thing) {
described_class.new
}
shared_context 'mocked MyPolicy.new' do
before do
allow(MyPolicy).to receive(:new).and_return(policy)
end
end
describe '#some_method' do
include_context 'mocked new'
it 'shows a policy' do
expect(policy).to receive(:show?).and_call_original
thing.some_method
end
end
describe '#other_method' do
include_context 'mocked MyPolicy.new'
it 'checks its policy' do
expect(policy).to receive(:check)
thing.other_method
end
end
end
Finally, inaccessible constructor calls are a headache both for testing, and they're inflexible. It's a default which cannot be overridden.
class SomeClass
def some_method
MyPolicy.new.show?
end
end
Turn it into an accessor with a default.
class SomeClass
attr_writer :policy
def policy
#policy ||= MyPolicy.new
end
def some_method
policy.show?
end
end
Now it can be accessed in the test or anywhere else.
RSpec.describe SomeClass do
let(:thing) {
described_class.new
}
describe '#some_method' do
it 'shows its policy' do
expect(thing.policy).to receive(:show?).and_call_original
thing.some_method
end
end
end
This is the most robust option.
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
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
I am having a method which returns the price of a given symbol and i am writing a test for that method.
This is my test
def setup
#asset = NetAssetValue.new
end
def test_retrieve_price_for_symbol_YHOO
assert_equal(33.987, #asset.retrieve_price_for_a_symbol('YHOO'))
end
def test_retrive_price_for_YHOO
def self.retrieve_price_for_a_symbol(symbol)
33.77
end
assert_equal(33.97, #asset.retrieve_price_for_a_symbol('YHOO'))
end
This is my method.
def retrieve_price_for_a_symbol(symbol)
symbol_price = { "YHOO" => 33.987, "UPS" => 35.345, "T" => 80.90 }
raise Exception if(symbol_price[symbol].nil?)
symbol_price[symbol]
end
I am trying to mock the retrieve_price_for_a_symbol method by writing same method in test class but when i call it, the call is happening to method in main class not in the test class.
How do I add that method to meta class from test and how do i call it? Please help.
Instead of re-defining the method inside, you need to mock it out.
Replace the method definition inside the test with
#asset.expects(:retrieve_price_for_a_symbol).with('YHOO').returns(33.97)
Assuming you don't really want to mock the method you're testing...
You are currently defining your mock on the instance of the test class. You can add your mock directly to the #asset object:
def test_retrive_price_for_YHOO
def #asset.retrieve_price_for_a_symbol(symbol)
33.77
end
assert_equal(33.97, #asset.retrieve_price_for_a_symbol('YHOO'))
end
Working in Sinatra, a local object request is created and made available to all views and helpers. So, I can make an ApplicationHelper module with helper methods, and if the helper methods are called in the view they can in turn call the request object, like so:
module ApplicationHelper
def nav_link_to(text,path)
path == request.path_info ? klass = 'class="current"' : klass = ''
%Q|<a href="#{path}" #{klass}>#{text}</a>|
end
end
Now, I want to test this, but in my test the request object doesn't exist. I tried to mock it, but that didn't work. Here's my test so far:
require 'minitest_helper'
require 'helpers/application_helper'
describe ApplicationHelper do
before :all do
#helper = Object.new
#helper.extend(ApplicationHelper)
end
describe "nav links" do
before :each do
request = MiniTest::Mock.new
request.expect :path_info, '/'
end
it "should return a link to a path" do
#helper.nav_link_to('test','/test').must_equal 'test'
end
it "should return an anchor link to the current path with class 'current'" do
#helper.nav_link_to('test','/').must_equal 'test'
end
end
end
So, how can you mock a 'local' object so that the code your testing can call it?
You need to make sure there is a request method on your #helper object that returns the mock request object.
In RSpec I'd just stub it. I'm not particularly familiar with Minitest, but a quick look suggests that this might work in recent versions (if you change request to #request in your before :each):
it "should return a link to a path" do
#helper.stub :request, #request do
#helper.nav_link_to('test','/test').must_equal 'test'
end
end
Update
Since Minitest requires that the stubbed method is already defined on the object, you could make #helper an instance of Struct.new(:request) instead of Object, i.e.
#helper = Struct.new(:request).new
And actually, having done that, you might not need the stub at all! You could just do
before :each do
#helper.request = MiniTest::Mock.new
#helper.request.expect :path_info, '/'
end