I have two classes:
1.Sale is a subclass of ActiveRecord; its job is to persist sales data to the database.
class Sale < ActiveRecord::Base
def self.total_for_duration(start_date, end_date)
self.count(conditions: {date: start_date..end_date})
end
#...
end
2.SalesReport is a standard Ruby class; its job is to produce and graph information about Sales.
class SalesReport
def initialize(start_date, end_date)
@start_date = start_date
@end_date = end_date
end
def sales_in_duration
Sale.total_for_duration(@start_date, @end_date)
end
#...
end
Because I want to use TDD and I want my tests to run really fast, I have written a spec for SalesReport that doesn't doesn't load Rails:
require_relative "../../app/models/sales_report.rb"
class Sale; end
# NOTE I have had to re-define Sale because I don't want to
# require `sale.rb` because it would then require ActiveRecord.
describe SalesReport do
describe "sales_in_duration" do
it "calls Sale.total_for_duration" do
Sale.should_receive(:total_for_duration)
SalesReport.new.sales_in_duration
end
end
end
This test works when I run bundle exec rspec spec/models/report_spec.rb
.
However this test fails when I run bundle exec rake spec
with the error superclass mismatch for class Sale (TypeError)
. I know the error is happening because Tap is defined by sale.rb
and inline within the spec.
So my question is there a way to Stub (or Mock or Double) a class if that class isn't defined? This would allow me to remove the inline class Sale; end
, which feels like a hack.
If not, how do I set up my tests such that they run correctly whether I run bundle exec rspec
or bundle exec rake spec
?
If not, is my approach to writing fast tests wrong?!
Finally, I don't want to use Spork. Thanks!
RSpec's recently added
stub_const
is specifically designed for cases like these:You may also want to use rspec-fire to use a test double in place of
Sale
that automatically checks all the mocked/stubbed methods exist on the realSale
class when running your tests with the realSale
class loaded (e.g. when you run your test suite):If you rename
total_for_duration
on the realSale
class, rspec-fire will give you an error when you mock the method since it doesn't exist on the real class.