如何在minitest中模拟一个块? [英] How can I mock with a block in minitest?
问题描述
希望对MiniTest员工来说是一个简单的问题.
Hopefully a simple question for MiniTest folks..
我有一段代码,我将在此处浓缩为一个示例:
I have a section of code which I'll 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
我该如何模拟/存根/其他方式,以便可以通过some_condition()
进行测试并可能由process_msg()
处理,从而馈入message
?
How can I mock/stub/something-else so that I can feed a message
through to be tested by the some_condition()
and possibly processed with process_msg()
?
即我要测试@poller.poll(idle_timeout: 60) do |message|
.
我试图用模拟轮询器对Aws::SQS::QueuePoller#new
进行存根处理,但是它并没有将消息返回给|message|
.
I have tried to stub the Aws::SQS::QueuePoller#new
with a mock poller, but it's not yielding the message to |message|
just returning it..
这是我所拥有的,不在工作:
This is what I have, which is not working:
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
如果我在#pick_first
中收到一个变量,那就是模拟将其放置在其中的地方,而不是放在|message|
中:
If I receive a variable in #pick_first
, that's where the mock puts it, not into |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
推荐答案
回答我自己的问题,以防其他人有相同的问题.
Answering my own question, in case someone else has the same question.
我通过Twitter寻求帮助,MiniTest的作者Ryan Davis(又名github上的@zenspider/Twitter上的@the_zenspider)给出了快速解答,并邀请将问题提交给MiniTest github问题跟踪器
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.
我这样做了,并得到了瑞安(Ryan)和来自Pete Higgins(在github上为@phiggins),我在此全文进行复制.谢谢你们俩的帮助!
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 说:
那又怎么样呢?
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 说:
注意:我是反嘲讽的.我在这件事上几乎是反对.恕我直言,如果 您不能在不进行模拟的情况下测试某些东西,您可能有一个 设计问题.根据下面的文字进行相应的校准.
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.
我建议使用Liskov替代校长(LSP),因为我当时 专注于测试process_msg在其中所做的正确的事情 语境.这个想法很简单,子类化,覆盖了方法中的 问题,并在测试中使用子类. LSP说测试 子类等效于测试超类.
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
然后测试很简单:
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中模拟一个块?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!