Thank you for all of the extra details. BTW, The create is not the ActiveRecord create. Also we are not using Rails, just ruby. We don’t have a view. The closest we come to a controller object/class is the class that the create_accounts method is in. Say for the sake of argument, that that class is named BatchController.
That’s fine. We don’t need to pretend it is Rails at all. I was just going off the very common AR create interface on that guess. To avoid using any_instance, I rewrote the call inside the loop to ProspectAccount.create_from_vendor(epoch, vendor), but that only moves the problem to the create_from_vendor method. It’s difficult to judge things based only off of this small code snippet. Without a better understanding of your domain models (nothing to do with Rails or ActiveRecord) and how they are related I can only guess at some alternatives. Given your latest sample it seems really that you are writing some sort of service object. I agree with Myron. Given it seems that a prospect account is tightly coupled to a vendor and an epoch; and you’ve stated create is of your own API design, I would pose the question: _”Is there a need to ever call create directly on a ProspectAccount instance; outside of create_from_vendor?” If the answer is “no”. I would strongly suggest going with what Myron proposed. Make create a private implementation detail and promote the tests for it up to the create_from_vendor call. Then only use create_from_vendor in the code. For example: class ProspectAccount def self.create_from_vendor(epoch, vendor) new(vendor).create(epoch) end private def create(epoch) # ... endend RSpec.describe ProspectAccount do describe "creating an account from a vendor with epoch" do # move all tests you had for `create` here endend Given all of your well thought out reasoning behind the design decisions so far, if the answer is “yes” we could approach things from another angle. Based only on these snippets, it seems you cannot have a ProspectAccount instance without a Vendor instance. We could also consider an API design with: class Vendor def self.generate_prospects(start_time) new_vendors(start_time).map { |v| ProspectAccount.new(v) } endend RSpec.describe Vendor do it "with no matching start time generating prospects returns an empty array" do expect(Vendor.generate_prospects(some_non_matching_time)).to eq [] end it "with a matching start time generating prospects returns the new prospects" do # This may need a custom matcher or some sort of equality checker for prospects expect(Vendor.generate_prospects(matching_time)).to match_array some_expected_prospects endend That wraps the factory up to the thing which the prospect depends on. This frees up your service object to be: class BatchController def self.create_accounts(epoch, start_time) Vendor.generate_prospects(start_time).each do |prospect| prospect.create(epoch) end end end RSpec.describe BatchController do context "with some prospects" do it "creating accounts transmits each prospect" do a_start_time = :start_time an_epoch = :epoch prospects = [spy(ProspectAccount), spy(ProspectAccount)] allow(Vendor).to receive(:generate_prospects).with(a_start_time).and_return(prospects) BatchController.create_accounts(an_epoch, a_start_time) prospects.each { |prospect| expect(prospect).to have_received(:create).with(an_epoch) } end endend On Thu, May 28, 2015 at 3:07 PM, Marlin Pierce <mpie...@capterra.com> 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 rspec+unsubscr...@googlegroups.com. > To post to this group, send email to rspec@googlegroups.com. > 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 rspec+unsubscr...@googlegroups.com. To post to this group, send email to rspec@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/rspec/CAKCESdjfYOmQ_aaLKnfxuQrHKx8%2Bea%3Dh%2BhMDkbx0w5hg84Ff0Q%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.