I know I'm replying to an old thread, but it seemed relevant.
Suppose we want to unit test in pure isolation a method that passes a block
to a collaborator. For example:
def baz
CollaboratorA.foo do
CollaboratorB.bar
end
end
It's important not only that `baz` calls `CollaboratorB.bar`, but that it
does it inside the block. Myron's gist
<https://gist.github.com/myronmarston/e79cbff12ce51b54814b> from a year ago
answers this question well. However, what if we take this a step further,
and say that we want to call `CollaboratorB.bar` once inside the block and
once outside the block like this:
def baz
CollaboratorA.foo do
CollaboratorB.bar
end
CollaboratorB.bar
end
Furthermore, let's suppose we want to assemble the return values from these
two `bar` calls. This isn't a Rails specific question, but I'll use
`ActiveRecord::Base.unscoped` (assuming a default_scope is set) as a
real-world example that I hope will be easily understood:
def fetch_products
visible_products = Product.all
all_products = Product.unscoped do
Product.all
end
{ visible_products: visible_products, all_products: all_products }
end
I know that an isolated test for this will be tightly coupled to the
implementation, but assuming that's what we want to do, I imagined a new a
new `inside` and `outside` API for rspec-mocks, and this is how I would
test `fetch_products`:
it "fetches products" do
all_products = double("all products")
visible_products = double("visible products")
unscoped = allow(Product).to receive(:unscoped).and_yield
allow(Product).to
receive(:all).inside(unscoped).and_return(all_products)
allow(Product).to
receive(:all).outside(unscoped).and_return(visible_products)
expect(fetch_products).to eq({
visible_products: visible_products,
all_products: all_products
})
end
If you didn't have return values to compare against, you could also write
something like:
expect(Product).to have_received(:all).inside(unscoped).once
expect(Product).to have_received(:all).outside(unscoped).once
So I have a two questions:
1. Is there a straightforward way to write this spec using the current API?
2. If not, would you be open to a PR that adds the `inside` and `outside`
API, or open to discussing an alternative API that would make this kind of
spec feasible?
I have a working prototype locally that enhances `@messages_received` to
track what existing message expectations are running an implementation when
a subsequent message is called, which can then be compared to to
constraints.
Thank you,
Nathan
On Thursday, March 5, 2015 at 9:16:31 PM UTC-7, Myron Marston wrote:
>
> On Thursday, March 5, 2015 at 7:06:58 PM UTC-8, Joe Van Dyk wrote:
>>
>> Say I have the following method:
>>
>> def run
>> transaction do
>> mark_non_eligible
>> make_invoice_batch
>> send_batch_email
>> end
>> end
>>
>> How can I test that with rspec's mocks?
>>
>> Joe
>>
>
> There's a rarely-used feature of `and_yield` that can help you with this.
> If you pass a block to `and_yield`, RSpec will pass your block an object
> that it will use to instance_eval the `transaction` block, allowing you to
> set message expectations on it. I put together an example gist:
>
> https://gist.github.com/myronmarston/e79cbff12ce51b54814b
>
> This works, but bear in mind there are a number of code smells here
> (mocking the object-under test, making a test that simply mirrors the
> implementation, etc). IMO, you'd be better off testing this with an
> integrated test that hits the DB. You could, for example, stub whatever
> collaborator `send_batch_email` delegates to so that it fails, and check
> that the database updates performed by `mark_non_eligible` and
> `make_invoice_batch` are rolled back.
>
> HTH,
> Myron
>
--
You received this message because you are subscribed to the Google Groups
"rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/rspec/c9704cee-9a7c-404f-837f-514d90c89dcc%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.