I’m not sure I grok how the communication pattern is complex.

It’s not super complex but calling 2 methods (new followed by create on the
returned object) is more complex than calling one (create_from_vendor).
any_instance makes this communication pattern look simpler than it actually
is in the test, and is thus less honest than the more verbose form.

Going to lengths to avoid any_instance and its variations, I had come up
with:

Is there a reason you feel the need to use mocking or stubbing at all when
testing create_from_vendor? At some point, you’ve got test the underlying
operations w/o mocking or stubbing them…so presumably you have tests for
the create method. Why not simply update that test to use
ProjectAccount.create_from_vendor rather than ProjectAccount#create? Then
there’s no need for the gymnastics of any_instance or the stubbing of new,
and your test makes it explicit that clients that need this operation
should use create_from_vendor to achieve it.

I bulk a little at stubbing the ::new method, as it is a fundamental to the
OOP language.

FWIW, I don’t consider new to be special in any way. It’s not a keyword.
It’s just a message like any other.

One strange point, (and maybe I’m not grokking what you are saying) is that
it seems that your example of returning a double, is stubbing the
object-under-test, and then you stub the :create method, which is stubbing
the method on the object-under-test.

To be clear, the example snippet that I sent (e.g. stubbingnew and the
expecting create) is *not* what I recommend. I recommend you have your
tests for ProjectAccount drive the interaction with it via
create_from_vendor with out using mocking or stubbing (or perhaps injecting
some test double collaborators as needed…but certainly not stubbing or
mocking any ProjectAccount class or instance methods). And then in any
other tests of code that needs to create a project account from a vendor,
you can simply and easily mock ProjectAccount.create_from_vendor.

The snippet was just meant to show that if it’s important to you to not
update your interfaces to create easily mockable “seams” between your
components, there’s an alternative to any_instance.

Further, I can see problems with stubbing the object-under-test, but not
> with writing an expectation to call a method when I am trying to verify
> that the method gets called.

To be clear, code smells are indication of possible problems, and not a
judgement that every single example of that smell is a problem. Anytime you
stub or use a message expectation on the object-under-test, you are
modifying it to behave differently than the real object behaves, and tests
of such objects are generally less trustworthy than tests of unmodified
objects. More generally, I fine my tests easier to reason about when
there’s an explicit separation between the object I am testing (which has
not been modified with a test-specific extension) and the surrounding
environment (which generally has been).

All that said: a lot of this depends upon your testing philosophy and what
level of granularity you are going for. I aim for unit tests that are very
fast (largely by avoiding IO dependencies or using injected test doubles to
replace them) while still being kinda course-grained so that the test is
coupled to a very small API surface area to facilitate future refactoring.
Others view the “unit” of a “unit test” as being an individual method, and
in such a philosophy, you might not view stubbing the object-under-test as
problematic since you are focused on testing a particular method, not a
particular object.

Myron
​

On Thu, May 28, 2015 at 12:07 PM, Marlin Pierce <[email protected]>
wrote:

> Yes that does help.
>
> I understand the second bullet point.  It might not be a bug if the create
> was called with a different vendor object, but it would be unless the other
> vendor object represented the same vendor in reality, (same name, same
> primary key).
>
> I'm not sure I grok how the communication pattern is complex.
>
> Maybe create is not the right name for the method.  It's more like write,
> or transmit.  Really, its transmit the call to the server for a create
> action.
>
> The design of the code is for a ProspectAccount object to initialize its
> state from a given Vendor object.  Then, knowing its state, send the create
> information to the web server acting as our queuing system.
>
>
> Going to lengths to avoid any_instance and its variations, I had come up
> with:
>
> let(:vendor) { ::Vendor.new(...) }
> let(:prospect) { ProspectAccount.new(vendor) }
>
> it 'calls create instance method' do
>   allow(ProspectAccount).to receive(:new).and_return(prospect)
>   expect(prospect).to receive(:create).and_return(true)
>
>   ProspectAccount.create_from_vendor(epoch, vendor)
> end
>
> I bulk a little at stubbing the ::new method, as it is a fundamental to
> the OOP language.
>
>
> One strange point, (and maybe I'm not grokking what you are saying) is
> that it seems that your example of returning a double, is stubbing the
> object-under-test, and then you stub the :create method, which is stubbing
> the method on the object-under-test.
>
> Further, I can see problems with stubbing the object-under-test, but not
> with writing an expectation to call a method when I am trying to verify
> that the method gets called.
>
>
>
> class BatchController
>   def create_accounts(epoch, start_time)
>     vendors = Vendor.new_vendors(start_time)
>
>     vendors.each do |vendor|
>       ProspectAccount.new(vendor).create(epoch)
>     end
>   end
> ...
> end
>
> describe '#create' do
>
>   let(:vendors) { [ ::Vendor.new(...), ::Vendor.new(...), ::Vendor.new
> (...) ] }
>   let(:prospect) { ProspectAccount.new(vendor) }
>
>   it 'calls create instance method' do
>     allow(Vendor).to receive(:new_vendors).and_return(vendors)
>     expect_any_instance_of(ProspectAccount).to receive(:create).exactly(3
> ).times.and_return(true)
>
>     BatchController.create(epoch, start_time)
>   end
> end
>
>
> Here, it does not seem that I'm stubbing the object-under-test.
>
> Maybe I am, since I am stubbing every instance of ProspectAccount, and I'm
> testing if those objects call the #create method.
>
>  --
> 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/bcb391cc-989b-499a-83b5-6fb1174220e7%40googlegroups.com
> <https://groups.google.com/d/msgid/rspec/bcb391cc-989b-499a-83b5-6fb1174220e7%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/CADUxQmvM9XN6yaZ0TMq6vS3iEJ_Bp6eybJHfkoK4hrdv9-W%2BWQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to