Add customMockName parameter for generateMock + hand-written mock coexistence#216
Merged
Add customMockName parameter for generateMock + hand-written mock coexistence#216
Conversation
When an @INSTANTIABLE type has both generateMock: true and a hand-written mock() method, SafeDI now generates a mock that calls through to the hand-written one. This lets adopters customize mock behavior while still getting generated tree construction. Constraints: - Dependency parameters on the hand-written mock must not have default values (to avoid ambiguity with the generated mock's signatures) - Non-dependency parameters must have default values (enforced always, not just with generateMock) - Types with no dependencies remain mutually exclusive (identical signatures would be ambiguous) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #216 +/- ##
========================================
Coverage 99.94% 99.95%
========================================
Files 40 40
Lines 5652 6070 +418
========================================
+ Hits 5649 6067 +418
Misses 3 3
🚀 New features to boost your workflow:
|
When there are no declarations, there are no dependencies, so the macro rejects and the generator skips the combination of generateMock + hand-written mock. The mockInitializer branch was unreachable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a hand-written mock exists, the generated mock should only expose parameters from the mock's signature, not default-valued init parameters that the mock chose not to expose. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ock ambiguity When generateMock: true and a hand-written mock coexist, the hand-written method must have a different name (specified via customMockName) to avoid Swift overload ambiguity. The generated mock() calls through to the custom-named method. Replaces the old mockMethodConflictsWithGenerateMock and mockMethodDependencyHasDefaultValue errors with mockMethodNeedsCustomName, customMockNameWithoutGenerateMock, and customMockNameMethodNotFound. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hrough Confirms that when a customMock method has parameters in a different order from the init, the generated mock's call-through matches the custom mock's parameter order. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a custom mock method exists, the generated mock() signature now orders its parameters to match the custom mock's parameter order, with any transitive parameters (not in the custom mock) appended alphabetically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a child's custom mock has a default value on a dependency parameter, that default now bubbles up to the parent's generated mock. A @forwarded property or uncovered @received dependency that would otherwise be required gets the default from the nearest receiver's custom mock (BFS: shallowest depth wins, declaration order breaks ties, @forwarded types skipped). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ased types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
generateCustomMockStub now produces a return statement that calls the init (concrete) or instantiate() (extension) with all dependency params, instead of an empty body. Also removes no-op `+ 0` in sort comparator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on, type-safe default bubbling 1. Detect `mock()` method alongside `customMockName` — when both a custom-named mock and a literal `mock()` exist, the latter conflicts with the generated mock. Now caught via `conflictingMockFunctionSyntax`. 2. Non-dependency parameters on mock methods must have defaults even when the type has zero dependencies. Removed the `!dependencies.isEmpty` guard that was skipping validation for zero-dep types. 3. Dependency default bubbling now keys on (label, type) via MockParameterIdentifier instead of just label. Prevents a String default from incorrectly being applied to an Int parameter with the same label. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. When customMockName is already set and a stray mock() exists, the fix-it now removes the conflicting method instead of incorrectly trying to rename it and duplicate customMockName. New error case mockMethodConflictsWithGeneratedMock with "Remove this mock()" fix-it. 2. Extension mock non-dependency default validation now runs for ALL mock overloads (iterating allMockFunctions), not just the first. Previously, later overloads with different return types could have required non-dependency parameters without triggering a diagnostic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers the extension branch of mockMethodConflictsWithGeneratedMock. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The example now shows stub session and noop logger defaults, making it clear why a custom mock is useful vs the generated one. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The macro's fix-its are self-discoverable; documenting individual ones is inconsistent with the rest of the manual. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dfed
commented
Apr 6, 2026
dfed
commented
Apr 6, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The original test used @received (dependency comes from parent). The rewrite to customMockName incorrectly changed it to @Instantiated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
customMockName: StaticString?to@Instantiable, enabling hand-written mock methods to coexist withgenerateMock: true. The generatedmock()calls through to the custom-named method, eliminating the Swift overload ambiguity that previously made them incompatible.Additionally, dependency-parameter defaults on custom mocks now bubble up to parent-generated mocks —
@Forwardedproperties and uncovered dependencies that would otherwise be required get the nearest child's custom mock default.customMockNamebehaviorgenerateMock: true+ hand-writtenmock(), nocustomMockNamecustomMockand addcustomMockName: "customMock"generateMock: true+customMockName+ hand-writtencustomMock()mock()calls throughgenerateMock: true+customMockName+ ALSO amock()methodmock()conflicts with generated mockcustomMockNameset withoutgenerateMock: truegenerateMock: truecustomMockNameset but no method with that name foundDependency default bubbling — "nearest receiver's default wins"
When a child's custom mock has a default on a dependency parameter, that default bubbles up to the parent's generated mock:
@Receivedtype with a custom mock default wins (BFS semantics via depth-tagged DFS)@Forwardedtypes skipped (pass-through)Stringdefault won't bleed into anIntparameter with the same labelGenerated mock parameter order
When a custom mock exists, the generated
mock()signature matches the custom mock's parameter order. Transitive parameters not in the custom mock are appended alphabetically.Mock method validation improvements
mock()method alongsidecustomMockNameis detected and flagged as conflictingError cases changed
mockMethodConflictsWithGenerateMock,mockMethodDependencyHasDefaultValuemockMethodNeedsCustomName,customMockNameWithoutGenerateMock,customMockNameMethodNotFoundmockMethodNonDependencyMissingDefaultValue(now also enforced for zero-dependency types)Test plan
🤖 Generated with Claude Code