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.
