Search code examples
ruby-on-railsrspecrspec-railsstubbing

Rspec - stubbing two classes that include the same module throws an error


I'm having a bit of a weird stubbing problem with Rspec. I have two classes that are functionally similar, that both include a module:

class Transaction
  module Overview
  ...
  end
end

class Actual
  class Overview
    include Transaction::Overview
  end
end

class Adjustment
  class Overview
    include Transaction::Overview
  end
end

I’m using them both in a method and attempting to stub them in a test like this:

actual_quarters = Actual::Overview::AllQuarters.new(actuals)
actual_overview = double("Actual::Overview", all_quarters: actual_quarters, value_for_report_quarter: 0)
expect(Actual::Overview).to receive(:new).with(activity_presenter, report_presenter).at_least(:once).and_return(actual_overview)

adjustment_quarters = Adjustment::Overview::AllQuarters.new(adjustments)
adjustment_overview = double("Adjustment::Overview", all_quarters: adjustment_quarters, value_for_report_quarter: 0)
expect(Adjustment::Overview).to receive(:new).with(activity_presenter, report_presenter).at_least(:once).and_return(adjustment_overview)

However, running the test gives me the error:

  1) Report::Export Report::Export::Row includes the actuals for the previous quarters
     Failure/Error: expect(Adjustment::Overview).to receive(:new).with(activity_presenter, report_presenter).at_least(:once).and_return(adjustment_overview)
       Transaction::Overview does not implement: new

If I reverse the order of the stubs like so:

adjustment_quarters = Adjustment::Overview::AllQuarters.new(adjustments)
adjustment_overview = double("Adjustment::Overview", all_quarters: adjustment_quarters, value_for_report_quarter: 0)
expect(Adjustment::Overview).to receive(:new).with(activity_presenter, report_presenter).at_least(:once).and_return(adjustment_overview)

actual_quarters = Actual::Overview::AllQuarters.new(actuals)
actual_overview = double("Actual::Overview", all_quarters: actual_quarters, value_for_report_quarter: 0)
expect(Actual::Overview).to receive(:new).with(activity_presenter, report_presenter).at_least(:once).and_return(actual_overview)

I get this error:

  1) Report::Export Report::Export::Row includes the actuals for the previous quarters
     Failure/Error: expect(Actual::Overview).to receive(:new).with(activity_presenter, report_presenter).at_least(:once).and_return(actual_overview)
       Transaction::Overview does not implement: new

Which suggests that Rspec doesn’t like me stubbing classes that include the same module in the same test. Is there any way round this, or something I’m doing wrong?


Solution

  • Try using an instance_double instead. Without having seen your code and the test setup, try changing your test code to:

    # is this the class you are testing? if so, use described_class.new(actuals) instead
    actual_quarters = Actual::Overview::AllQuarters.new(actuals) 
    
    actual_overview_double = instance_double(Actual::Overview, all_quarters: actual_quarters)
    
    # I don't think this should be at_least(:once) - if it is being instantiated more than once, you may need another instance_double
    expect(Actual::Overview).to receive(:new).with(activity_presenter, report_presenter).and_return(actual_overview_double)
    
    # again - should this be described_class?
    adjustment_quarters = Adjustment::Overview::AllQuarters.new(adjustments)
    
    adjustment_overview_double = instance_double(Adjustment::Overview, all_quarters: adjustment_quarters, value_for_report_quarter: 0)
    
    # again about the at_least(:once)
    expect(Adjustment::Overview).to receive(:new).with(activity_presenter, report_presenter).and_return(adjustment_overview)