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

      

+3


source to share


1 answer


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.

+3


source