Table XI
Published in

Table XI

RSpec and Rails Are Mocking Me

A very small Rails design decision

  • ActiveRecord associations are really useful.
  • ActiveRecord associations are really hard to replace with test doubles because Rails magic.

A design problem

def create_payment
payment = Payment.create(attribute_hash)
tickets.each do |ticket|
payment.payment_line_items.create(
ticket: ticket, price_cents: ticket.price_cents)
end
end
def create_payment
payment = Payment.create(attribute_hash)
tickets.each do |ticket|
payment.payment_line_items.create(
purchasable: ticket, price_cents: ticket.price_cents)
end
end

A Test Failure

  • Replace the ticket double with an actual ActiveRecord object
  • Replace the ticket double with a FactoryGirl stubbed object, which would be a real ActiveRecord object with it’s database interactions stubbed out.
  • Change the create call to set the purchaseable_id and purchasable_type, explicitly setting purchasable_type to Ticket without introspection.
  • Creating a real object slows down the test and encourages more interaction between the service object and the database.
  • The FactoryGirl object is faster, but still does not discourage separation between the workflow and the model. Also FactoryGirl stubs raise an error if you try to save them, which can be a pain when trying to test this kind of workflow
  • Changing the create call would seem to violate the general principal that you don’t do idiosyncratic things in code just to protect your tests, and it arguably hard codes a dependency. It also lends itself to odd bugs if it’s inadvertently called with something that isn’t a ticket.

Test Failures May Mean Design Flaws

def create_payment
payment = new_payment(attributes)
payment.create_ticket_line_items(ticket)
end
def create_ticket_line_items(tickets)
tickets.each do |ticket|
payment_line_items.create(
purchasable: ticket, price_cents: ticket.price_cents)
end
end
payment_double = instance_double(Payment)
allow(workflow).to receive(:new_payment).and_return(payment_double)
expect(payment_double).to
receive(:create_ticket_line_items).with([ticket_1])

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Noel Rappin

Noel Rappin is an Engineering Manager II at Root Insurance. He is the author of Modern Front-Front End Development For Rails. Find him @noelrap.