Alternative approach to write application little bit differently.
Extract all important logic you want to test into ruby classes without database dependencies.
Write tests for those classes only - your tests will fly! :)
For example
ProductQuantity = Struct.new(:product_id, :quantity)
class Customer < ActiveRecord
def create_order(product_quantities)
product_ids = product_quantities.map(&:product_id)
products = Product.where(:id => product_ids).pluck(:id, unit_price).to_h
total = product_quantities.reduce(0) do |sum, p|
sum += p.quantity * products.fetch(p.product_id, 0)
end
Order.create!(:customer_id => id, :total => total)
end
end
Extract "business logic" out of database dependencies
class Customer < ActiveRecord
def create_order(product_quantities)
products = Product.where(:id => product_ids).pluck(:id, unit_price).to_h
total = CalculateNewOrderTotal.from(products, product_quantities)
Order.create!(:customer_id => id, :total => total)
end
end
module CalculateNewOrderTotal
def self.from(products, product_quantities)
product_quantities.reduce(0) do |sum, p|
sum += p.quantity * products.fetch(p.product_id, 0)
end
end
end
Now the module CalculateNewOrderTotal
can be fully covered with very fast tests which doesn't require mocks or actual database.
You can still write happy path tests with actual database for Customer.create_order
method.
Extra benefits
Your business logic is independent of persistence framework.
Your business logic is independent of persistence schema, you can change how to store data without "touching" important business logic code.
No mocks involved
No extra layers of abstractions - you can still use all benefits of the ActiveRecord, for example wrap call of the method with the transaction.
No other frameworks or gems are involved - pure ruby and RSpec or testing framework of your choice :)