Invoice#total, oldest call first. Each argument is an
object built earlier on the path.
LineItem and Integer and no values.
The problem
Testing with real collaborators costs more setup than testing with doubles. To call Invoice#total
on a real object you need an Invoice with line items, and the items need a sku the price table
knows. The source does not list which of those are required or the order to build them in, so you
read the code, guess, and debug the guess.
The cost of that setup is a reason people use doubles when they would prefer real objects.
The fix
Your agent can ask while it writes an integration spec:
$ ra how_to_reach Invoice#total
Invoice#total
· seen_in: spec/models/invoice_spec.rb:6
- LineItem.new(sku: String)
- Invoice#add_line(item: LineItem, quantity: Integer)
- Invoice#total
Objects built outside traced code (factories, fixtures, seeds) appear only as the class of an argument.
The agent turns that list into setup that uses real objects throughout. The path was recorded from
spec/models/invoice_spec.rb:6, which contains that setup:
invoice = Invoice.new(account: account, number: "INV-1", issued_on: Date.current)
invoice.add_line(item: LineItem.new(sku: "A1"), quantity: 2)
expect(invoice.total).to eq(Money.new(2_880, "GBP"))
The classes and the call order come from the recording, and you supply the values.
ra scaffold Invoice#total goes one step further and drafts the example.
How it works
The recording keeps object identity. how_to_reach finds a recorded call to the target method, takes
its receiver and arguments, and walks backwards through the same call tree to the traced calls that
built or returned those objects, in order. Of all the recorded ways to reach the method it prefers
one that built real objects over one that was passed doubles, then the shortest, and it names the
spec example the path came from.
Run it on your app
The output on this page comes from our example Rails app, and it is regenerated whenever a tool
changes. These are the commands behind it. On your own app, run ra record first, then put your
own class, method or file in their place.
ra how_to_reach 'Invoice#total'
ra scaffold 'Invoice#total'
The recorded setup is spec/models/invoice_spec.rb:6, and the method is app/models/invoice.rb:35.
Reading the result
- A construction step appears when the constructor is traced.
Invoice.newis ActiveRecord’s constructor and a factory’screateruns in gem code, so the invoice itself is not a step in the path.LineItem.newis defined in the app, so it appears. --avoid Klassskips paths that go through a class you would prefer not to depend on.
Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.
Related tools
testing_pyramid
Boundaries crossed through doubles alone. The calls between classes that your specs make through a double alone, with how many specs stub each one and how often your recorded app traffic made it for real, plus the number of examples in each spec directory.
See more →verify_mock
Stubs that disagree with the real object. Compares what each stub in a spec returns with what the real method returned in the recording, and reports the stubs that return a class the real method did not.
See more →observed_shape
The interface a call depends on. The messages your code sends to a value, read from recorded runs. They are the interface a call depends on, whether or not anyone declared it.
See more →