How can I laugh with a block in minitest?
Hopefully a simple question for MiniTest users.
I have a section of code that I will condense into an example here:
class Foo
def initialize(name)
@sqs = Aws::SQS::Client.new
@id = @sqs.create_queue( queue_name: name ).fetch(:queue_url)
@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
How can I mock / stub / something-else so that I can try message
for testing some_condition()
and possibly be handled with process_msg()
?
those. I want to check @poller.poll(idle_timeout: 60) do |message|
.
I've tried Aws::SQS::QueuePoller#new
mocking with a mock poller, but that doesn't result in a message |message|
just returning it.
This is what I have, not :
mockqueue = MiniTest::Mock.new
mocksqs = MiniTest::Mock.new
mocksqs.expect :create_queue, mockqueue, [Hash]
mockpoller = MiniTest::Mock.new
mockpoller.expect :poll, 'message', [{ idle_timeout: 60 }]
Aws::SQS::Client.stub :new, mocksqs do
Aws::SQS::QueuePoller.stub :new, mockpoller do
queue = Foo.new(opts)
queue.pick_first
end
end
If I get the variable in #pick_first
, then wherever the layout puts it, not in |message|
:
def pick_first
receiver = @poller.poll(idle_timeout: 60) do |message|
process_msg(message) if some_condition(message)
end
puts receiver # this shows my 'message' !!! WHYYYY??
end
source to share
Answering my own question in case anyone has the same question.
I asked for help on this on Twitter, and MiniTest author Ryan Davis (aka @zenspider on github / @ the_zenspider on Twitter) gave a quick response along with an invitation to submit a question to the github mini test ..
I did this , and got a couple of great answers from Ryan as well as Pete Higgins (@phiggins on github), which I reproduce here in full. Thank you both for your help!
@phiggins said:
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 am the enemy. I am almost the enemy in this regard. IMHO, if you can't test something without mocking it, you may have a design problem. Calibrate the appropriate text below.
I suggested using Liskov Substitution Principal (LSP) because I was focusing on testing that process_msg did the right thing in this context. The idea is simple, subclass, override the method in question and use the subclass within your tests. The LSP says that testing a subclass is equivalent to testing a superclass.
In the case of a poll object, you have three issues (polling, filtering and processing) in this method, one of which you shouldn't be testing (because this is third party code). I would 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 simple:
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 / "ruby" ways to do this, but they are equivalent to the above, and point is shown above to be better.
source to share