Before · invoice_spec.rb:4
def total
rate_table = RateTable.for(account:)
…
end After · rate table passed in
The problem
spec/models/invoice_spec.rb:21 in the example app is in spec/models, so by its directory it is a
unit spec:
it "settles by debiting the account" do
invoice = create(:invoice, account: account)
invoice.add_line(item: LineItem.new(sku: "B2"), quantity: 1)
invoice.save!
expect { invoice.settle }.to change { account.reload.balance_cents }.by(-960)
end
It fails when someone changes the VAT rules, and again when the ledger’s audit formatter is
refactored. Neither change touches Invoice.
The spec’s source does not show the reason. Invoice#total builds its own Billing::RateTable,
which loads the plan, the tax rules and a rate cache, and settle then calls into
Finance::Accounts, the ledger and the audit log. The spec exercises all of them, and finding that
out from the source means reading every method on the path.
The fix
ra how_far_it_reached reports the reach of each example in a directory:
$ ra how_far_it_reached spec/models
how far each example reached
· 37 examples
classes depth example
14 7 spec/models/invoice_spec.rb:21
12 8 spec/models/report_spec.rb:7
12 7 spec/models/invoice_spec.rb:29
10 6 spec/models/invoice_spec.rb:6
7 6 spec/models/invoice_spec.rb:13
… 32 more
· spec/models/invoice_spec.rb:21 reached Account, Billing::Proration, Billing::RateCache,
Billing::RateTable, Billing::TaxRules, Finance::Accounts, Finance::Audit, Finance::Ledger,
Invoice, InvoiceLine, LineItem, Money, Plan, Reporting::Formatters::Currency
The example reaches fourteen classes and goes seven calls deep, so it is an integration test in a unit spec’s directory. The list of classes shows where the reach comes from.
Further down the table, invoice_spec.rb:6 reaches ten classes and invoice_spec.rb:13 reaches
seven. Both test the same total, and :13 reaches fewer classes because it passes the rate table
in:
rate_table = instance_double(Billing::RateTable, vat_for: 0.2)
invoice = Invoice.with_rate_table(rate_table, account: account, number: "INV-2", issued_on: Date.current)
verify_mock checks that double against the real Billing::RateTable in its own spec, and the
path through fourteen classes can move to a spec that is labelled as an integration spec.
How it works
Every call tree in the recording is stamped with the spec example that started it. For each example,
how_far_it_reached walks its trees and counts the distinct receiver classes of every recorded
call, and the longest chain of calls from the example to a leaf. Those are the classes and depth
columns.
Both numbers are read from the call edges, so they cover what ran, including anything reached
through send, through callbacks or by a collaborator building its own collaborators.
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_far_it_reached spec/models
The two total examples are spec/models/invoice_spec.rb:6 and :13. The rate table is built in
app/models/invoice.rb:40.
Reading the result
- It reports the reach and leaves the threshold to you. Some units legitimately compose a handful of value objects.
- The recorder wraps the methods your app defines, so
String,Hash, ActiveRecord and library internals are left out of the class total.
Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.
Related tools
specs_that_reach
Run only the specs that reach your change. The spec examples whose recorded runs reach a method. After a change you rerun those examples alone, and the red-green loop takes seconds.
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 →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 →