> 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:

Remember that rspec-mocks allows you to pass an implementation block, which
flexibly allows you to do pretty much anything.  In this case you could do:

``` ruby
sequence = []
allow(CollaboratorB).to receive(:bar) { sequence << :bar }
allow(CollaboratorA).to receive(:foo) do |&block|
  sequence << :foo_start
  block.call
  sequence << :foo_end
end

baz

expect(sequence).to eq [:foo_start, :bar, :foo_end, :bar]
```

For the `fetch_product` case, you could do something similar.  Although,
personally I wouldn't test a method like that with mocks at all: I'd tend
to integration test it.

Anyhow, as for adding something more explicit to support this directly from
the rspec-mocks API: I think we'd want to hear that this is a more common
problem with our users to warrant adding that complexity to RSpec.   I
can't think of a time I've ever wanted that and I can't think of any user
requesting it before.  It's a complex thing and the `inside`/`outside`
thing you've come up with reads pretty well but it's pretty different from
the rest of the rspec-mocks API to get a result from setting up a prior
stub and use it in a later one.

HTH,
Myron

On Mon, Feb 15, 2016 at 3:28 PM, Nathan Wenneker <[email protected]>
wrote:

> 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
> <https://groups.google.com/d/msgid/rspec/c9704cee-9a7c-404f-837f-514d90c89dcc%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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/CADUxQmtTQ7Jj8K1EjViJZ2TmhZ11cMdEMnoxnAV%3DOy9Vq%2BTE-Q%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to