How to Stub Things in Minitest

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



Leave a reply



Submit