Skip to content

DeallocTests 4.0 (3/5): trackForDeallocation, leak hints and Dependency Injection support - #27

Open
DanielCech wants to merge 5 commits into
dc/release-4.0/2-expect-deallocationfrom
dc/release-4.0/3-tracking-hints-di
Open

DanielCech wants to merge 5 commits into
dc/release-4.0/2-expect-deallocationfrom
dc/release-4.0/3-tracking-hints-di

Conversation

@DanielCech

@DanielCech DanielCech commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Part 3 of 5. DeallocTests 4.0 series (merge in order): #25 groundwork → #26 expectDeallocation → #27 tracking, hints, DI → #28 configuration → #29 remove legacy, docs. Why 4.0, the breaking changes and the alternatives we looked at: see #25.

Beyond one object: check owned objects and any unit test's system under test, explain leaks, and check dependencies from a Dependency Injection container.

Why

  • Only the root was watched in 3.x: a screen can deallocate while its view model leaks. checkClasses only worked for classes that installed tracking themselves, in app code.
  • No Swift Testing teardown: the most common leak check in the wild, a weak reference in an XCTest teardown block, has no equivalent there.
  • "X was not deallocated" says that, not why. The only aid in 3.x was Alloc/Dealloc console output.
  • Shared registrations look like leaks: .shared registrations live in the container. 3.x only handled this inside a DeallocTester subclass.

What changes

trackForDeallocation

// Inside expectDeallocation: checked together with the screen
await expectDeallocation(.present) {
    let viewModel = trackForDeallocation(ProfileViewModel())
    return ProfileViewController(viewModel: viewModel)
}

// Any Swift Testing test or suite
@Test(.checksDeallocation) func loadsProfile() async {
    let viewModel = trackForDeallocation(ProfileViewModel(api: MockAPI()))
    await viewModel.load()
}

// XCTest: a method on the test case, checked at teardown
let viewModel = trackForDeallocation(ProfileViewModel(api: MockAPI()))

A call without the trait or an enclosing check is reported instead of silently checking nothing.

Leak hints

App.ProfileViewController was not deallocated within 2 sec. Possible causes:
  • `onUpdate` is a closure. Make sure it captures self weakly
  • `self.child.parent` refers back to the object. That's a retain cycle unless one of the references is weak
  • Or something outside still holds it: a parent's list of children, a cache or a singleton
  • Detects closures, Tasks, Combine subscriptions, timers and reference cycles through properties.
  • Skips nil and empty values, shows @Observable names, and doesn't follow Apple framework objects.
  • Deliberately not done: checking every reachable object. Mirror can't tell weak from strong, so a weak delegate would be a false leak. Hints are computed only for a confirmed leak and phrased as suggestions.

Dependency Injection (with the default trait)

await expectDeallocation(of: APIManaging.self, resolvedFrom: container)

The dependency is resolved, the container's shared instances are released, then the check runs. A value type is reported with its concrete type, because checking a boxed copy would always pass.

How to review

Order File What
1 Expectation/TrackForDeallocation.swift both entry points and the trait; the tracker becomes a task-local
2 Diagnostics/LeakHints.swift, LeakReport.swift reflection (bounded in depth and size) and message
3 Expectation/ExpectDeallocation+DependencyInjection.swift 71 lines

Source +398, tests +327.

Testing

Tracking in tests, suites, parameterized tests, nested and XCTest; one leak case per hint kind; DI shared/new/value type. Green on macOS (both trait settings) and the iOS Simulator.

🤖 Generated with Claude Code

- `trackForDeallocation` checks objects owned by the tested one inside
  `expectDeallocation`, and the system under test of any unit test:
  an XCTestCase method, or the `.checksDeallocation` Swift Testing trait.
- Failure messages list likely causes found in the leaked object's
  stored properties: closures, Tasks, Combine subscriptions, timers and
  reference cycles. Only for confirmed leaks, phrased as suggestions,
  because Mirror can't tell weak properties from strong ones.
- `expectDeallocation(of:resolvedFrom:)` resolves a dependency from an
  AsyncContainer, releases the shared instances and checks it; a value
  type is reported with its concrete type.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DanielCech and others added 2 commits October 5, 2026 18:13
Doc comments are kept for public API only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The Swift Testing trait skips deallocation verification whenever the test body throws.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds broader deallocation tracking, diagnostic leak hints, and Dependency Injection container support.

Changes:

  • Adds trackForDeallocation for Swift Testing and XCTest.
  • Generates reflection-based leak hints.
  • Adds AsyncContainer dependency checks and related tests.
File Description
Package.swift Adds conditional DI test dependency.
Sources/​DeallocTests/​Diagnostics/​LeakHints.swift Detects likely leak causes.
Sources/​DeallocTests/​Diagnostics/​LeakReport.swift Includes hints in reports.
Sources/​DeallocTests/​Expectation/​DeallocationTracker.swift Supports task-local tracking and hints.
Sources/​DeallocTests/​Expectation/​ExpectDeallocation+DependencyInjection.swift Adds DI resolution checks.
Sources/​DeallocTests/​Expectation/​ExpectDeallocation.swift Tracks nested objects.
Sources/​DeallocTests/​Expectation/​TrackForDeallocation.swift Adds tracking APIs and trait.
Tests/​DeallocTestsTests/​ExpectDeallocationTests.swift Tests tracking and DI support.
Tests/​DeallocTestsTests/​LeakHintsAndSwiftUITests.swift Tests hints and nested tracking.
Tests/​DeallocTestsTests/​LeakReportTests.swift Tests hint generation and formatting.
Tests/​DeallocTestsTests/​TrackForDeallocationXCTests.swift Tests XCTest tracking.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Sources/DeallocTests/Expectation/TrackForDeallocation.swift Outdated
DanielCech and others added 2 commits October 6, 2026 10:17
…rows

The trait skipped verification when the test body threw, so a failing
test silently checked nothing. It now verifies before rethrowing, like
XCTest's teardown blocks, which also run after a thrown error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DanielCech
DanielCech added this pull request to stack #30 October 9, 2026 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants