runtimeanalyz.ing

← All capabilities

how_to_reach

MCP · CLI Pro

The setup a method needs. The collaborators and the call path that put an object into the state a method needs, taken from a recorded run. Use it to write setup for tests that use real objects in place of doubles.

billing_spec.rb:31 recorded run
The shortest recorded path to Invoice#total, oldest call first. Each argument is an object built earlier on the path.
Account.new ( plan: Plan )
Account built
RateTable.for ( account: Account )
RateTable built
Invoice.new ( account:, Account rate_table: RateTable )
Invoice built
The same object receives all three calls, in this order.
add_line ( item:, LineItem quantity: Integer )
total
total Money
The classes and the order of the calls come from the recording. You choose the values, because the recording keeps the classes 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.new is ActiveRecord’s constructor and a factory’s create runs in gem code, so the invoice itself is not a step in the path. LineItem.new is defined in the app, so it appears.
  • --avoid Klass skips 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.

testing_pyramid

CLI · MCP Pro

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

CLI · LSP · MCP · CI free

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

CLI · LSP · MCP Pro

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 →