Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Documentation/Manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ Set `mockConditionalCompilation` to `nil` to generate mocks without conditional

Each `@Instantiable` type gets a `mock()` static method that builds its full dependency subtree. Types with `generateMock: true` must not also contain a hand-written `mock()` method — the macro will emit an error with a fix-it to remove `generateMock: true`. If you provide your own `mock()` method (without `generateMock: true`), parent types that instantiate the child will call `ChildType.mock(...)` instead of `ChildType(...)` when constructing it, threading mock parameters through your custom method. Note that mocks defined in separate extensions are not detected; the method must be in the `@Instantiable`-decorated declaration body.

Your user-defined `mock()` method must be `public` (or `open`) and must accept parameters for each of the type's `@Instantiated`, `@Received`, and `@Forwarded` dependencies. It may also accept additional parameters with default values. On concrete type declarations the return type must be `Self` or the type name; on extension-based `@Instantiable` types the return type must match the extended type (e.g. `-> Container<Bool>`), mirroring the corresponding `instantiate()` method. The `@Instantiable` macro validates these requirements and provides fix-its for any issues.
Your user-defined `mock()` method must be `public` (or `open`) and must accept parameters for each of the type's `@Instantiated`, `@Received`, and `@Forwarded` dependencies. It may also accept additional parameters with default values. On concrete type declarations the return type must be `Self`, the type name, or a type listed in `fulfillingAdditionalTypes`; on extension-based `@Instantiable` types the return type must match the extended type (e.g. `-> Container<Bool>`) or a `fulfillingAdditionalTypes` entry, mirroring the corresponding `instantiate()` method. The `@Instantiable` macro validates these requirements and provides fix-its for any issues.

```swift
#if DEBUG
Expand Down
4 changes: 4 additions & 0 deletions Sources/SafeDIMacros/Macros/InstantiableMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -432,9 +432,11 @@ public struct InstantiableMacro: MemberMacro {
let typeName = concreteDeclaration.name.text
let instantiableTypeStrippingGenerics = visitor.instantiableType?.strippingGenerics
let mockReturnType = mockSyntax.signature.returnClause?.type.typeDescription
let additionalTypesStrippingGenerics = (visitor.additionalInstantiables ?? []).map(\.strippingGenerics)
let isSelfReturnType = mockReturnType == .simple(name: "Self", generics: [])
let returnTypeMatchesTypeName = isSelfReturnType
|| mockReturnType?.strippingGenerics == instantiableTypeStrippingGenerics
|| additionalTypesStrippingGenerics.contains(where: { $0 == mockReturnType?.strippingGenerics })
if !returnTypeMatchesTypeName {
var fixedMockSyntax = mockSyntax
if let existingReturnClause = mockSyntax.signature.returnClause {
Expand Down Expand Up @@ -620,7 +622,9 @@ public struct InstantiableMacro: MemberMacro {
var seenMockReturnTypes = [TypeDescription: FunctionDeclSyntax]()
for mockFunction in allMockFunctions {
let mockReturnType = mockFunction.signature.returnClause?.type.typeDescription
let additionalTypesStrippingGenerics = (visitor.additionalInstantiables ?? []).map(\.strippingGenerics)
let returnTypeMatchesExtendedType = mockReturnType?.strippingGenerics == extendedTypeStrippingGenerics
|| additionalTypesStrippingGenerics.contains(where: { $0 == mockReturnType?.strippingGenerics })
if !returnTypeMatchesExtendedType {
var fixedMockFunction = mockFunction
if let existingReturnClause = mockFunction.signature.returnClause {
Expand Down
52 changes: 52 additions & 0 deletions Tests/SafeDIMacrosTests/InstantiableMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4933,6 +4933,58 @@ import Testing
)
}

@Test
func mockMethodReturningFulfillingAdditionalTypeProducesNoDiagnostic() {
assertMacroExpansion(
"""
@Instantiable(fulfillingAdditionalTypes: [MyServiceProtocol.self])
public struct MyService: MyServiceProtocol, Instantiable {
public init() {}

public static func mock() -> MyServiceProtocol {
MyService()
}
}
""",
expandedSource: """
public struct MyService: MyServiceProtocol, Instantiable {
public init() {}

public static func mock() -> MyServiceProtocol {
MyService()
}
}
""",
macros: instantiableTestMacros,
)
}

@Test
func extension_mockMethodReturningFulfillingAdditionalTypeProducesNoDiagnostic() {
assertMacroExpansion(
"""
@Instantiable(fulfillingAdditionalTypes: [MyServiceProtocol.self])
extension MyService: Instantiable {
public static func instantiate() -> MyService { MyService() }

public static func mock() -> MyServiceProtocol {
MyService()
}
}
""",
expandedSource: """
extension MyService: Instantiable {
public static func instantiate() -> MyService { MyService() }

public static func mock() -> MyServiceProtocol {
MyService()
}
}
""",
macros: instantiableTestMacros,
)
}

@Test
func extension_mockMethodNotPublicProducesDiagnostic() {
assertMacroExpansion(
Expand Down
Loading