Stub and Mock Minitest
You can use a lambda to pass the parameter:
class ZombieTest < Minitest::Test
def test_mr_mock
@mock = Minitest::Mock.new
@mock.expect(:roar, 'rawrgerslardar', ['slardar'])
Zombie.stub :roar, ->(a) { @mock.roar(a) } do
Zombie.intimidate
end
@mock.verify
end
end
Minitest: How to stub/mock the file result of Kernel.open on a URL
The second argument to stub
is what you want the return value to be for the duration of your test, but the way Kernel.open
is used here requires the value it yields to the block to be changed instead.
You can achieve this by providing a third argument. Try changing the call to Kernel.stub
to
Kernel.stub :open, true, @mock_file do
#...
Note the extra argument true
, so that @mock_file
is now the third argument and will be yielded to the block. The actual value of the second argument doesn’t really matter in this case, you might want to use @mock_file
there too to more closely correspond to how open
behaves.
How can I mock with a block in minitest?
Answering my own question, in case someone else has the same question.
I asked for help on this via Twitter, and the author of MiniTest, Ryan Davis (aka @zenspider on github / @the_zenspider on Twitter) gave a quick answer along with an invite to submit the question to the MiniTest github issue tracker.
I did so, and got a couple of great responses, from Ryan and also from Pete Higgins (@phiggins on github), which I reproduce here in their entirety. Thank you to both of you for your help!
@phiggins said:
What about something like:
class Foo def initialize(name, opts={})
@sqs = Aws::SQS::Client.new
@id = @sqs.create_queue( queue_name: name ).fetch(:queue_url)
@poller = opts.fetch(:poller) { Aws::SQS::QueuePoller.new(@id) } end
def pick_first
@poller.poll(idle_timeout: 60) do |message|
process_msg(message) if some_condition(message)
end
end
end
# later, in your tests
describe Foo do
it "does the thing in the block" do
# could be moved into top-level TestPoller, or into shared setup, etc.
poller = Object.new
def poller.poll(*) ; yield ; end
foo = Foo.new("lol", :poller => poller)
foo.pick_first
assert foo.some_state_was_updated
end
end
@zenspider said:
NOTE: I'm anti-mock. I'm almost anti-stub for that matter. IMHO, if
you can't test something without mocking it, you probably have a
design issue. Calibrate accordingly against the text below.I suggested using Liskov Substitution Principal (LSP) because I was
focused on testing that process_msg did the right thing in that
context. The idea is simple, subclass, override the method in
question, and use the subclass within the tests. LSP says that testing
a subclass is equivalent to testing the superclass.In the case of the polling object, you have three concerns (polling,
filtering, and processing) going on in that method, one of whom you
shouldn't be testing (because it is third-party code). I'd refactor to
something like this:class Foo
# ....
def poll
@poller.poll(idle_timeout: 60) do |message|
yield message
end
end
def pick_first
poll do |message|
process_msg(message) if some_condition(message)
end
end
end
Then testing is a simple matter:
class TestFoo1 < Foo
def poll
yield 42 # or whatever
end
# ...
end
# ...
assert_equal 42, TestFoo1.new.pick_first # some_condition truthy
assert_nil TestFoo2.new.pick_first # some_condition falsey
There are shorter/"rubyier" ways to do this, but they're equivalent to the above and the above illustrates the point better.
Minitest stub passing block to mock instance
As of now MiniTest tries to guess if the passed val_or_callable
is a Proc
by checking whether it responds to call
, cf.:
- https://apidock.com/ruby/Proc/call
- https://github.com/seattlerb/minitest/blob/b84b8176930bacb4d70d6bef476b1ea0f7c94977/lib/minitest/mock.rb#L226
Unfortunately, in this specific case Redis
as well as the passed MockRedis
-instance both provide a generic call
-method for executing Redis commands, cf.:
- https://github.com/brigade/mock_redis/blob/master/lib/mock_redis.rb#L51
You already found the correct workaround. In this case, your only chance is to explicitly use the proc-version of stub
.
Note: There are some communities using def call
as a pattern with ServiceObjects in Ruby which may have a difficult time using minitest's stub
. It is probably a good idea to open an issue in seattlerb/minitest.
Related Topics
Rails4 Unknown Encoding Name - Cp720
Counter_Cache With Has_Many :Through
How to Remove Blank Elements from an Array
Ruby/Rails - Change the Timezone of a Time, Without Changing the Value
Ruby Local Variable Is Undefined
Looking For Suggestions For Building a Secure Rest API Within Ruby on Rails
Has Anyone Tried Installing Ruby & Rubygems from Source on Ubuntu (Preferably Ubuntu 9)
In Ruby on Rails, Are '#Encoding: Utf-8' and 'Config.Encoding = "Utf-8"' Different
"Rake Assets:Precompile" Gives Punc Error
Why Do I Get a "Permission Denied" Error While Installing a Gem
Ruby Method Array#≪≪ Not Updating the Array in Hash
Iterate Through Every File in One Directory
Why Not Use Shared Activerecord Connections For Rspec + Selenium
Running Another Ruby Script from a Ruby Script
Why Should We Avoid Using Class Variables @@ in Rails
How to Add an Array to Another Array in Ruby and Not End Up With a Multi-Dimensional Result