├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation.yml │ ├── feature_request.yml │ ├── maintenance.yml │ └── question.yml ├── assets │ ├── swift-mocking-banner-2000w.png │ ├── swift-mocking-banner-6400w.png │ ├── swift-mocking-logo.png │ └── swift-mocking-social-preview.png ├── pull_request_template.md ├── scripts │ └── validate-pr-title.js └── workflows │ ├── ci.yml │ ├── pr-labels.yml │ └── pr-title.yml ├── .gitignore ├── .swiftformat ├── Brewfile ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── Mocking │ ├── Extensions │ │ └── Result+AsyncCatching.swift │ ├── Macros │ │ ├── MockableMethod.swift │ │ ├── MockableProperty.swift │ │ ├── Mocked.swift │ │ ├── MockedMembers.swift │ │ ├── MockedMethod.swift │ │ └── MockedProperty.swift │ └── Models │ │ ├── MacroArguments │ │ ├── MockCompilationCondition.swift │ │ └── MockSendableConformance.swift │ │ ├── MockImplementationDescription │ │ └── MockImplementationDescription.swift │ │ ├── MockMethods │ │ ├── MockReturningMethods │ │ │ ├── MockReturningMethodImplementation.swift │ │ │ ├── MockReturningNonParameterizedAsyncMethod │ │ │ │ ├── MockReturningNonParameterizedAsyncMethod+Implementation.swift │ │ │ │ └── MockReturningNonParameterizedAsyncMethod.swift │ │ │ ├── MockReturningNonParameterizedAsyncThrowingMethod │ │ │ │ ├── MockReturningNonParameterizedAsyncThrowingMethod+Implementation.swift │ │ │ │ └── MockReturningNonParameterizedAsyncThrowingMethod.swift │ │ │ ├── MockReturningNonParameterizedMethod │ │ │ │ ├── MockReturningNonParameterizedMethod+Implementation.swift │ │ │ │ └── MockReturningNonParameterizedMethod.swift │ │ │ ├── MockReturningNonParameterizedThrowingMethod │ │ │ │ ├── MockReturningNonParameterizedThrowingMethod+Implementation.swift │ │ │ │ └── MockReturningNonParameterizedThrowingMethod.swift │ │ │ ├── MockReturningParameterizedAsyncMethod │ │ │ │ ├── MockReturningParameterizedAsyncMethod.swift │ │ │ │ └── MockReturningParameterizedAsyncMethodImplementation.swift │ │ │ ├── MockReturningParameterizedAsyncThrowingMethod │ │ │ │ ├── MockReturningParameterizedAsyncThrowingMethod.swift │ │ │ │ └── MockReturningParameterizedAsyncThrowingMethodImplementation.swift │ │ │ ├── MockReturningParameterizedMethod │ │ │ │ ├── MockReturningParameterizedMethod.swift │ │ │ │ └── MockReturningParameterizedMethodImplementation.swift │ │ │ └── MockReturningParameterizedThrowingMethod │ │ │ │ ├── MockReturningParameterizedThrowingMethod.swift │ │ │ │ └── MockReturningParameterizedThrowingMethodImplementation.swift │ │ └── MockVoidMethods │ │ │ ├── MockVoidMethodImplementation.swift │ │ │ ├── MockVoidNonParameterizedAsyncMethod │ │ │ ├── MockVoidNonParameterizedAsyncMethod+Implementation.swift │ │ │ └── MockVoidNonParameterizedAsyncMethod.swift │ │ │ ├── MockVoidNonParameterizedAsyncThrowingMethod │ │ │ ├── MockVoidNonParameterizedAsyncThrowingMethod+Implementation.swift │ │ │ └── MockVoidNonParameterizedAsyncThrowingMethod.swift │ │ │ ├── MockVoidNonParameterizedMethod │ │ │ ├── MockVoidNonParameterizedMethod+Implementation.swift │ │ │ └── MockVoidNonParameterizedMethod.swift │ │ │ ├── MockVoidNonParameterizedThrowingMethod │ │ │ ├── MockVoidNonParameterizedThrowingMethod+Implementation.swift │ │ │ └── MockVoidNonParameterizedThrowingMethod.swift │ │ │ ├── MockVoidParameterizedAsyncMethod │ │ │ ├── MockVoidParameterizedAsyncMethod.swift │ │ │ └── MockVoidParameterizedAsyncMethodImplementation.swift │ │ │ ├── MockVoidParameterizedAsyncThrowingMethod │ │ │ ├── MockVoidParameterizedAsyncThrowingMethod.swift │ │ │ └── MockVoidParameterizedAsyncThrowingMethodImplementation.swift │ │ │ ├── MockVoidParameterizedMethod │ │ │ ├── MockVoidParameterizedMethod.swift │ │ │ └── MockVoidParameterizedMethodImplementation.swift │ │ │ └── MockVoidParameterizedThrowingMethod │ │ │ ├── MockVoidParameterizedThrowingMethod.swift │ │ │ └── MockVoidParameterizedThrowingMethodImplementation.swift │ │ ├── MockProperties │ │ ├── MockAccessors │ │ │ ├── MockPropertyAsyncGetter │ │ │ │ ├── MockPropertyAsyncGetter+Implementation.swift │ │ │ │ └── MockPropertyAsyncGetter.swift │ │ │ ├── MockPropertyAsyncThrowingGetter │ │ │ │ ├── MockPropertyAsyncThrowingGetter+Implementation.swift │ │ │ │ └── MockPropertyAsyncThrowingGetter.swift │ │ │ ├── MockPropertyGetter │ │ │ │ ├── MockPropertyGetter+Implementation.swift │ │ │ │ └── MockPropertyGetter.swift │ │ │ ├── MockPropertySetter │ │ │ │ ├── MockPropertySetter+Implementation.swift │ │ │ │ └── MockPropertySetter.swift │ │ │ └── MockPropertyThrowingGetter │ │ │ │ ├── MockPropertyThrowingGetter+Implementation.swift │ │ │ │ └── MockPropertyThrowingGetter.swift │ │ ├── MockReadOnlyAsyncProperty.swift │ │ ├── MockReadOnlyAsyncThrowingProperty.swift │ │ ├── MockReadOnlyProperty.swift │ │ ├── MockReadOnlyThrowingProperty.swift │ │ └── MockReadWriteProperty.swift │ │ └── MockedPropertyType │ │ ├── MockedPropertyType+AsyncSpecifier.swift │ │ ├── MockedPropertyType+ThrowsSpecifier.swift │ │ └── MockedPropertyType.swift ├── MockingClient │ ├── AttributedTypes.swift │ ├── CompilationConditions.swift │ ├── GenericMethodsWithArrayTypes.swift │ ├── GenericMethodsWithAttributedTypes.swift │ ├── GenericMethodsWithDictionaryTypes.swift │ ├── GenericMethodsWithFunctionTypes.swift │ ├── GenericMethodsWithIdentifierTypes.swift │ ├── GenericMethodsWithMemberTypes.swift │ ├── GenericMethodsWithMetatypes.swift │ ├── GenericMethodsWithOpaqueTypes.swift │ ├── GenericMethodsWithOptionalTypes.swift │ ├── GenericMethodsWithTupleTypes.swift │ ├── Initializers.swift │ ├── MethodOverloads.swift │ ├── NonParameterizedMethods.swift │ ├── ParameterizedMethods.swift │ ├── StaticMembers.swift │ ├── VariadicParameters.swift │ └── main.swift └── MockingMacros │ ├── Extensions │ ├── InheritedTypeSyntax │ │ └── InheritedTypeSyntax+UncheckedSendable.swift │ └── String │ │ └── String+WithFirstCharacterCapitalized.swift │ ├── Macros │ ├── MockableMethodMacro │ │ └── MockableMethodMacro.swift │ ├── MockablePropertyMacro │ │ └── MockablePropertyMacro.swift │ ├── MockedMacro │ │ ├── MockedMacro+MacroArguments.swift │ │ ├── MockedMacro+MacroError.swift │ │ └── MockedMacro.swift │ ├── MockedMembersMacro │ │ ├── MockedMembersMacro+MacroError.swift │ │ ├── MockedMembersMacro+MemberAttributeMacro.swift │ │ ├── MockedMembersMacro+MemberMacro.swift │ │ └── MockedMembersMacro.swift │ ├── MockedMethodMacro │ │ ├── MockedMethodMacro+BodyMacro.swift │ │ ├── MockedMethodMacro+MacroArguments.swift │ │ ├── MockedMethodMacro+MacroError.swift │ │ ├── MockedMethodMacro+PeerMacro.swift │ │ ├── MockedMethodMacro+TypeErasure.swift │ │ └── MockedMethodMacro.swift │ └── MockedPropertyMacro │ │ ├── MockedPropertyMacro+AccessorMacro.swift │ │ ├── MockedPropertyMacro+MacroArguments.swift │ │ ├── MockedPropertyMacro+MacroError.swift │ │ ├── MockedPropertyMacro+PeerMacro.swift │ │ └── MockedPropertyMacro.swift │ ├── MockingPlugin.swift │ └── Models │ ├── MacroArguments │ ├── MacroArgumentValue.swift │ ├── MockCompilationCondition.swift │ └── MockSendableConformance.swift │ ├── MockMethodNameComponents │ ├── MockMethodNameComponent+ID.swift │ ├── MockMethodNameComponent.swift │ └── MockMethodNameComponents.swift │ └── MockedPropertyType │ ├── MockedPropertyType+AsyncSpecifier.swift │ ├── MockedPropertyType+ParsingError.swift │ ├── MockedPropertyType+ThrowsSpecifier.swift │ └── MockedPropertyType.swift ├── Tests ├── MockingMacrosTests │ ├── Macros │ │ ├── MockedMacro │ │ │ ├── MockedMacro_MacroErrorTests.swift │ │ │ ├── Mocked_AccessLevelTests.swift │ │ │ ├── Mocked_AssociatedTypeTests.swift │ │ │ ├── Mocked_CompilationConditionTests.swift │ │ │ ├── Mocked_InheritanceTests.swift │ │ │ ├── Mocked_InitializerTests.swift │ │ │ ├── Mocked_MacroArgumentsTests.swift │ │ │ ├── Mocked_SendableConformanceTests.swift │ │ │ └── TestHelpers │ │ │ │ ├── AssertMocked.swift │ │ │ │ ├── InterfaceConfiguration.swift │ │ │ │ ├── MockConfiguration.swift │ │ │ │ └── MockedTestConfigurations.swift │ │ ├── MockedMembersMacro │ │ │ ├── MockedMembers_InitializerTests.swift │ │ │ ├── MockedMembers_MethodOverloadsTests.swift │ │ │ └── TestHelpers │ │ │ │ └── AssertMockedMembers.swift │ │ ├── MockedMethodMacro │ │ │ ├── MockedMethod_GenericMethod_ArrayTypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_AttributedTypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_DictionaryTypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_FunctionTypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_IdentifierTypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_MemberTypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_MetatypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_OpaqueTypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_OptionalTypeTests.swift │ │ │ ├── MockedMethod_GenericMethod_TupleTypeTests.swift │ │ │ ├── MockedMethod_VariadicParameterTests.swift │ │ │ └── TestHelpers │ │ │ │ └── AssertMockedMethod.swift │ │ └── MockedPropertyMacro │ │ │ ├── MockedProperty_ReadOnlyPropertyTests.swift │ │ │ ├── MockedProperty_ReadWritePropertyTests.swift │ │ │ └── TestHelpers │ │ │ └── AssertMockedProperty.swift │ ├── Models │ │ └── MockSendableConformanceTests.swift │ └── TestHelpers │ │ └── Extensions │ │ └── LabeledExprSyntax+MacroArgumentSyntax.swift └── MockingTests │ └── Models │ ├── MockImplementationDescription │ └── MockImplementationDescriptionTests.swift │ └── MockMethods │ ├── MockReturningMethods │ ├── MockReturningNonParameterizedAsyncMethod │ │ ├── MockReturningNonParameterizedAsyncMethodTests.swift │ │ └── MockReturningNonParameterizedAsyncMethod_ImplementationTests.swift │ ├── MockReturningNonParameterizedAsyncThrowingMethod │ │ ├── MockReturningNonParameterizedAsyncThrowingMethodTests.swift │ │ └── MockReturningNonParameterizedAsyncThrowingMethod_ImplementationTests.swift │ ├── MockReturningNonParameterizedMethod │ │ ├── MockReturningNonParameterizedMethodTests.swift │ │ └── MockReturningNonParameterizedMethod_ImplementationTests.swift │ ├── MockReturningNonParameterizedThrowingMethod │ │ ├── MockReturningNonParameterizedThrowingMethodTests.swift │ │ └── MockReturningNonParameterizedThrowingMethod_ImplementationTests.swift │ ├── MockReturningParameterizedAsyncMethod │ │ └── MockReturningParameterizedAsyncMethodTests.swift │ ├── MockReturningParameterizedAsyncThrowingMethod │ │ └── MockReturningParameterizedAsyncThrowingMethodTests.swift │ ├── MockReturningParameterizedMethod │ │ └── MockReturningParameterizedMethodTests.swift │ └── MockReturningParameterizedThrowingMethod │ │ └── MockReturningParameterizedThrowingMethodTests.swift │ └── MockVoidMethods │ ├── MockVoidNonParameterizedAsyncMethod │ ├── MockVoidNonParameterizedAsyncMethodTests.swift │ └── MockVoidNonParameterizedAsyncMethod_ImplementationTests.swift │ ├── MockVoidNonParameterizedAsyncThrowingMethod │ ├── MockVoidNonParameterizedAsyncThrowingMethodTests.swift │ └── MockVoidNonParameterizedAsyncThrowingMethod_ImplementationTests.swift │ ├── MockVoidNonParameterizedMethod │ ├── MockVoidNonParameterizedMethodTests.swift │ └── MockVoidNonParameterizedMethod_ImplementationTests.swift │ ├── MockVoidNonParameterizedThrowingMethod │ ├── MockVoidNonParameterizedThrowingMethodTests.swift │ └── MockVoidNonParameterizedThrowingMethod_ImplementationTests.swift │ ├── MockVoidParameterizedAsyncMethod │ └── MockVoidParameterizedAsyncMethodTests.swift │ ├── MockVoidParameterizedAsyncThrowingMethod │ └── MockVoidParameterizedAsyncThrowingMethodTests.swift │ ├── MockVoidParameterizedMethod │ └── MockVoidParameterizedMethodTests.swift │ └── MockVoidParameterizedThrowingMethod │ └── MockVoidParameterizedThrowingMethodTests.swift └── codecov.yml /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug or unexpected behavior 3 | title: "[Bug] " 4 | labels: [bug] 5 | assignees: [] 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | ### Thanks for reporting a bug! 12 | Please fill out the form below with as much detail as possible. 13 | 14 | - type: input 15 | id: swift_version 16 | attributes: 17 | label: Swift Version 18 | placeholder: "e.g. 6.1" 19 | validations: 20 | required: true 21 | 22 | - type: input 23 | id: package_version 24 | attributes: 25 | label: Package Version 26 | placeholder: "e.g. 1.2.0 or main" 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | id: description 32 | attributes: 33 | label: Bug Description 34 | description: A clear and concise description of the bug. 35 | placeholder: "What happened?" 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: steps 41 | attributes: 42 | label: Steps to Reproduce 43 | description: Describe the steps needed to reproduce the issue. 44 | placeholder: "If describing an issue related to code, please provide an isolated code example that demonstrates the issue using as little code as possible." 45 | validations: 46 | required: true 47 | 48 | - type: textarea 49 | id: expected_behavior 50 | attributes: 51 | label: Expected Behavior 52 | description: What did you expect to happen? 53 | validations: 54 | required: false 55 | 56 | - type: textarea 57 | id: actual_behavior 58 | attributes: 59 | label: Actual Behavior 60 | description: What actually happened? 61 | validations: 62 | required: false 63 | 64 | - type: textarea 65 | id: logs 66 | attributes: 67 | label: Stack Trace / Logs 68 | description: Paste any error messages or logs. 69 | render: shell 70 | validations: 71 | required: false 72 | 73 | - type: textarea 74 | id: additional_context 75 | attributes: 76 | label: Additional Context 77 | description: Add screenshots, links to related issues, or any other useful info. 78 | validations: 79 | required: false 80 | 81 | - type: checkboxes 82 | id: terms 83 | attributes: 84 | label: Code of Conduct 85 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/fetch-rewards/swift-mocking/blob/main/CODE_OF_CONDUCT.md). 86 | options: 87 | - label: I agree to follow this project's Code of Conduct 88 | required: true 89 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | description: Request changes to documentation 3 | title: "[Documentation] " 4 | labels: [documentation] 5 | assignees: [] 6 | 7 | body: 8 | - type: textarea 9 | id: location 10 | attributes: 11 | label: Where is the documentation issue? 12 | description: Please provide a link or describe the section of the documentation that needs attention. 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: problem 18 | attributes: 19 | label: What is the problem? 20 | description: Explain what's wrong (typo, unclear instructions, outdated info, etc.). 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: suggested_change 26 | attributes: 27 | label: Suggested fix or improvement 28 | description: If you have a suggestion for how to fix the issue, please describe it here. 29 | validations: 30 | required: false 31 | 32 | - type: checkboxes 33 | id: terms 34 | attributes: 35 | label: Code of Conduct 36 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/fetch-rewards/swift-mocking/blob/main/CODE_OF_CONDUCT.md). 37 | options: 38 | - label: I agree to follow this project's Code of Conduct 39 | required: true 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Propose a new feature or enhancement 3 | title: "[Feature] " 4 | labels: [enhancement] 5 | assignees: [] 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | ### Got an idea? 12 | Share your feature request or enhancement so we can consider adding it. 13 | 14 | - type: input 15 | id: use_case 16 | attributes: 17 | label: Use Case 18 | description: What problem does this feature solve? 19 | placeholder: "I'm trying to..." 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: proposal 25 | attributes: 26 | label: Feature Proposal 27 | description: Describe the feature you’d like to see. 28 | placeholder: "I would like to see..." 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | id: alternatives 34 | attributes: 35 | label: Alternatives Considered 36 | description: Have you considered any alternatives or workarounds? 37 | placeholder: "I've tried..." 38 | validations: 39 | required: false 40 | 41 | - type: textarea 42 | id: additional_context 43 | attributes: 44 | label: Additional Context 45 | description: Add screenshots, examples, or any other helpful information. 46 | validations: 47 | required: false 48 | 49 | - type: checkboxes 50 | id: terms 51 | attributes: 52 | label: Code of Conduct 53 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/fetch-rewards/swift-mocking/blob/main/CODE_OF_CONDUCT.md). 54 | options: 55 | - label: I agree to follow this project's Code of Conduct 56 | required: true 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/maintenance.yml: -------------------------------------------------------------------------------- 1 | name: Maintenance 2 | description: Suggest internal improvements unrelated to features or bugs 3 | title: "[Maintenance] " 4 | labels: [] 5 | assignees: [] 6 | 7 | body: 8 | - type: textarea 9 | id: area 10 | attributes: 11 | label: What area needs maintenance? 12 | description: Specify what part of the project you want to improve. 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: reason 18 | attributes: 19 | label: Why is this improvement needed? 20 | description: Explain the benefit (e.g. performance, test coverage, code quality, build speed). 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: proposed_solution 26 | attributes: 27 | label: Proposed solution 28 | description: If you have a suggestion for how to fix the issue, please describe it here. 29 | validations: 30 | required: false 31 | 32 | - type: checkboxes 33 | id: terms 34 | attributes: 35 | label: Code of Conduct 36 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/fetch-rewards/swift-mocking/blob/main/CODE_OF_CONDUCT.md). 37 | options: 38 | - label: I agree to follow this project's Code of Conduct 39 | required: true 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: Question 2 | description: Ask a question or request clarification 3 | title: "[Question] " 4 | labels: [question] 5 | assignees: [] 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | ### Thanks for asking a question! 12 | > [!Important] 13 | > Please consider using [Discussions](https://github.com/fetch-rewards/swift-mocking/discussions) to ask your question. There, you can receive answers from maintainers as well as community members. While we do provide this form for submitting questions, we prefer to reserve issues for things that need to be fixed, changed, or added. If you are sharing feedback, asking for help or opinions, or looking to brainstorm ideas with other members of the community, please use [Discussions](https://github.com/fetch-rewards/swift-mocking/discussions). 14 | 15 | - type: textarea 16 | id: question 17 | attributes: 18 | label: What is your question? 19 | description: Please be clear and specific. 20 | validations: 21 | required: true 22 | 23 | - type: checkboxes 24 | id: checklist 25 | attributes: 26 | label: Checklist 27 | options: 28 | - label: I have searched existing [Issues](https://github.com/fetch-rewards/swift-mocking/issues) and [Discussions](https://github.com/fetch-rewards/swift-mocking/discussions). 29 | required: true 30 | - label: This is not a bug report or feature request. 31 | required: true 32 | 33 | - type: checkboxes 34 | id: terms 35 | attributes: 36 | label: Code of Conduct 37 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/fetch-rewards/swift-mocking/blob/main/CODE_OF_CONDUCT.md). 38 | options: 39 | - label: I agree to follow this project's Code of Conduct 40 | required: true 41 | -------------------------------------------------------------------------------- /.github/assets/swift-mocking-banner-2000w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fetch-rewards/swift-mocking/ed08a0ca14accc130421a7afaf8d7c80b0e3bb26/.github/assets/swift-mocking-banner-2000w.png -------------------------------------------------------------------------------- /.github/assets/swift-mocking-banner-6400w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fetch-rewards/swift-mocking/ed08a0ca14accc130421a7afaf8d7c80b0e3bb26/.github/assets/swift-mocking-banner-6400w.png -------------------------------------------------------------------------------- /.github/assets/swift-mocking-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fetch-rewards/swift-mocking/ed08a0ca14accc130421a7afaf8d7c80b0e3bb26/.github/assets/swift-mocking-logo.png -------------------------------------------------------------------------------- /.github/assets/swift-mocking-social-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fetch-rewards/swift-mocking/ed08a0ca14accc130421a7afaf8d7c80b0e3bb26/.github/assets/swift-mocking-social-preview.png -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 📝 Summary 2 | 3 | 7 | 8 | ## 🛠️ Type of Change 9 | 10 | 11 | 12 | - [ ] Bug fix (change that fixes an issue) 13 | - [ ] New feature (change that adds functionality) 14 | - [ ] Breaking change (bug fix or feature that is not backwards compatible) 15 | - [ ] Documentation (DocC, API docs, markdown files, templates, etc.) 16 | - [ ] Testing (new tests, updated tests, etc.) 17 | - [ ] Refactoring or code formatting (no logic changes) 18 | - [ ] Updating dependencies (Swift packages, Homebrew, etc.) 19 | - [ ] CI/CD (change to automated workflows) 20 | 21 | ## 🧪 How Has This Been Tested? 22 | 23 | 28 | 29 | ## 🔗 Related PRs or Issues 30 | 31 | 37 | 38 | 45 | 46 | ## ✅ Checklist 47 | 48 | 49 | 50 | - [ ] I have added relevant tests 51 | - [ ] I have verified all tests pass 52 | - [ ] I have formatted my code using `SwiftFormat` 53 | - [ ] I have updated documentation (if needed) 54 | - [ ] I have added the appropriate label to my PR 55 | - [ ] I have read the [contributing guidelines](https://github.com/fetch-rewards/swift-mocking/blob/main/CONTRIBUTING.md) 56 | - [ ] I agree to follow this project's [Code of Conduct](https://github.com/fetch-rewards/swift-mocking/blob/main/CODE_OF_CONDUCT.md) 57 | -------------------------------------------------------------------------------- /.github/scripts/validate-pr-title.js: -------------------------------------------------------------------------------- 1 | const nlp = require('compromise'); 2 | const title = process.env.PR_TITLE || ''; 3 | 4 | let isValidTitle = true; 5 | 6 | function logSuccess(message) { 7 | console.log(`✅ ${message}`); 8 | } 9 | 10 | function logFailure(message) { 11 | isValidTitle = false; 12 | console.error(`❌ ${message}`); 13 | } 14 | 15 | function capitalized(string) { 16 | if (!string) return ''; 17 | return string[0].toUpperCase() + string.substring(1); 18 | } 19 | 20 | // Rule 1: PR title must not be empty 21 | if (title) { 22 | logSuccess(`PR title is not empty`); 23 | } else { 24 | logFailure(`PR title must not be empty`); 25 | } 26 | 27 | // Rule 2: PR title must be 72 characters or less 28 | if (title.length <= 72) { 29 | logSuccess(`PR title is ${title.length} characters`); 30 | } else { 31 | logFailure(`PR title must be 72 characters or less (currently ${title.length} characters)`); 32 | } 33 | 34 | // Rule 3: PR title must begin with a capital letter 35 | if (/^[A-Z]/.test(title)) { 36 | logSuccess(`PR title begins with a capital letter`); 37 | } else { 38 | logFailure('PR title must begin with a capital letter'); 39 | } 40 | 41 | // Rule 4: PR title must end with a letter or number 42 | if (/[A-Za-z0-9]$/.test(title)) { 43 | logSuccess(`PR title ends with a letter or number`); 44 | } else { 45 | logFailure('PR title must end with a letter or number'); 46 | } 47 | 48 | // Rule 5: PR title must be written in the imperative 49 | const firstWord = title.split(' ')[0]; 50 | const firstWordLowercased = firstWord.toLowerCase(); 51 | const firstWordCapitalized = capitalized(firstWord); 52 | const firstWordAsImperativeVerb = nlp(firstWord).verbs().toInfinitive().out('text'); 53 | const firstWordAsImperativeVerbLowercased = firstWordAsImperativeVerb.toLowerCase(); 54 | const firstWordAsImperativeVerbCapitalized = capitalized(firstWordAsImperativeVerb); 55 | 56 | if (firstWordLowercased === firstWordAsImperativeVerbLowercased) { 57 | logSuccess(`PR title is written in the imperative`); 58 | } else if (firstWordAsImperativeVerb) { 59 | logFailure(`PR title must be written in the imperative ("${firstWordAsImperativeVerbCapitalized}" instead of "${firstWordCapitalized}")`); 60 | } else { 61 | logFailure(`PR title must begin with a verb and be written in the imperative`); 62 | } 63 | 64 | if (!isValidTitle) { 65 | process.exit(1); 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | types: [opened, reopened, synchronize] 10 | branches: 11 | - '*' 12 | 13 | jobs: 14 | build-and-test: 15 | name: Build, Test, & Report Coverage 16 | runs-on: macos-latest 17 | 18 | steps: 19 | - name: Checkout source code 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up Xcode 23 | uses: maxim-lobanov/setup-xcode@v1 24 | with: 25 | xcode-version: latest 26 | 27 | - name: Install Brewfile dependencies 28 | run: | 29 | brew update --quiet 30 | brew bundle install 31 | 32 | - name: Run SwiftFormat in lint mode 33 | run: | 34 | swiftformat --version 35 | swiftformat --lint . 36 | 37 | - name: Install package dependencies 38 | run: swift package resolve 39 | 40 | - name: Build the package 41 | run: swift build 42 | 43 | - name: Run tests with code coverage 44 | run: swift test --enable-code-coverage 45 | 46 | - name: Gather code coverage 47 | run: xcrun llvm-cov export -format="lcov" .build/debug/swift-mockingPackageTests.xctest/Contents/MacOS/swift-mockingPackageTests -instr-profile .build/debug/codecov/default.profdata > coverage_report.lcov 48 | 49 | - name: Upload coverage to Codecov 50 | uses: codecov/codecov-action@v5 51 | with: 52 | files: coverage_report.lcov 53 | token: ${{ secrets.CODECOV_TOKEN }} 54 | slug: fetch-rewards/swift-mocking 55 | verbose: true 56 | fail_ci_if_error: true 57 | -------------------------------------------------------------------------------- /.github/workflows/pr-labels.yml: -------------------------------------------------------------------------------- 1 | name: PR Labels 2 | 3 | on: 4 | pull_request: 5 | types: [opened, labeled, unlabeled, reopened, synchronize] 6 | 7 | permissions: 8 | contents: read 9 | pull-requests: read 10 | 11 | jobs: 12 | check-for-required-labels: 13 | name: Check For Required Labels 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout source code 18 | uses: actions/checkout@v4 19 | 20 | - name: Validate PR has required labels 21 | env: 22 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | run: | 24 | PR_NUMBER=${{ github.event.pull_request.number }} 25 | REPO=${{ github.repository }} 26 | 27 | REQUIRED_LABELS=("bug" "ci/cd" "dependencies" "documentation" "enhancement" "formatting" "refactoring" "testing") 28 | LABELS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '.labels[].name') 29 | 30 | echo "PR labels:" 31 | echo "$LABELS" 32 | 33 | for required in "${REQUIRED_LABELS[@]}"; do 34 | if echo "$LABELS" | grep -q "^$required$"; then 35 | echo "✅ Found required label: $required" 36 | exit 0 37 | fi 38 | done 39 | 40 | echo "❌ PR is missing a required label." 41 | echo "At least one of the following labels is required:" 42 | printf '%s\n' "${REQUIRED_LABELS[@]}" 43 | exit 1 44 | shell: bash 45 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yml: -------------------------------------------------------------------------------- 1 | name: PR Title 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited, reopened, synchronize] 6 | 7 | permissions: 8 | contents: read 9 | pull-requests: read 10 | 11 | jobs: 12 | validate-pr-title: 13 | name: Validate PR Title 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout source code 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: latest 23 | 24 | - name: Install dependencies 25 | run: npm install compromise 26 | 27 | - name: Validate PR title 28 | run: node .github/scripts/validate-pr-title.js 29 | env: 30 | PR_TITLE: ${{ github.event.pull_request.title }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | 3 | ## User settings 4 | xcuserdata/ 5 | 6 | ## Obj-C/Swift specific 7 | *.hmap 8 | 9 | ## App packaging 10 | *.ipa 11 | *.dSYM.zip 12 | *.dSYM 13 | 14 | ## Playgrounds 15 | timeline.xctimeline 16 | playground.xcworkspace 17 | 18 | # Swift Package Manager 19 | Packages/ 20 | Package.pins 21 | Package.resolved 22 | *.xcodeproj 23 | .swiftpm 24 | .build/ 25 | 26 | # Homebrew 27 | Brewfile.lock.json 28 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew "swiftformat" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](https://semver.org). 6 | 7 | ## 📦 [Version 0.1.0](https://github.com/fetch-rewards/swift-mocking/releases/tag/0.1.0) - April 23, 2025 ([Full Changelog](https://github.com/fetch-rewards/swift-mocking/commits/0.1.0)) 8 | 9 | ### 🚀 Initial Release 10 | 11 | This is the first public release of Swift Mocking, a library that provides a collection of Swift macros used to generate mocks. 12 | 13 | This initial release includes: 14 | 15 | - `@Mocked` - an attached peer macro that generates a mock class from a protocol declaration. 16 | - `@MockedMembers` - an attached member and member-attribute macro that generates mocked members for a mock declaration. 17 | - `@MockableProperty` - an attached macro that marks a property as being mockable. 18 | - `@MockableMethod` - an attached macro that marks a method as being mockable. 19 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lead maintainer 2 | * @graycampbell 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2025 Fetch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | 3 | import CompilerPluginSupport 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "swift-mocking", 8 | platforms: [ 9 | .macOS(.v13), 10 | .iOS(.v16), 11 | .tvOS(.v16), 12 | .watchOS(.v9), 13 | .macCatalyst(.v16), 14 | ], 15 | products: [ 16 | .library( 17 | name: "Mocking", 18 | targets: ["Mocking"] 19 | ), 20 | .executable( 21 | name: "MockingClient", 22 | targets: ["MockingClient"] 23 | ), 24 | ], 25 | dependencies: [ 26 | .package( 27 | url: "https://github.com/fetch-rewards/swift-synchronization.git", 28 | exact: "0.1.0" 29 | ), 30 | .package( 31 | url: "https://github.com/apple/swift-syntax.git", 32 | exact: "600.0.0" // Must match SwiftSyntaxSugar's swift-syntax version 33 | ), 34 | .package( 35 | url: "https://github.com/fetch-rewards/SwiftSyntaxSugar.git", 36 | exact: "0.1.0" // Must match swift-synchronization's SwiftSyntaxSugar version 37 | ), 38 | ], 39 | targets: [ 40 | .target( 41 | name: "Mocking", 42 | dependencies: [ 43 | "MockingMacros", 44 | .product( 45 | name: "Synchronization", 46 | package: "swift-synchronization" 47 | ), 48 | ], 49 | swiftSettings: .default 50 | ), 51 | .testTarget( 52 | name: "MockingTests", 53 | dependencies: ["Mocking"], 54 | swiftSettings: .default 55 | ), 56 | .executableTarget( 57 | name: "MockingClient", 58 | dependencies: ["Mocking"], 59 | swiftSettings: .default 60 | ), 61 | .macro( 62 | name: "MockingMacros", 63 | dependencies: [ 64 | .product( 65 | name: "SwiftCompilerPlugin", 66 | package: "swift-syntax" 67 | ), 68 | .product( 69 | name: "SwiftSyntaxMacros", 70 | package: "swift-syntax" 71 | ), 72 | .product( 73 | name: "SwiftSyntaxSugar", 74 | package: "SwiftSyntaxSugar" 75 | ), 76 | ], 77 | swiftSettings: .default 78 | ), 79 | .testTarget( 80 | name: "MockingMacrosTests", 81 | dependencies: [ 82 | "MockingMacros", 83 | .product( 84 | name: "SwiftSyntaxMacrosTestSupport", 85 | package: "swift-syntax" 86 | ), 87 | ], 88 | swiftSettings: .default 89 | ), 90 | ] 91 | ) 92 | 93 | // MARK: - Swift Settings 94 | 95 | extension SwiftSetting { 96 | static let existentialAny: SwiftSetting = .enableUpcomingFeature( 97 | "ExistentialAny" 98 | ) 99 | 100 | static let internalImportsByDefault: SwiftSetting = .enableUpcomingFeature( 101 | "InternalImportsByDefault" 102 | ) 103 | } 104 | 105 | extension [SwiftSetting] { 106 | 107 | /// Default Swift settings to enable for targets. 108 | static let `default`: [SwiftSetting] = [ 109 | .existentialAny, 110 | .internalImportsByDefault, 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /Sources/Mocking/Extensions/Result+AsyncCatching.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result+AsyncCatching.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension Result where Failure == any Error { 10 | 11 | // MARK: Initializers 12 | 13 | /// Creates a result by evaluating an async throwing closure, capturing the 14 | /// returned value as a success or any thrown error as a failure. 15 | /// 16 | /// - Parameter body: An async throwing closure to evaluate. 17 | init(catching body: () async throws -> Success) async { 18 | do { 19 | self = .success(try await body()) 20 | } catch { 21 | self = .failure(error) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Mocking/Macros/MockableMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockableMethod.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// A macro that marks a method as being mockable. 8 | /// 9 | /// This macro does not itself produce an expansion and is intended to be used 10 | /// in conjunction with the `@MockedMembers` macro: 11 | /// ```swift 12 | /// @MockedMembers 13 | /// final class DependencyMock: Dependency { 14 | /// @MockableMethod(mockMethodName: "increment") 15 | /// func increment() 16 | /// } 17 | /// ``` 18 | /// 19 | /// - Important: Use of this macro is not required to generate a mocked method. 20 | /// The ``MockedMembers()`` macro will add the `@_MockedMethod` macro to any 21 | /// method declaration inside its declaration's member block, regardless of 22 | /// whether that method declaration is marked with the 23 | /// ``MockableMethod(mockMethodName:)`` macro. The ``MockedMembers()`` macro 24 | /// is capable of resolving most naming conflicts caused by method overloads, 25 | /// but in cases where it is unable to successfully resolve those conflicts, 26 | /// this macro may be used to provide a unique `mockMethodName` for a method. 27 | /// - Parameter mockMethodName: The name to use for the mock method. 28 | @attached(body) 29 | public macro MockableMethod(mockMethodName: String) = #externalMacro( 30 | module: "MockingMacros", 31 | type: "MockableMethodMacro" 32 | ) 33 | -------------------------------------------------------------------------------- /Sources/Mocking/Macros/MockableProperty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockableProperty.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// A macro that marks a property as being mockable. 8 | /// 9 | /// This macro does not itself produce an expansion and is intended to be used 10 | /// in conjunction with the `@MockedMembers` macro: 11 | /// ```swift 12 | /// @MockedMembers 13 | /// final class DependencyMock: Dependency { 14 | /// @MockableProperty(.readWrite) 15 | /// var count: Int 16 | /// } 17 | /// ``` 18 | /// 19 | /// - Parameter propertyType: The type of property being mocked. 20 | @attached(accessor) 21 | public macro MockableProperty(_ propertyType: MockedPropertyType) = #externalMacro( 22 | module: "MockingMacros", 23 | type: "MockablePropertyMacro" 24 | ) 25 | -------------------------------------------------------------------------------- /Sources/Mocking/Macros/Mocked.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocked.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// A macro that produces a mock when attached to a protocol. 8 | /// 9 | /// For example: 10 | /// ```swift 11 | /// @Mocked 12 | /// public protocol Dependency { ... } 13 | /// ``` 14 | /// produces a mock that conforms to `Dependency`: 15 | /// ```swift 16 | /// public final class DependencyMock: Dependency { ... } 17 | /// ``` 18 | /// 19 | /// - Parameters: 20 | /// - compilationCondition: The compilation condition to apply to the 21 | /// `#if` compiler directive used to wrap the generated mock. 22 | /// - sendableConformance: The `Sendable` conformance to apply to 23 | /// the generated mock. 24 | @attached(peer, names: suffixed(Mock)) 25 | public macro Mocked( 26 | compilationCondition: MockCompilationCondition = .swiftMockingEnabled, 27 | sendableConformance: MockSendableConformance = .checked 28 | ) = #externalMacro( 29 | module: "MockingMacros", 30 | type: "MockedMacro" 31 | ) 32 | -------------------------------------------------------------------------------- /Sources/Mocking/Macros/MockedMembers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMembers.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// A macro that adds the `@_MockedProperty` macro to all property declarations 8 | /// that are marked with ``MockableProperty(_:)`` and the `@_MockedMethod` macro 9 | /// to all method declarations inside the declaration to which it's attached. 10 | /// 11 | /// For example: 12 | /// ```swift 13 | /// @MockedMembers 14 | /// public final class DependencyMock: Dependency { 15 | /// @MockableProperty(.readWrite) 16 | /// public var property: String 17 | /// 18 | /// public func method() 19 | /// } 20 | /// ``` 21 | /// expands to: 22 | /// ```swift 23 | /// public final class DependencyMock: Dependency { 24 | /// public init() {} 25 | /// 26 | /// @MockableProperty(.readWrite) 27 | /// @MockedProperty( 28 | /// .readWrite, 29 | /// mockName: "DependencyMock", 30 | /// isMockAnActor: false 31 | /// ) 32 | /// public var property: String 33 | /// 34 | /// @MockedMethod( 35 | /// mockName: "DependencyMock", 36 | /// isMockAnActor: false, 37 | /// mockMethodName: "method" 38 | /// ) 39 | /// public func method() 40 | /// } 41 | /// ``` 42 | @attached(memberAttribute) 43 | @attached(member, names: named(init), named(resetMockedStaticMembers)) 44 | public macro MockedMembers() = #externalMacro( 45 | module: "MockingMacros", 46 | type: "MockedMembersMacro" 47 | ) 48 | -------------------------------------------------------------------------------- /Sources/Mocking/Macros/MockedMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMethod.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// A macro that produces a mocked method when attached to a method declaration. 8 | /// 9 | /// - Important: This macro is used in the expansion of the ``MockedMembers()`` 10 | /// macro and is not intended to be used directly. To generate a mocked 11 | /// method, use the ``MockableMethod(mockMethodName:)`` macro (optional) in 12 | /// conjunction with the ``MockedMembers()`` macro instead. 13 | /// - Parameters: 14 | /// - mockName: The name of the encompassing mock declaration. 15 | /// - isMockAnActor: A Boolean value indicating whether the encompassing mock 16 | /// is an actor. 17 | /// - mockMethodName: The name to use for the mock method. 18 | @attached(peer, names: arbitrary) 19 | @attached(body) 20 | public macro _MockedMethod( 21 | mockName: String, 22 | isMockAnActor: Bool, 23 | mockMethodName: String 24 | ) = #externalMacro( 25 | module: "MockingMacros", 26 | type: "MockedMethodMacro" 27 | ) 28 | -------------------------------------------------------------------------------- /Sources/Mocking/Macros/MockedProperty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedProperty.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// A macro that produces a mocked property when attached to a property 8 | /// declaration. 9 | /// 10 | /// - Important: This macro is used in the expansion of the ``MockedMembers()`` 11 | /// macro and is not intended to be used directly. To generate a mocked 12 | /// property, use the ``MockableProperty(_:)`` macro in conjunction with the 13 | /// ``MockedMembers()`` macro instead. 14 | /// - Parameters: 15 | /// - propertyType: The type of property being mocked. 16 | /// - mockName: The name of the encompassing mock declaration. 17 | /// - isMockAnActor: A Boolean value indicating whether the encompassing mock 18 | /// is an actor. 19 | @attached(peer, names: prefixed(_), prefixed(__)) 20 | @attached(accessor) 21 | public macro _MockedProperty( 22 | _ propertyType: MockedPropertyType, 23 | mockName: String, 24 | isMockAnActor: Bool 25 | ) = #externalMacro( 26 | module: "MockingMacros", 27 | type: "MockedPropertyMacro" 28 | ) 29 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MacroArguments/MockCompilationCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockCompilationCondition.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// A compilation condition for an `#if` compiler directive used to wrap a mock 10 | /// declaration. 11 | public enum MockCompilationCondition: RawRepresentable, Equatable, ExpressibleByStringLiteral { 12 | 13 | // MARK: Cases 14 | 15 | /// The mock declaration is not wrapped in an `#if` compiler directive. 16 | case none 17 | 18 | /// The mock declaration is wrapped in an `#if DEBUG` compiler directive. 19 | case debug 20 | 21 | /// The mock declaration is wrapped in an `#if SWIFT_MOCKING_ENABLED` 22 | /// compiler directive. 23 | case swiftMockingEnabled 24 | 25 | /// The mock declaration is wrapped in an `#if` compiler directive with a 26 | /// custom condition. 27 | case custom(_ condition: String) 28 | 29 | // MARK: Properties 30 | 31 | /// The compilation condition as a string, or `nil` if the compilation 32 | /// condition is `.none`. 33 | public var rawValue: String? { 34 | switch self { 35 | case .none: 36 | nil 37 | case .debug: 38 | "DEBUG" 39 | case .swiftMockingEnabled: 40 | "SWIFT_MOCKING_ENABLED" 41 | case let .custom(condition): 42 | condition 43 | } 44 | } 45 | 46 | // MARK: Initializers 47 | 48 | /// Creates a compilation condition from the provided `rawValue`. 49 | /// 50 | /// - Parameter rawValue: The compilation condition as a string. 51 | public init(rawValue: String?) { 52 | switch rawValue { 53 | case .none: 54 | self = .none 55 | case "DEBUG": 56 | self = .debug 57 | case "SWIFT_MOCKING_ENABLED": 58 | self = .swiftMockingEnabled 59 | case let .some(rawValue): 60 | self = .custom(rawValue) 61 | } 62 | } 63 | 64 | /// Creates a compilation condition from the provided string literal. 65 | /// 66 | /// - Parameter value: The compilation condition as a string literal. 67 | public init(stringLiteral value: StringLiteralType) { 68 | self.init(rawValue: value) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MacroArguments/MockSendableConformance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockSendableConformance.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// A `Sendable` conformance that can be applied to a mock declaration. 8 | public enum MockSendableConformance { 9 | 10 | /// The mock conforms to the protocol it is mocking, resulting in 11 | /// checked `Sendable` conformance if the protocol inherits from 12 | /// `Sendable`. 13 | case checked 14 | 15 | /// The mock conforms to `@unchecked Sendable`. 16 | case unchecked 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockImplementationDescription/MockImplementationDescription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockImplementationDescription.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// A mock implementation's description. 10 | public struct MockImplementationDescription: CustomDebugStringConvertible, Sendable { 11 | 12 | // MARK: Properties 13 | 14 | /// The description of the mock's type. 15 | private let type: String 16 | 17 | /// The description of the mock's member. 18 | private let member: String 19 | 20 | /// The description of the mock's implementation. 21 | /// 22 | /// ```swift 23 | /// let implementationDescription = MockImplementationDescription( 24 | /// type: DependencyMock.self, 25 | /// member: "_user" 26 | /// ) 27 | /// 28 | /// print(implementationDescription.debugDescription) // "DependencyMock._user" 29 | /// ``` 30 | /// 31 | /// - Returns: The description of the mock's implementation. 32 | public var debugDescription: String { 33 | "\(self.type).\(self.member)" 34 | } 35 | 36 | // MARK: Initializers 37 | 38 | /// Creates a mock implementation's description. 39 | /// 40 | /// ```swift 41 | /// let implementationDescription = MockImplementationDescription( 42 | /// type: DependencyMock.self, 43 | /// member: "_user" 44 | /// ) 45 | /// ``` 46 | /// 47 | /// - Parameters: 48 | /// - type: The description of the mock's type. 49 | /// - member: The description of the mock's member. 50 | public init(type: Type.Type, member: String) { 51 | self.type = String(describing: type) 52 | self.member = member 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// An implementation for a returning mock method. 10 | public protocol MockReturningMethodImplementation< 11 | Arguments, 12 | Error, 13 | ReturnValue, 14 | Closure 15 | > { 16 | /// The implementation's arguments type. 17 | associatedtype Arguments 18 | 19 | /// The implementation's error type. 20 | associatedtype Error: Swift.Error 21 | 22 | /// The implementation's return value type. 23 | associatedtype ReturnValue 24 | 25 | /// The implementation's closure type. 26 | associatedtype Closure 27 | 28 | /// Triggers a fatal error when invoked if the implementation must return a 29 | /// value, otherwise, does nothing. 30 | static var unimplemented: Self { get } 31 | 32 | /// Invokes the provided closure when invoked. 33 | /// 34 | /// - Parameter closure: The closure to invoke. 35 | static func uncheckedInvokes(_ closure: Closure) -> Self 36 | 37 | /// The implementation as a closure, or `nil` if unimplemented. 38 | var _closure: Closure? { get } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethod+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedAsyncMethod+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockReturningNonParameterizedAsyncMethod { 10 | 11 | /// An implementation for a returning, non-parameterized, async mock method. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Triggers a fatal error when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () async -> ReturnValue) 23 | 24 | // MARK: Constructors 25 | 26 | /// Invokes the provided closure when invoked. 27 | /// 28 | /// - Parameter closure: The closure to invoke. 29 | public static func invokes( 30 | _ closure: @Sendable @escaping () async -> ReturnValue 31 | ) -> Self where ReturnValue: Sendable { 32 | .uncheckedInvokes(closure) 33 | } 34 | 35 | /// Returns the provided value when invoked. 36 | /// 37 | /// - Parameter value: The value to return. 38 | public static func uncheckedReturns(_ value: ReturnValue) -> Self { 39 | .uncheckedInvokes { value } 40 | } 41 | 42 | /// Returns the provided value when invoked. 43 | /// 44 | /// - Parameter value: The value to return. 45 | public static func returns( 46 | _ value: ReturnValue 47 | ) -> Self where ReturnValue: Sendable { 48 | .uncheckedInvokes { value } 49 | } 50 | 51 | // MARK: Call As Function 52 | 53 | /// Invokes the implementation. 54 | /// 55 | /// - Returns: A value, if the implementation returns a value. 56 | func callAsFunction() async -> ReturnValue? { 57 | switch self { 58 | case .unimplemented: 59 | nil 60 | case let .uncheckedInvokes(closure): 61 | await closure() 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethod+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedAsyncThrowingMethod+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockReturningNonParameterizedAsyncThrowingMethod { 10 | 11 | /// An implementation for a returning, non-parameterized, async, throwing 12 | /// mock method. 13 | public enum Implementation: @unchecked Sendable { 14 | 15 | // MARK: Cases 16 | 17 | /// Triggers a fatal error when invoked. 18 | case unimplemented 19 | 20 | /// Invokes the provided closure when invoked. 21 | /// 22 | /// - Parameter closure: The closure to invoke. 23 | case uncheckedInvokes(_ closure: () async throws -> ReturnValue) 24 | 25 | // MARK: Constructors 26 | 27 | /// Invokes the provided closure when invoked. 28 | /// 29 | /// - Parameter closure: The closure to invoke. 30 | public static func invokes( 31 | _ closure: @Sendable @escaping () async throws -> ReturnValue 32 | ) -> Self where ReturnValue: Sendable { 33 | .uncheckedInvokes(closure) 34 | } 35 | 36 | /// Throws the provided error when invoked. 37 | /// 38 | /// - Parameter error: The error to throw. 39 | public static func `throws`(_ error: any Error) -> Self { 40 | .uncheckedInvokes { throw error } 41 | } 42 | 43 | /// Returns the provided value when invoked. 44 | /// 45 | /// - Parameter value: The value to return. 46 | public static func uncheckedReturns(_ value: ReturnValue) -> Self { 47 | .uncheckedInvokes { value } 48 | } 49 | 50 | /// Returns the provided value when invoked. 51 | /// 52 | /// - Parameter value: The value to return. 53 | public static func returns( 54 | _ value: ReturnValue 55 | ) -> Self where ReturnValue: Sendable { 56 | .uncheckedInvokes { value } 57 | } 58 | 59 | // MARK: Call As Function 60 | 61 | /// Invokes the implementation. 62 | /// 63 | /// - Throws: An error, if the implementation throws an error. 64 | /// - Returns: A value, if the implementation returns a value. 65 | func callAsFunction() async throws -> ReturnValue? { 66 | switch self { 67 | case .unimplemented: 68 | nil 69 | case let .uncheckedInvokes(closure): 70 | try await closure() 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethod+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedMethod+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockReturningNonParameterizedMethod { 10 | 11 | /// An implementation for a returning, non-parameterized mock method. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Triggers a fatal error when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () -> ReturnValue) 23 | 24 | // MARK: Constructors 25 | 26 | /// Invokes the provided closure when invoked. 27 | /// 28 | /// - Parameter closure: The closure to invoke. 29 | public static func invokes( 30 | _ closure: @Sendable @escaping () -> ReturnValue 31 | ) -> Self where ReturnValue: Sendable { 32 | .uncheckedInvokes(closure) 33 | } 34 | 35 | /// Returns the provided value when invoked. 36 | /// 37 | /// - Parameter value: The value to return. 38 | public static func uncheckedReturns(_ value: ReturnValue) -> Self { 39 | .uncheckedInvokes { value } 40 | } 41 | 42 | /// Returns the provided value when invoked. 43 | /// 44 | /// - Parameter value: The value to return. 45 | public static func returns( 46 | _ value: ReturnValue 47 | ) -> Self where ReturnValue: Sendable { 48 | .uncheckedInvokes { value } 49 | } 50 | 51 | // MARK: Call As Function 52 | 53 | /// Invokes the implementation. 54 | /// 55 | /// - Returns: A value, if the implementation returns a value. 56 | func callAsFunction() -> ReturnValue? { 57 | switch self { 58 | case .unimplemented: 59 | nil 60 | case let .uncheckedInvokes(closure): 61 | closure() 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethod+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedThrowingMethod+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockReturningNonParameterizedThrowingMethod { 10 | 11 | /// An implementation for a returning, non-parameterized, throwing mock 12 | /// method. 13 | public enum Implementation: @unchecked Sendable { 14 | 15 | // MARK: Cases 16 | 17 | /// Triggers a fatal error when invoked. 18 | case unimplemented 19 | 20 | /// Invokes the provided closure when invoked. 21 | /// 22 | /// - Parameter closure: The closure to invoke. 23 | case uncheckedInvokes(_ closure: () throws -> ReturnValue) 24 | 25 | // MARK: Constructors 26 | 27 | /// Invokes the provided closure when invoked. 28 | /// 29 | /// - Parameter closure: The closure to invoke. 30 | public static func invokes( 31 | _ closure: @Sendable @escaping () throws -> ReturnValue 32 | ) -> Self where ReturnValue: Sendable { 33 | .uncheckedInvokes(closure) 34 | } 35 | 36 | /// Throws the provided error when invoked. 37 | /// 38 | /// - Parameter error: The error to throw. 39 | public static func `throws`(_ error: any Error) -> Self { 40 | .uncheckedInvokes { throw error } 41 | } 42 | 43 | /// Returns the provided value when invoked. 44 | /// 45 | /// - Parameter value: The value to return. 46 | public static func uncheckedReturns(_ value: ReturnValue) -> Self { 47 | .uncheckedInvokes { value } 48 | } 49 | 50 | /// Returns the provided value when invoked. 51 | /// 52 | /// - Parameter value: The value to return. 53 | public static func returns( 54 | _ value: ReturnValue 55 | ) -> Self where ReturnValue: Sendable { 56 | .uncheckedInvokes { value } 57 | } 58 | 59 | // MARK: Call As Function 60 | 61 | /// Invokes the implementation. 62 | /// 63 | /// - Throws: An error, if the implementation throws an error. 64 | /// - Returns: A value, if the implementation returns a value. 65 | func callAsFunction() throws -> ReturnValue? { 66 | switch self { 67 | case .unimplemented: 68 | nil 69 | case let .uncheckedInvokes(closure): 70 | try closure() 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningParameterizedAsyncMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// An implementation for a returning, parameterized, async mock method. 8 | public protocol MockReturningParameterizedAsyncMethodImplementation< 9 | Arguments, 10 | ReturnValue, 11 | Closure 12 | >: MockReturningMethodImplementation where Error == Never {} 13 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningParameterizedAsyncThrowingMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// An implementation for a returning, parameterized, async, throwing mock 10 | /// method. 11 | public protocol MockReturningParameterizedAsyncThrowingMethodImplementation< 12 | Arguments, 13 | ReturnValue, 14 | Closure 15 | >: MockReturningMethodImplementation where Error == any Swift.Error {} 16 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningParameterizedMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// An implementation for a returning, parameterized mock method. 10 | public protocol MockReturningParameterizedMethodImplementation< 11 | Arguments, 12 | ReturnValue, 13 | Closure 14 | >: MockReturningMethodImplementation where Error == Never {} 15 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningParameterizedThrowingMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// An implementation for a returning, parameterized, throwing mock method. 10 | public protocol MockReturningParameterizedThrowingMethodImplementation< 11 | Arguments, 12 | ReturnValue, 13 | Closure 14 | >: MockReturningMethodImplementation where Error == any Swift.Error {} 15 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// An implementation for a void mock method. 10 | public protocol MockVoidMethodImplementation< 11 | Arguments, 12 | Error, 13 | Closure 14 | >: MockReturningMethodImplementation where ReturnValue == Void {} 15 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethod+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedAsyncMethod+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockVoidNonParameterizedAsyncMethod { 10 | 11 | /// An implementation for a void, non-parameterized, async mock method. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Does nothing when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () async -> Void) 23 | 24 | // MARK: Constructors 25 | 26 | /// Invokes the provided closure when invoked. 27 | /// 28 | /// - Parameter closure: The closure to invoke. 29 | public static func invokes( 30 | _ closure: @Sendable @escaping () async -> Void 31 | ) -> Self { 32 | .uncheckedInvokes(closure) 33 | } 34 | 35 | // MARK: Call As Function 36 | 37 | /// Invokes the implementation. 38 | func callAsFunction() async { 39 | switch self { 40 | case .unimplemented: 41 | return 42 | case let .uncheckedInvokes(closure): 43 | await closure() 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedAsyncMethod.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock method that contains implementation details and invocation records 11 | /// for a void, non-parameterized, async method. 12 | public final class MockVoidNonParameterizedAsyncMethod: Sendable { 13 | 14 | // MARK: Properties 15 | 16 | /// The method's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the method has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | // MARK: Initializers 25 | 26 | /// Creates a mock method that contains implementation details and 27 | /// invocation records for a void, non-parameterized, async method. 28 | private init() {} 29 | 30 | // MARK: Factories 31 | 32 | /// Creates a mock method, an async closure for invoking the mock method, 33 | /// and a closure for resetting the mock method, returning them in a labeled 34 | /// tuple. 35 | /// 36 | /// ```swift 37 | /// private let __logOut = MockVoidNonParameterizedAsyncMethod.makeMethod() 38 | /// 39 | /// public var _logOut: MockVoidNonParameterizedAsyncMethod { 40 | /// self.__logOut.method 41 | /// } 42 | /// 43 | /// public func logOut() async { 44 | /// await self.__logOut.invoke() 45 | /// } 46 | /// ``` 47 | /// 48 | /// - Returns: A tuple containing a mock method, an async closure for 49 | /// invoking the mock method, and a closure for resetting the mock method, 50 | /// returning them in a labeled tuple. 51 | public static func makeMethod( 52 | ) -> ( 53 | method: MockVoidNonParameterizedAsyncMethod, 54 | invoke: @Sendable () async -> Void, 55 | reset: @Sendable () -> Void 56 | ) { 57 | let method = MockVoidNonParameterizedAsyncMethod() 58 | 59 | return ( 60 | method: method, 61 | invoke: method.invoke, 62 | reset: method.reset 63 | ) 64 | } 65 | 66 | // MARK: Invoke 67 | 68 | /// Records the invocation of the method and invokes 69 | /// ``implementation-swift.property``. 70 | private func invoke() async { 71 | self.callCount += 1 72 | await self.implementation() 73 | } 74 | 75 | // MARK: Reset 76 | 77 | /// Resets the method's implementation and invocation records. 78 | private func reset() { 79 | self.implementation = .unimplemented 80 | self.callCount = .zero 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethod+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedAsyncThrowingMethod+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockVoidNonParameterizedAsyncThrowingMethod { 10 | 11 | /// An implementation for a void, non-parameterized, async, throwing mock 12 | /// method. 13 | public enum Implementation: @unchecked Sendable { 14 | 15 | // MARK: Cases 16 | 17 | /// Does nothing when invoked. 18 | case unimplemented 19 | 20 | /// Invokes the provided closure when invoked. 21 | /// 22 | /// - Parameter closure: The closure to invoke. 23 | case uncheckedInvokes(_ closure: () async throws -> Void) 24 | 25 | // MARK: Constructors 26 | 27 | /// Invokes the provided closure when invoked. 28 | /// 29 | /// - Parameter closure: The closure to invoke. 30 | public static func invokes( 31 | _ closure: @Sendable @escaping () async throws -> Void 32 | ) -> Self { 33 | .uncheckedInvokes(closure) 34 | } 35 | 36 | /// Throws the provided error when invoked. 37 | /// 38 | /// - Parameter error: The error to throw. 39 | public static func `throws`(_ error: any Error) -> Self { 40 | .uncheckedInvokes { throw error } 41 | } 42 | 43 | // MARK: Call As Function 44 | 45 | /// Invokes the implementation. 46 | /// 47 | /// - Throws: An error, if the implementation throws an error. 48 | func callAsFunction() async throws { 49 | switch self { 50 | case .unimplemented: 51 | return 52 | case let .uncheckedInvokes(closure): 53 | try await closure() 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedAsyncThrowingMethod.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock method that contains implementation details and invocation records 11 | /// for a void, non-parameterized, async, throwing method. 12 | public final class MockVoidNonParameterizedAsyncThrowingMethod: Sendable { 13 | 14 | // MARK: Properties 15 | 16 | /// The method's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the method has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | /// All the errors that have been thrown by the method. 25 | @Locked(.unchecked) 26 | public private(set) var thrownErrors: [any Error] = [] 27 | 28 | /// The last error thrown by the method. 29 | public var lastThrownError: (any Error)? { 30 | self.thrownErrors.last 31 | } 32 | 33 | // MARK: Initializers 34 | 35 | /// Creates a mock method that contains implementation details and 36 | /// invocation records for a void, non-parameterized, async, throwing 37 | /// method. 38 | private init() {} 39 | 40 | // MARK: Factories 41 | 42 | /// Creates a mock method, an async, throwing closure for invoking the 43 | /// mock method, and a closure for resetting the mock method, returning them 44 | /// in a labeled tuple. 45 | /// 46 | /// ```swift 47 | /// private let __logOut = MockVoidNonParameterizedAsyncThrowingMethod.makeMethod() 48 | /// 49 | /// public var _logOut: MockVoidNonParameterizedAsyncThrowingMethod { 50 | /// self.__logOut.method 51 | /// } 52 | /// 53 | /// public func logOut() async throws { 54 | /// try await self.__logOut.invoke() 55 | /// } 56 | /// ``` 57 | /// 58 | /// - Returns: A tuple containing a mock method, an async, throwing closure 59 | /// for invoking the mock method, and a closure for resetting the mock 60 | /// method. 61 | public static func makeMethod( 62 | ) -> ( 63 | method: MockVoidNonParameterizedAsyncThrowingMethod, 64 | invoke: @Sendable () async throws -> Void, 65 | reset: @Sendable () -> Void 66 | ) { 67 | let method = MockVoidNonParameterizedAsyncThrowingMethod() 68 | 69 | return ( 70 | method: method, 71 | invoke: method.invoke, 72 | reset: method.reset 73 | ) 74 | } 75 | 76 | // MARK: Invoke 77 | 78 | /// Records the invocation of the method and invokes 79 | /// ``implementation-swift.property``. 80 | private func invoke() async throws { 81 | self.callCount += 1 82 | 83 | do { 84 | try await self.implementation() 85 | } catch { 86 | self.thrownErrors.append(error) 87 | throw error 88 | } 89 | } 90 | 91 | // MARK: Reset 92 | 93 | /// Resets the method's implementation and invocation records. 94 | private func reset() { 95 | self.implementation = .unimplemented 96 | self.callCount = .zero 97 | self.thrownErrors.removeAll() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethod+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedMethod+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockVoidNonParameterizedMethod { 10 | 11 | /// An implementation for a void, non-parameterized mock method. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Does nothing when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () -> Void) 23 | 24 | // MARK: Constructors 25 | 26 | /// Invokes the provided closure when invoked. 27 | /// 28 | /// - Parameter closure: The closure to invoke. 29 | static func invokes(_ closure: @Sendable @escaping () -> Void) -> Self { 30 | .uncheckedInvokes(closure) 31 | } 32 | 33 | // MARK: Call As Function 34 | 35 | /// Invokes the implementation. 36 | func callAsFunction() { 37 | switch self { 38 | case .unimplemented: 39 | return 40 | case let .uncheckedInvokes(closure): 41 | closure() 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedMethod.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock method that contains implementation details and invocation records 11 | /// for a void, non-parameterized method. 12 | public final class MockVoidNonParameterizedMethod: Sendable { 13 | 14 | // MARK: Properties 15 | 16 | /// The method's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the method has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | // MARK: Initializers 25 | 26 | /// Creates a mock method that contains implementation details and 27 | /// invocation records for a void, non-parameterized method. 28 | private init() {} 29 | 30 | // MARK: Factories 31 | 32 | /// Creates a mock method, a closure for invoking the mock method, and a 33 | /// closure for resetting the mock method, returning them in a labeled 34 | /// tuple. 35 | /// 36 | /// ```swift 37 | /// private let __logOut = MockVoidNonParameterizedMethod.makeMethod() 38 | /// 39 | /// public var _logOut: MockVoidNonParameterizedMethod { 40 | /// self.__logOut.method 41 | /// } 42 | /// 43 | /// public func logOut() { 44 | /// self.__logOut.invoke() 45 | /// } 46 | /// ``` 47 | /// 48 | /// - Returns: A tuple containing a mock method, a closure for invoking the 49 | /// mock method, and a closure for resetting the mock method. 50 | public static func makeMethod( 51 | ) -> ( 52 | method: MockVoidNonParameterizedMethod, 53 | invoke: @Sendable () -> Void, 54 | reset: @Sendable () -> Void 55 | ) { 56 | let method = MockVoidNonParameterizedMethod() 57 | 58 | return ( 59 | method: method, 60 | invoke: method.invoke, 61 | reset: method.reset 62 | ) 63 | } 64 | 65 | // MARK: Invoke 66 | 67 | /// Records the invocation of the method and invokes 68 | /// ``implementation-swift.property``. 69 | private func invoke() { 70 | self.callCount += 1 71 | self.implementation() 72 | } 73 | 74 | // MARK: Reset 75 | 76 | /// Resets the method's implementation and invocation records. 77 | private func reset() { 78 | self.implementation = .unimplemented 79 | self.callCount = .zero 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethod+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedThrowingMethod+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockVoidNonParameterizedThrowingMethod { 10 | 11 | /// An implementation for a void, non-parameterized, throwing mock method. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Does nothing when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () throws -> Void) 23 | 24 | // MARK: Constructors 25 | 26 | /// Invokes the provided closure when invoked. 27 | /// 28 | /// - Parameter closure: The closure to invoke. 29 | public static func invokes( 30 | _ closure: @Sendable @escaping () throws -> Void 31 | ) -> Self { 32 | .uncheckedInvokes(closure) 33 | } 34 | 35 | /// Throws the provided error when invoked. 36 | /// 37 | /// - Parameter error: The error to throw. 38 | public static func `throws`(_ error: any Error) -> Self { 39 | .uncheckedInvokes { throw error } 40 | } 41 | 42 | // MARK: Call As Function 43 | 44 | /// Invokes the implementation. 45 | /// 46 | /// - Throws: An error, if the implementation throws an error. 47 | func callAsFunction() throws { 48 | switch self { 49 | case .unimplemented: 50 | return 51 | case let .uncheckedInvokes(closure): 52 | try closure() 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedThrowingMethod.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock method that contains implementation details and invocation records 11 | /// for a void, non-parameterized, throwing method. 12 | public final class MockVoidNonParameterizedThrowingMethod: Sendable { 13 | 14 | // MARK: Properties 15 | 16 | /// The method's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the method has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | /// All the errors that have been thrown by the method. 25 | @Locked(.unchecked) 26 | public private(set) var thrownErrors: [any Error] = [] 27 | 28 | /// The last error thrown by the method. 29 | public var lastThrownError: (any Error)? { 30 | self.thrownErrors.last 31 | } 32 | 33 | // MARK: Initializers 34 | 35 | /// Creates a mock method that contains implementation details and 36 | /// invocation records for a void, non-parameterized, throwing method. 37 | private init() {} 38 | 39 | // MARK: Factories 40 | 41 | /// Creates a mock method, a throwing closure for invoking the mock method, 42 | /// and a closure for resetting the mock method, returning them in a labeled 43 | /// tuple. 44 | /// 45 | /// ```swift 46 | /// private let __logOut = MockVoidNonParameterizedThrowingMethod.makeMethod() 47 | /// 48 | /// public var _logOut: MockVoidNonParameterizedThrowingMethod { 49 | /// self.__logOut.method 50 | /// } 51 | /// 52 | /// public func logOut() throws { 53 | /// try self.__logOut.invoke() 54 | /// } 55 | /// ``` 56 | /// 57 | /// - Returns: A tuple containing a mock method, a throwing closure for 58 | /// invoking the mock method, and a closure for resetting the mock method. 59 | public static func makeMethod( 60 | ) -> ( 61 | method: MockVoidNonParameterizedThrowingMethod, 62 | invoke: @Sendable () throws -> Void, 63 | reset: @Sendable () -> Void 64 | ) { 65 | let method = MockVoidNonParameterizedThrowingMethod() 66 | 67 | return ( 68 | method: method, 69 | invoke: method.invoke, 70 | reset: method.reset 71 | ) 72 | } 73 | 74 | // MARK: Invoke 75 | 76 | /// Records the invocation of the method and invokes 77 | /// ``implementation-swift.property``. 78 | private func invoke() throws { 79 | self.callCount += 1 80 | 81 | do { 82 | try self.implementation() 83 | } catch { 84 | self.thrownErrors.append(error) 85 | throw error 86 | } 87 | } 88 | 89 | // MARK: Reset 90 | 91 | /// Resets the method's implementation and invocation records. 92 | private func reset() { 93 | self.implementation = .unimplemented 94 | self.callCount = .zero 95 | self.thrownErrors.removeAll() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidParameterizedAsyncMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// An implementation for a void, parameterized, async mock method. 10 | public protocol MockVoidParameterizedAsyncMethodImplementation< 11 | Arguments, 12 | Closure 13 | >: MockVoidMethodImplementation where Error == Never {} 14 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidParameterizedAsyncThrowingMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// An implementation for a void, parameterized, async, throwing mock method. 8 | public protocol MockVoidParameterizedAsyncThrowingMethodImplementation< 9 | Arguments, 10 | Closure 11 | >: MockVoidMethodImplementation where Error == any Swift.Error {} 12 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidParameterizedMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// An implementation for a void, parameterized mock method. 10 | public protocol MockVoidParameterizedMethodImplementation< 11 | Arguments, 12 | Closure 13 | >: MockVoidMethodImplementation where Error == Never {} 14 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethodImplementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidParameterizedThrowingMethodImplementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | /// An implementation for a void, parameterized, throwing mock method. 8 | public protocol MockVoidParameterizedThrowingMethodImplementation< 9 | Arguments, 10 | Closure 11 | >: MockVoidMethodImplementation where Error == any Swift.Error {} 12 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncGetter/MockPropertyAsyncGetter+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertyAsyncGetter+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockPropertyAsyncGetter { 10 | 11 | /// An implementation for an async mock property getter. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Triggers a fatal error when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () async -> Value) 23 | 24 | // MARK: Constructors 25 | 26 | /// Returns the provided value when invoked. 27 | /// 28 | /// - Parameter value: The value to return. 29 | public static func uncheckedReturns(_ value: Value) -> Self { 30 | .uncheckedInvokes { value } 31 | } 32 | 33 | /// Invokes the provided closure when invoked. 34 | /// 35 | /// - Parameter closure: The closure to invoke. 36 | public static func invokes( 37 | _ closure: @Sendable @escaping () async -> Value 38 | ) -> Self where Value: Sendable { 39 | .uncheckedInvokes(closure) 40 | } 41 | 42 | /// Returns the provided value when invoked. 43 | /// 44 | /// - Parameter value: The value to return. 45 | public static func returns( 46 | _ value: Value 47 | ) -> Self where Value: Sendable { 48 | .invokes { value } 49 | } 50 | 51 | // MARK: Call As Function 52 | 53 | /// Invokes the implementation. 54 | /// 55 | /// - Returns: A value, if the implementation returns a value. 56 | func callAsFunction() async -> Value? { 57 | switch self { 58 | case .unimplemented: 59 | nil 60 | case let .uncheckedInvokes(closure): 61 | await closure() 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncGetter/MockPropertyAsyncGetter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertyAsyncGetter.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock property getter that contains implementation details and invocation 11 | /// records for an async property getter. 12 | public final class MockPropertyAsyncGetter { 13 | 14 | // MARK: Properties 15 | 16 | /// The getter's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the getter has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | /// All the values that have been returned by the getter. 25 | @Locked(.unchecked) 26 | public private(set) var returnedValues: [Value] = [] 27 | 28 | /// The last value returned by the getter. 29 | public var lastReturnedValue: Value? { 30 | self.returnedValues.last 31 | } 32 | 33 | /// The description of the mock's exposed property. 34 | /// 35 | /// This description is used when generating an `unimplemented` test failure 36 | /// to indicate which exposed property needs an implementation for the test 37 | /// to succeed. 38 | private let exposedPropertyDescription: MockImplementationDescription 39 | 40 | // MARK: Initializers 41 | 42 | /// Creates a mock property getter that contains implementation details and 43 | /// invocation records for an async property getter. 44 | /// 45 | /// - Parameter exposedPropertyDescription: The description of the mock's 46 | /// exposed property. 47 | init(exposedPropertyDescription: MockImplementationDescription) { 48 | self.exposedPropertyDescription = exposedPropertyDescription 49 | } 50 | 51 | // MARK: Get 52 | 53 | /// Records the invocation of the getter and invokes 54 | /// ``implementation-swift.property``. 55 | /// 56 | /// - Returns: A value, if ``implementation-swift.property`` returns a 57 | /// value. 58 | func get() async -> Value { 59 | self.callCount += 1 60 | 61 | guard let value = await self.implementation() else { 62 | fatalError("Unimplemented: \(self.exposedPropertyDescription)") 63 | } 64 | 65 | self.returnedValues.append(value) 66 | 67 | return value 68 | } 69 | 70 | // MARK: Reset 71 | 72 | /// Resets the getter's implementation and invocation records. 73 | func reset() { 74 | self.implementation = .unimplemented 75 | self.callCount = .zero 76 | self.returnedValues.removeAll() 77 | } 78 | } 79 | 80 | // MARK: - Sendable 81 | 82 | extension MockPropertyAsyncGetter: Sendable where Value: Sendable {} 83 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncThrowingGetter/MockPropertyAsyncThrowingGetter+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertyAsyncThrowingGetter+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockPropertyAsyncThrowingGetter { 10 | 11 | /// An implementation for an async, throwing mock property getter. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Triggers a fatal error when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () async throws -> Value) 23 | 24 | // MARK: Constructors 25 | 26 | /// Returns the provided value when invoked. 27 | /// 28 | /// - Parameter value: The value to return. 29 | public static func uncheckedReturns(_ value: Value) -> Self { 30 | .uncheckedInvokes { value } 31 | } 32 | 33 | /// Invokes the provided closure when invoked. 34 | /// 35 | /// - Parameter closure: The closure to invoke. 36 | public static func invokes( 37 | _ closure: @Sendable @escaping () async throws -> Value 38 | ) -> Self where Value: Sendable { 39 | .uncheckedInvokes(closure) 40 | } 41 | 42 | /// Throws the provided error when invoked. 43 | /// 44 | /// - Parameter error: The error to throw. 45 | public static func `throws`(_ error: any Error) -> Self { 46 | .uncheckedInvokes { throw error } 47 | } 48 | 49 | /// Returns the provided value when invoked. 50 | /// 51 | /// - Parameter value: The value to return. 52 | public static func returns( 53 | _ value: Value 54 | ) -> Self where Value: Sendable { 55 | .invokes { value } 56 | } 57 | 58 | // MARK: Call As Function 59 | 60 | /// Invokes the implementation. 61 | /// 62 | /// - Throws: An error, if the implementation throws an error. 63 | /// - Returns: A value, if the implementation returns a value. 64 | func callAsFunction() async throws -> Value? { 65 | switch self { 66 | case .unimplemented: 67 | nil 68 | case let .uncheckedInvokes(closure): 69 | try await closure() 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncThrowingGetter/MockPropertyAsyncThrowingGetter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertyAsyncThrowingGetter.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock property getter that contains implementation details and invocation 11 | /// records for an async, throwing property getter. 12 | public final class MockPropertyAsyncThrowingGetter { 13 | 14 | // MARK: Properties 15 | 16 | /// The getter's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the getter has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | /// All the values that have been returned by the getter. 25 | @Locked(.unchecked) 26 | public private(set) var returnedValues: [Result] = [] 27 | 28 | /// The last value returned by the getter. 29 | public var lastReturnedValue: Result? { 30 | self.returnedValues.last 31 | } 32 | 33 | /// The description of the mock's exposed property. 34 | /// 35 | /// This description is used when generating an `unimplemented` test failure 36 | /// to indicate which exposed property needs an implementation for the test 37 | /// to succeed. 38 | private let exposedPropertyDescription: MockImplementationDescription 39 | 40 | // MARK: Initializers 41 | 42 | /// Creates a mock property getter that contains implementation details and 43 | /// invocation records for an async, throwing property getter. 44 | /// 45 | /// - Parameter exposedPropertyDescription: The description of the mock's 46 | /// exposed property. 47 | init(exposedPropertyDescription: MockImplementationDescription) { 48 | self.exposedPropertyDescription = exposedPropertyDescription 49 | } 50 | 51 | // MARK: Get 52 | 53 | /// Records the invocation of the getter and invokes 54 | /// ``implementation-swift.property``. 55 | /// 56 | /// - Throws: An error, if ``implementation-swift.property`` throws an 57 | /// error. 58 | /// - Returns: A value, if ``implementation-swift.property`` returns a 59 | /// value. 60 | func get() async throws -> Value { 61 | self.callCount += 1 62 | 63 | let value = await Result { 64 | guard let value = try await self.implementation() else { 65 | fatalError("Unimplemented: \(self.exposedPropertyDescription)") 66 | } 67 | 68 | return value 69 | } 70 | 71 | self.returnedValues.append(value) 72 | 73 | return try value.get() 74 | } 75 | 76 | // MARK: Reset 77 | 78 | /// Resets the getter's implementation and invocation records. 79 | func reset() { 80 | self.implementation = .unimplemented 81 | self.callCount = .zero 82 | self.returnedValues.removeAll() 83 | } 84 | } 85 | 86 | // MARK: - Sendable 87 | 88 | extension MockPropertyAsyncThrowingGetter: Sendable where Value: Sendable {} 89 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyGetter/MockPropertyGetter+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertyGetter+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockPropertyGetter { 10 | 11 | /// An implementation for a mock property getter. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Triggers a fatal error when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () -> Value) 23 | 24 | // MARK: Constructors 25 | 26 | /// Returns the provided value when invoked. 27 | /// 28 | /// - Parameter value: The value to return. 29 | public static func uncheckedReturns(_ value: Value) -> Self { 30 | .uncheckedInvokes { value } 31 | } 32 | 33 | /// Invokes the provided closure when invoked. 34 | /// 35 | /// - Parameter closure: The closure to invoke. 36 | public static func invokes( 37 | _ closure: @Sendable @escaping () -> Value 38 | ) -> Self where Value: Sendable { 39 | .uncheckedInvokes(closure) 40 | } 41 | 42 | /// Returns the provided value when invoked. 43 | /// 44 | /// - Parameter value: The value to return. 45 | public static func returns( 46 | _ value: Value 47 | ) -> Self where Value: Sendable { 48 | .invokes { value } 49 | } 50 | 51 | // MARK: Call As Function 52 | 53 | /// Invokes the implementation. 54 | /// 55 | /// - Returns: A value, if the implementation returns a value. 56 | func callAsFunction() -> Value? { 57 | switch self { 58 | case .unimplemented: 59 | nil 60 | case let .uncheckedInvokes(closure): 61 | closure() 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyGetter/MockPropertyGetter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertyGetter.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock property getter that contains implementation details and invocation 11 | /// records for a property getter. 12 | public final class MockPropertyGetter { 13 | 14 | // MARK: Properties 15 | 16 | /// The getter's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the getter has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | /// All the values that have been returned by the getter. 25 | @Locked(.unchecked) 26 | public private(set) var returnedValues: [Value] = [] 27 | 28 | /// The last value returned by the getter. 29 | public var lastReturnedValue: Value? { 30 | self.returnedValues.last 31 | } 32 | 33 | /// The description of the mock's exposed property. 34 | /// 35 | /// This description is used when generating an `unimplemented` test failure 36 | /// to indicate which exposed property needs an implementation for the test 37 | /// to succeed. 38 | private let exposedPropertyDescription: MockImplementationDescription 39 | 40 | // MARK: Initializers 41 | 42 | /// Creates a mock property getter that contains implementation details and 43 | /// invocation records for a property getter. 44 | /// 45 | /// - Parameter exposedPropertyDescription: The description of the mock's 46 | /// exposed property. 47 | init(exposedPropertyDescription: MockImplementationDescription) { 48 | self.exposedPropertyDescription = exposedPropertyDescription 49 | } 50 | 51 | // MARK: Get 52 | 53 | /// Records the invocation of the getter and invokes 54 | /// ``implementation-swift.property``. 55 | /// 56 | /// - Returns: A value, if ``implementation-swift.property`` returns a 57 | /// value. 58 | func get() -> Value { 59 | self.callCount += 1 60 | 61 | guard let value = self.implementation() else { 62 | fatalError("Unimplemented: \(self.exposedPropertyDescription)") 63 | } 64 | 65 | self.returnedValues.append(value) 66 | 67 | return value 68 | } 69 | 70 | // MARK: Reset 71 | 72 | /// Resets the getter's implementation and invocation records. 73 | func reset() { 74 | self.implementation = .unimplemented 75 | self.callCount = .zero 76 | self.returnedValues.removeAll() 77 | } 78 | } 79 | 80 | // MARK: - Sendable 81 | 82 | extension MockPropertyGetter: Sendable where Value: Sendable {} 83 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertySetter/MockPropertySetter+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertySetter+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockPropertySetter { 10 | 11 | /// An implementation for a mock property setter. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Does nothing when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: (Value) -> Void) 23 | 24 | // MARK: Constructors 25 | 26 | /// Invokes the provided closure when invoked. 27 | /// 28 | /// - Parameter closure: The closure to invoke. 29 | public static func invokes( 30 | _ closure: @Sendable @escaping (Value) -> Void 31 | ) -> Self where Value: Sendable { 32 | .uncheckedInvokes(closure) 33 | } 34 | 35 | // MARK: Call As Function 36 | 37 | /// Invokes the implementation. 38 | /// 39 | /// - Parameter value: The value with which to invoke the 40 | /// implementation. 41 | func callAsFunction(_ value: Value) { 42 | switch self { 43 | case .unimplemented: 44 | return 45 | case let .uncheckedInvokes(closure): 46 | closure(value) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertySetter/MockPropertySetter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertySetter.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock property setter that contains implementation details and invocation 11 | /// records for a property setter. 12 | public final class MockPropertySetter { 13 | 14 | // MARK: Properties 15 | 16 | /// The setter's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the setter has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | /// All the values with which the setter has been invoked. 25 | @Locked(.unchecked) 26 | public private(set) var invocations: [Value] = [] 27 | 28 | /// The last value with which the setter has been invoked. 29 | public var lastInvocation: Value? { 30 | self.invocations.last 31 | } 32 | 33 | // MARK: Set 34 | 35 | /// Records the invocation of the method and invokes 36 | /// ``implementation-swift.property``. 37 | /// 38 | /// - Parameter value: The value with which the setter is being invoked. 39 | func set(_ value: Value) { 40 | self.callCount += 1 41 | self.invocations.append(value) 42 | self.implementation(value) 43 | } 44 | 45 | // MARK: Reset 46 | 47 | /// Resets the setter's implementation and invocation records. 48 | func reset() { 49 | self.implementation = .unimplemented 50 | self.callCount = .zero 51 | self.invocations.removeAll() 52 | } 53 | } 54 | 55 | // MARK: - Sendable 56 | 57 | extension MockPropertySetter: Sendable where Value: Sendable {} 58 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyThrowingGetter/MockPropertyThrowingGetter+Implementation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertyThrowingGetter+Implementation.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockPropertyThrowingGetter { 10 | 11 | /// An implementation for a throwing mock property getter. 12 | public enum Implementation: @unchecked Sendable { 13 | 14 | // MARK: Cases 15 | 16 | /// Triggers a fatal error when invoked. 17 | case unimplemented 18 | 19 | /// Invokes the provided closure when invoked. 20 | /// 21 | /// - Parameter closure: The closure to invoke. 22 | case uncheckedInvokes(_ closure: () throws -> Value) 23 | 24 | // MARK: Constructors 25 | 26 | /// Returns the provided value when invoked. 27 | /// 28 | /// - Parameter value: The value to return. 29 | public static func uncheckedReturns(_ value: Value) -> Self { 30 | .uncheckedInvokes { value } 31 | } 32 | 33 | /// Invokes the provided closure when invoked. 34 | /// 35 | /// - Parameter closure: The closure to invoke. 36 | public static func invokes( 37 | _ closure: @Sendable @escaping () throws -> Value 38 | ) -> Self where Value: Sendable { 39 | .uncheckedInvokes(closure) 40 | } 41 | 42 | /// Throws the provided error when invoked. 43 | /// 44 | /// - Parameter error: The error to throw. 45 | public static func `throws`(_ error: any Error) -> Self { 46 | .uncheckedInvokes { throw error } 47 | } 48 | 49 | /// Returns the provided value when invoked. 50 | /// 51 | /// - Parameter value: The value to return. 52 | public static func returns( 53 | _ value: Value 54 | ) -> Self where Value: Sendable { 55 | .invokes { value } 56 | } 57 | 58 | // MARK: Call As Function 59 | 60 | /// Invokes the implementation. 61 | /// 62 | /// - Throws: An error, if the implementation throws an error. 63 | /// - Returns: A value, if the implementation returns a value. 64 | func callAsFunction() throws -> Value? { 65 | switch self { 66 | case .unimplemented: 67 | nil 68 | case let .uncheckedInvokes(closure): 69 | try closure() 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyThrowingGetter/MockPropertyThrowingGetter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPropertyThrowingGetter.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Synchronization 9 | 10 | /// A mock property getter that contains implementation details and invocation 11 | /// records for a throwing property getter. 12 | public final class MockPropertyThrowingGetter { 13 | 14 | // MARK: Properties 15 | 16 | /// The getter's implementation. 17 | @Locked(.unchecked) 18 | public var implementation: Implementation = .unimplemented 19 | 20 | /// The number of times the getter has been called. 21 | @Locked(.unchecked) 22 | public private(set) var callCount: Int = .zero 23 | 24 | /// All the values that have been returned by the getter. 25 | @Locked(.unchecked) 26 | public private(set) var returnedValues: [Result] = [] 27 | 28 | /// The last value returned by the getter. 29 | public var lastReturnedValue: Result? { 30 | self.returnedValues.last 31 | } 32 | 33 | /// The description of the mock's exposed property. 34 | /// 35 | /// This description is used when generating an `unimplemented` test failure 36 | /// to indicate which exposed property needs an implementation for the test 37 | /// to succeed. 38 | private let exposedPropertyDescription: MockImplementationDescription 39 | 40 | // MARK: Initializers 41 | 42 | /// Creates a mock property getter that contains implementation details and 43 | /// invocation records for a throwing property getter. 44 | /// 45 | /// - Parameter exposedPropertyDescription: The description of the mock's 46 | /// exposed property. 47 | init(exposedPropertyDescription: MockImplementationDescription) { 48 | self.exposedPropertyDescription = exposedPropertyDescription 49 | } 50 | 51 | // MARK: Get 52 | 53 | /// Records the invocation of the getter and invokes 54 | /// ``implementation-swift.property``. 55 | /// 56 | /// - Throws: An error, if ``implementation-swift.property`` throws an 57 | /// error. 58 | /// - Returns: A value, if ``implementation-swift.property`` returns a 59 | /// value. 60 | func get() throws -> Value { 61 | self.callCount += 1 62 | 63 | let value = Result { 64 | guard let value = try self.implementation() else { 65 | fatalError("Unimplemented: \(self.exposedPropertyDescription)") 66 | } 67 | 68 | return value 69 | } 70 | 71 | self.returnedValues.append(value) 72 | 73 | return try value.get() 74 | } 75 | 76 | // MARK: Reset 77 | 78 | /// Resets the getter's implementation and invocation records. 79 | func reset() { 80 | self.implementation = .unimplemented 81 | self.callCount = .zero 82 | self.returnedValues.removeAll() 83 | } 84 | } 85 | 86 | // MARK: - Sendable 87 | 88 | extension MockPropertyThrowingGetter: Sendable where Value: Sendable {} 89 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockedPropertyType/MockedPropertyType+AsyncSpecifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyType+AsyncSpecifier.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockedPropertyType { 10 | 11 | /// The `async` specifier to apply to a mocked property's accessor. 12 | public enum AsyncSpecifier: String, CaseIterable { 13 | 14 | // MARK: Cases 15 | 16 | /// An `async` specifier. 17 | case async 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockedPropertyType/MockedPropertyType+ThrowsSpecifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyType+ThrowsSpecifier.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockedPropertyType { 10 | 11 | /// The `throws` specifier to apply to a mocked property's accessor. 12 | public enum ThrowsSpecifier { 13 | 14 | // MARK: Cases 15 | 16 | /// A `throws` specifier. 17 | case `throws` 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Mocking/Models/MockedPropertyType/MockedPropertyType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyType.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | /// The type of property being mocked. 10 | public enum MockedPropertyType { 11 | 12 | // MARK: Cases 13 | 14 | /// A read-only property. 15 | /// 16 | /// - Parameters: 17 | /// - asyncSpecifier: The getter's `async` specifier. 18 | /// - throwsSpecifier: The getter's `throws` specifier. 19 | case readOnly(AsyncSpecifier? = nil, ThrowsSpecifier? = nil) 20 | 21 | /// A read-write property. 22 | case readWrite 23 | 24 | // MARK: Properties 25 | 26 | /// A read-only property without any effect specifiers. 27 | public static var readOnly: MockedPropertyType { 28 | .readOnly() 29 | } 30 | 31 | // MARK: Constructors 32 | 33 | /// Returns a read-only property with the provided `throwsSpecifier`. 34 | /// 35 | /// - Returns: A read-only property with the provided `throwsSpecifier`. 36 | public static func readOnly( 37 | _ throwsSpecifier: ThrowsSpecifier 38 | ) -> MockedPropertyType { 39 | .readOnly(nil, throwsSpecifier) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/MockingClient/AttributedTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AttributedTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of attributed types. 11 | /// 12 | /// - Important: Please only use this protocol for permanent verification of 13 | /// Mocked's handling of attributed types. For temporary testing of Mocked's 14 | /// expansion, use the `Playground` protocol in `main.swift`. 15 | @Mocked 16 | public protocol AttributedTypes { 17 | func voidMethod( 18 | inoutParameter: inout Int, 19 | consumingParameter: consuming String 20 | ) 21 | 22 | func voidAsyncMethod( 23 | inoutParameter: inout Int, 24 | consumingParameter: consuming String 25 | ) async 26 | 27 | func voidThrowingMethod( 28 | inoutParameter: inout Int, 29 | consumingParameter: consuming String 30 | ) throws 31 | 32 | func voidAsyncThrowingMethod( 33 | inoutParameter: inout Int, 34 | consumingParameter: consuming String 35 | ) async throws 36 | 37 | func returningMethod( 38 | inoutParameter: inout Int, 39 | consumingParameter: consuming String 40 | ) -> Bool 41 | 42 | func returningAsyncMethod( 43 | inoutParameter: inout Int, 44 | consumingParameter: consuming String 45 | ) async -> Bool 46 | 47 | func returningThrowingMethod( 48 | inoutParameter: inout Int, 49 | consumingParameter: consuming String 50 | ) throws -> Bool 51 | 52 | func returningAsyncThrowingMethod( 53 | inoutParameter: inout Int, 54 | consumingParameter: consuming String 55 | ) async throws -> Bool 56 | } 57 | -------------------------------------------------------------------------------- /Sources/MockingClient/CompilationConditions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompilationConditions.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of its `compilationCondition` 11 | /// argument without a provided value. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of its `compilationCondition` argument without a 15 | /// provided value. For temporary testing of Mocked's expansion, use the 16 | /// `Playground` protocol in `main.swift`. 17 | @Mocked 18 | public protocol DefaultCompilationCondition {} 19 | 20 | /// A protocol for verifying Mocked's handling of its `compilationCondition` 21 | /// argument with a `.none` value. 22 | /// 23 | /// - Important: Please only use this protocol for permanent verification of 24 | /// Mocked's handling of its `compilationCondition` argument with a `.none` 25 | /// value. For temporary testing of Mocked's expansion, use the `Playground` 26 | /// protocol in `main.swift`. 27 | @Mocked(compilationCondition: .none) 28 | public protocol NoneCompilationCondition {} 29 | 30 | /// A protocol for verifying Mocked's handling of its `compilationCondition` 31 | /// argument with a `.debug` value. 32 | /// 33 | /// - Important: Please only use this protocol for permanent verification of 34 | /// Mocked's handling of its `compilationCondition` argument with a `.debug` 35 | /// value. For temporary testing of Mocked's expansion, use the `Playground` 36 | /// protocol in `main.swift`. 37 | @Mocked(compilationCondition: .debug) 38 | public protocol DebugCompilationCondition {} 39 | 40 | /// A protocol for verifying Mocked's handling of its `compilationCondition` 41 | /// argument with a `.swiftMockingEnabled` value. 42 | /// 43 | /// - Important: Please only use this protocol for permanent verification of 44 | /// Mocked's handling of its `compilationCondition` argument with a 45 | /// `.swiftMockingEnabled` value. For temporary testing of Mocked's expansion, 46 | /// use the `Playground` protocol in `main.swift`. 47 | @Mocked(compilationCondition: .swiftMockingEnabled) 48 | public protocol SwiftMockingEnabledCompilationCondition {} 49 | 50 | /// A protocol for verifying Mocked's handling of its `compilationCondition` 51 | /// argument with a `.custom` compilation condition. 52 | /// 53 | /// - Important: Please only use this protocol for permanent verification of 54 | /// Mocked's handling of its `compilationCondition` argument with a `.custom` 55 | /// compilation condition. For temporary testing of Mocked's expansion, use 56 | /// the `Playground` protocol in `main.swift`. 57 | @Mocked(compilationCondition: .custom("!RELEASE")) 58 | public protocol CustomCompilationCondition {} 59 | 60 | /// A protocol for verifying Mocked's handling of its `compilationCondition` 61 | /// argument with a string literal compilation condition. 62 | /// 63 | /// - Important: Please only use this protocol for permanent verification of 64 | /// Mocked's handling of its `compilationCondition` argument with a string 65 | /// literal compilation condition. For temporary testing of Mocked's 66 | /// expansion, use the `Playground` protocol in `main.swift`. 67 | @Mocked(compilationCondition: "!RELEASE") 68 | public protocol StringLiteralCompilationCondition {} 69 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithArrayTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithArrayTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of generic methods with `Array` 11 | /// types. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of generic methods with `Array` types. For temporary 15 | /// testing of Mocked's expansion, use the `Playground` protocol in 16 | /// `main.swift`. 17 | @Mocked 18 | public protocol GenericMethodsWithArrayTypes { 19 | func genericMethodWithArrayTypeAndUnconstrainedGenericParameter( 20 | parameter: [Value] 21 | ) -> [Value] 22 | 23 | func genericMethodWithArrayTypeAndConstrainedGenericParameter( 24 | parameter: [Value] 25 | ) -> [Value] where Value: Sendable, Value: Comparable & Hashable 26 | } 27 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithAttributedTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithAttributedTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of generic methods with 11 | /// attributed types. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of generic methods with attributed types. For temporary 15 | /// testing of Mocked's expansion, use the `Playground` protocol in 16 | /// `main.swift`. 17 | @Mocked 18 | public protocol GenericMethodsWithAttributedTypes { 19 | func genericMethodWithAttributedTypesAndUnconstrainedGenericParameter( 20 | inoutParameter: inout Value, 21 | consumingParameter: consuming Value 22 | ) 23 | 24 | func genericMethodWithAttributedTypesAndConstrainedGenericParameter< 25 | Value: Equatable 26 | >( 27 | inoutParameter: inout Value, 28 | consumingParameter: consuming Value 29 | ) where Value: Sendable, Value: Comparable & Hashable 30 | } 31 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithDictionaryTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithDictionaryTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of generic methods with 11 | /// `Dictionary` types. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of generic methods with `Dictionary` types. For 15 | /// temporary testing of Mocked's expansion, use the `Playground` protocol in 16 | /// `main.swift`. 17 | @Mocked 18 | public protocol GenericMethodsWithDictionaryTypes { 19 | func genericMethodWithDictionaryTypeAndUnconstrainedGenericParameters( 20 | parameter: [Key: Value] 21 | ) -> [Key: Value] 22 | 23 | func genericMethodWithDictionaryTypeAndConstrainedGenericParameters< 24 | Key: Hashable, 25 | Value: Equatable 26 | >( 27 | parameter: [Key: Value] 28 | ) -> [Key: Value] where Key: Sendable, Value: Comparable & Hashable 29 | } 30 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithFunctionTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithFunctionTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of generic methods with function 11 | /// types. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of generic methods with function types. For temporary 15 | /// testing of Mocked's expansion, use the `Playground` protocol in 16 | /// `main.swift`. 17 | @Mocked 18 | public protocol GenericMethodsWithFunctionTypes { 19 | func genericMethodWithFunctionTypeAndUnconstrainedGenericParameters( 20 | parameter: @escaping (String) -> Value 21 | ) -> (String) -> Value 22 | 23 | func genericMethodWithFunctionTypeAndConstrainedGenericParameters( 24 | parameter: @escaping (String) -> Value 25 | ) -> (String) -> Value where Value: Equatable 26 | } 27 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithIdentifierTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithIdentifierTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | // swiftformat:disable typeSugar 8 | 9 | import Foundation 10 | public import Mocking 11 | 12 | /// A protocol for verifying Mocked's handling of generic methods with 13 | /// identifier types. 14 | /// 15 | /// - Important: Please only use this protocol for permanent verification of 16 | /// Mocked's handling of generic methods with identifier types. For temporary 17 | /// testing of Mocked's expansion, use the `Playground` protocol in 18 | /// `main.swift`. 19 | @Mocked 20 | public protocol GenericMethodsWithIdentifierTypes { 21 | func genericMethodWithArrayIdentifierTypeAndUnconstrainedGenericParameter( 22 | parameter: Array 23 | ) -> Array 24 | 25 | func genericMethodWithArrayIdentifierTypeAndConstrainedGenericParameter< 26 | Value: Equatable 27 | >( 28 | parameter: Array 29 | ) -> Array where Value: Sendable, Value: Comparable & Hashable 30 | 31 | func genericMethodWithDictionaryIdentifierTypeAndUnconstrainedGenericParameters< 32 | Key, 33 | Value 34 | >( 35 | parameter: Dictionary 36 | ) -> Dictionary 37 | 38 | func genericMethodWithDictionaryIdentifierTypeAndConstrainedGenericParameters< 39 | Key: Hashable, 40 | Value: Equatable 41 | >( 42 | parameter: Dictionary 43 | ) -> Dictionary where Key: Sendable, Value: Comparable & Hashable 44 | 45 | func genericMethodWithOptionalIdentifierTypeAndUnconstrainedGenericParameter< 46 | Value 47 | >( 48 | parameter: Optional 49 | ) -> Optional 50 | 51 | func genericMethodWithOptionalIdentifierTypeAndConstrainedGenericParameter< 52 | Value: Equatable 53 | >( 54 | parameter: Optional 55 | ) -> Optional where Value: Sendable, Value: Comparable & Hashable 56 | 57 | func genericMethodWithSetIdentifierTypeAndUnconstrainedGenericParameter< 58 | Value 59 | >( 60 | parameter: Set 61 | ) -> Set 62 | 63 | func genericMethodWithSetIdentifierTypeAndConstrainedGenericParameter< 64 | Value: Equatable 65 | >( 66 | parameter: Set 67 | ) -> Set where Value: Sendable, Value: Comparable & Hashable 68 | } 69 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithMemberTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithMemberTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | // swiftformat:disable typeSugar 8 | 9 | import Foundation 10 | public import Mocking 11 | 12 | /// A protocol for verifying Mocked's handling of generic methods with member 13 | /// types. 14 | /// 15 | /// - Important: Please only use this protocol for permanent verification of 16 | /// Mocked's handling of generic methods with member types. For temporary 17 | /// testing of Mocked's expansion, use the `Playground` protocol in 18 | /// `main.swift`. 19 | @Mocked 20 | public protocol GenericMethodsWithMemberTypes { 21 | func genericMethodWithArrayMemberTypeAndUnconstrainedGenericParameter( 22 | parameter: Swift.Array 23 | ) -> Swift.Array 24 | 25 | func genericMethodWithArrayMemberTypeAndConstrainedGenericParameter< 26 | Value: Equatable 27 | >( 28 | parameter: Swift.Array 29 | ) -> Swift.Array where Value: Sendable, Value: Comparable & Hashable 30 | 31 | func genericMethodWithDictionaryMemberTypeAndUnconstrainedGenericParameters< 32 | Key, 33 | Value 34 | >( 35 | parameter: Swift.Dictionary 36 | ) -> Swift.Dictionary 37 | 38 | func genericMethodWithDictionaryMemberTypeAndConstrainedGenericParameters< 39 | Key: Hashable, 40 | Value: Equatable 41 | >( 42 | parameter: Swift.Dictionary 43 | ) -> Swift.Dictionary where Key: Sendable, Value: Comparable & Hashable 44 | 45 | func genericMethodWithOptionalMemberTypeAndUnconstrainedGenericParameter< 46 | Value 47 | >( 48 | parameter: Swift.Optional 49 | ) -> Swift.Optional 50 | 51 | func genericMethodWithOptionalMemberTypeAndConstrainedGenericParameter< 52 | Value: Equatable 53 | >( 54 | parameter: Swift.Optional 55 | ) -> Swift.Optional where Value: Sendable, Value: Comparable & Hashable 56 | 57 | func genericMethodWithSetMemberTypeAndUnconstrainedGenericParameter< 58 | Value 59 | >( 60 | parameter: Swift.Set 61 | ) -> Swift.Set 62 | 63 | func genericMethodWithSetMemberTypeAndConstrainedGenericParameter< 64 | Value: Equatable 65 | >( 66 | parameter: Swift.Set 67 | ) -> Swift.Set where Value: Sendable, Value: Comparable & Hashable 68 | } 69 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithMetatypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithMetatypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of generic methods with 11 | /// metatypes. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of generic methods with metatypes. For temporary testing 15 | /// of Mocked's expansion, use the `Playground` protocol in `main.swift`. 16 | @Mocked 17 | public protocol GenericMethodsWithMetatypes { 18 | func genericMethodWithGenericTypeMetatypeAndUnconstrainedGenericParameter( 19 | parameter: Value.Type 20 | ) -> Value.Type 21 | 22 | func genericMethodWithGenericTypeMetatypeAndConstrainedGenericParameter< 23 | Value: Equatable 24 | >( 25 | parameter: Value.Type 26 | ) -> Value.Type where Value: Sendable, Value: Comparable & Hashable 27 | 28 | func genericMethodWithOpaqueTypeMetatypeWithOneConstraint( 29 | parameter: (some Equatable).Type 30 | ) 31 | 32 | func genericMethodWithOpaqueTypeMetatypeWithMultipleConstraints( 33 | parameter: (some Equatable & Sendable & Comparable).Type 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithOpaqueTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithOpaqueTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of generic methods with opaque 11 | /// types. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of generic methods with opaque types. For temporary 15 | /// testing of Mocked's expansion, use the `Playground` protocol in 16 | /// `main.swift`. 17 | @Mocked 18 | public protocol GenericMethodsWithOpaqueTypes { 19 | func genericMethodWithOpaqueTypeWithOneConstraint(parameter: some Equatable) 20 | 21 | func genericMethodWithOpaqueTypeWithMultipleConstraints( 22 | parameter: some Equatable & Sendable & Comparable 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithOptionalTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithOptionalTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of generic methods with 11 | /// `Optional` types. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of generic methods with `Optional` types. For temporary 15 | /// testing of Mocked's expansion, use the `Playground` protocol in 16 | /// `main.swift`. 17 | @Mocked 18 | public protocol GenericMethodsWithOptionalTypes { 19 | func genericMethodWithOptionalTypeAndUnconstrainedGenericParameter( 20 | parameter: Value? 21 | ) -> Value? 22 | 23 | func genericMethodWithOptionalTypeAndConstrainedGenericParameter( 24 | parameter: Value? 25 | ) -> Value? where Value: Sendable, Value: Comparable & Hashable 26 | } 27 | -------------------------------------------------------------------------------- /Sources/MockingClient/GenericMethodsWithTupleTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericMethodsWithTupleTypes.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of generic methods with tuple 11 | /// types. 12 | /// 13 | /// - Important: Please only use this protocol for permanent verification of 14 | /// Mocked's handling of generic methods with tuple types. For temporary 15 | /// testing of Mocked's expansion, use the `Playground` protocol in 16 | /// `main.swift`. 17 | @Mocked 18 | public protocol GenericMethodsWithTupleTypes { 19 | func genericMethodWithTupleTypeAndUnconstrainedGenericParameters( 20 | parameter: (Value1, Value2) 21 | ) -> (Value1, Value2) 22 | 23 | func genericMethodWithTupleTypeAndConstrainedGenericParameters< 24 | Value1: Equatable, 25 | Value2: Hashable 26 | >( 27 | parameter: (Value1, Value2) 28 | ) -> (Value1, Value2) where Value1: Sendable, Value2: Comparable 29 | } 30 | -------------------------------------------------------------------------------- /Sources/MockingClient/Initializers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Initializers.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of initializers. 11 | /// 12 | /// - Important: Please only use this protocol for permanent verification of 13 | /// Mocked's handling of initializers. For temporary testing of Mocked's 14 | /// expansion, use the `Playground` protocol in `main.swift`. 15 | @Mocked 16 | public protocol Initializers { 17 | init() 18 | init(parameter: Int) 19 | init(parameters: Int...) 20 | init(parameter1: Int, parameter2: Int) 21 | } 22 | -------------------------------------------------------------------------------- /Sources/MockingClient/NonParameterizedMethods.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NonParameterizedMethods.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of non-parameterized methods. 11 | /// 12 | /// - Important: Please only use this protocol for permanent verification of 13 | /// Mocked's handling of non-parameterized methods. For temporary testing of 14 | /// Mocked's expansion, use the `Playground` protocol in `main.swift`. 15 | @Mocked 16 | public protocol NonParameterizedMethods { 17 | func voidMethod() 18 | func voidAsyncMethod() async 19 | func voidThrowingMethod() throws 20 | func voidAsyncThrowingMethod() async throws 21 | 22 | func returningMethod() -> Bool 23 | func returningAsyncMethod() async -> Bool 24 | func returningThrowingMethod() throws -> Bool 25 | func returningAsyncThrowingMethod() async throws -> Bool 26 | } 27 | -------------------------------------------------------------------------------- /Sources/MockingClient/ParameterizedMethods.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParameterizedMethods.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of parameterized methods. 11 | /// 12 | /// - Important: Please only use this protocol for permanent verification of 13 | /// Mocked's handling of parameterized methods. For temporary testing of 14 | /// Mocked's expansion, use the `Playground` protocol in `main.swift`. 15 | @Mocked 16 | public protocol ParameterizedMethods { 17 | func voidMethod(string: String, integer: Int) 18 | func voidAsyncMethod(string: String, integer: Int) async 19 | func voidThrowingMethod(string: String, integer: Int) throws 20 | func voidAsyncThrowingMethod(string: String, integer: Int) async throws 21 | 22 | func returningMethod(string: String, integer: Int) -> Bool 23 | func returningAsyncMethod(string: String, integer: Int) async -> Bool 24 | func returningThrowingMethod(string: String, integer: Int) throws -> Bool 25 | func returningAsyncThrowingMethod(string: String, integer: Int) async throws -> Bool 26 | } 27 | -------------------------------------------------------------------------------- /Sources/MockingClient/StaticMembers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StaticMembers.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of static properties and methods. 11 | /// 12 | /// - Important: Please only use this protocol for permanent verification of 13 | /// Mocked's handling of static properties and methods. For temporary testing 14 | /// of Mocked's expansion, use the `Playground` protocol in `main.swift`. 15 | @Mocked 16 | public protocol StaticMembers { 17 | static var staticReadOnlyAsyncProperty: Int { get async } 18 | static var staticReadOnlyAsyncThrowingProperty: Int { get async throws } 19 | static var staticReadOnlyProperty: Int { get } 20 | static var staticReadOnlyThrowingProperty: Int { get throws } 21 | static var staticReadWriteProperty: Int { get set } 22 | 23 | var readOnlyAsyncProperty: Int { get async } 24 | var readOnlyAsyncThrowingProperty: Int { get async throws } 25 | var readOnlyProperty: Int { get } 26 | var readOnlyThrowingProperty: Int { get throws } 27 | var readWriteProperty: Int { get set } 28 | 29 | static func staticReturningAsyncMethodWithoutParameters() async -> Int 30 | static func staticReturningAsyncMethodWithParameters(parameter: Int) async -> Int 31 | static func staticReturningAsyncThrowingMethodWithoutParameters() async throws -> Int 32 | static func staticReturningAsyncThrowingMethodWithParameters(parameter: Int) async throws -> Int 33 | static func staticReturningMethodWithoutParameters() -> Int 34 | static func staticReturningMethodWithParameters(parameter: Int) -> Int 35 | static func staticReturningThrowingMethodWithoutParameters() throws -> Int 36 | static func staticReturningThrowingMethodWithParameters(parameter: Int) throws -> Int 37 | 38 | func returningAsyncMethodWithoutParameters() async -> Int 39 | func returningAsyncMethodWithParameters(parameter: Int) async -> Int 40 | func returningAsyncThrowingMethodWithoutParameters() async throws -> Int 41 | func returningAsyncThrowingMethodWithParameters(parameter: Int) async throws -> Int 42 | func returningMethodWithoutParameters() -> Int 43 | func returningMethodWithParameters(parameter: Int) -> Int 44 | func returningThrowingMethodWithoutParameters() throws -> Int 45 | func returningThrowingMethodWithParameters(parameter: Int) throws -> Int 46 | 47 | static func staticVoidAsyncMethodWithoutParameters() async 48 | static func staticVoidAsyncMethodWithParameters(parameter: Int) async 49 | static func staticVoidAsyncThrowingMethodWithoutParameters() async throws 50 | static func staticVoidAsyncThrowingMethodWithParameters(parameter: Int) async throws 51 | static func staticVoidMethodWithoutParameters() 52 | static func staticVoidMethodWithParameters(parameter: Int) 53 | static func staticVoidThrowingMethodWithoutParameters() throws 54 | static func staticVoidThrowingMethodWithParameters(parameter: Int) throws 55 | 56 | func voidAsyncMethodWithoutParameters() async 57 | func voidAsyncMethodWithParameters(parameter: Int) async 58 | func voidAsyncThrowingMethodWithoutParameters() async throws 59 | func voidAsyncThrowingMethodWithParameters(parameter: Int) async throws 60 | func voidMethodWithoutParameters() 61 | func voidMethodWithParameters(parameter: Int) 62 | func voidThrowingMethodWithoutParameters() throws 63 | func voidThrowingMethodWithParameters(parameter: Int) throws 64 | } 65 | -------------------------------------------------------------------------------- /Sources/MockingClient/VariadicParameters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VariadicParameters.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for verifying Mocked's handling of variadic parameters. 11 | /// 12 | /// - Important: Please only use this protocol for permanent verification of 13 | /// Mocked's handling of variadic parameters. For temporary testing of 14 | /// Mocked's expansion, use the `Playground` protocol in `main.swift`. 15 | @Mocked 16 | public protocol VariadicParameters { 17 | func method(strings: String..., integers: Int...) 18 | } 19 | -------------------------------------------------------------------------------- /Sources/MockingClient/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | public import Mocking 9 | 10 | /// A protocol for testing/verifying Mocked's expansion. 11 | /// 12 | /// - Important: Only use this for temporary testing/verification of Mocked's 13 | /// expansion. Assume that anything added to this protocol can and will be 14 | /// removed or replaced at some point in the future. 15 | @Mocked 16 | public protocol Playground {} 17 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Extensions/InheritedTypeSyntax/InheritedTypeSyntax+UncheckedSendable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InheritedTypeSyntax+UncheckedSendable.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | extension InheritedTypeSyntax { 10 | 11 | /// An `InheritedTypeSyntax` representing `@unchecked Sendable` conformance. 12 | /// 13 | /// ```swift 14 | /// @unchecked Sendable 15 | /// ``` 16 | static let uncheckedSendable = InheritedTypeSyntax( 17 | type: AttributedTypeSyntax( 18 | specifiers: [], 19 | attributes: AttributeListSyntax { 20 | AttributeSyntax( 21 | attributeName: IdentifierTypeSyntax( 22 | name: "unchecked" 23 | ) 24 | ) 25 | }, 26 | baseType: IdentifierTypeSyntax(name: "Sendable") 27 | ) 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Extensions/String/String+WithFirstCharacterCapitalized.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+WithFirstCharacterCapitalized.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension String { 10 | 11 | /// Returns a copy of the string with the first character capitalized. 12 | /// 13 | /// - Returns: A copy of the string with the first character capitalized. 14 | func withFirstCharacterCapitalized() -> String { 15 | let firstCharacter = self.prefix(1).capitalized 16 | let remainingCharacters = self.dropFirst() 17 | 18 | return String(firstCharacter) + String(remainingCharacters) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockableMethodMacro/MockableMethodMacro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockableMethodMacro.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | public import SwiftSyntax 8 | import SwiftSyntaxBuilder 9 | public import SwiftSyntaxMacros 10 | import SwiftSyntaxSugar 11 | 12 | public struct MockableMethodMacro: BodyMacro { 13 | 14 | // MARK: Expansion 15 | 16 | public static func expansion( 17 | of node: AttributeSyntax, 18 | providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, 19 | in context: some MacroExpansionContext 20 | ) throws -> [CodeBlockItemSyntax] { 21 | // The @MockableMethod macro is used only as a marker to provide 22 | // information for the @MockedMembers macro and therefore does not 23 | // produce an expansion. 24 | [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockablePropertyMacro/MockablePropertyMacro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockablePropertyMacro.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | public import SwiftSyntax 8 | import SwiftSyntaxBuilder 9 | public import SwiftSyntaxMacros 10 | import SwiftSyntaxSugar 11 | 12 | public struct MockablePropertyMacro: AccessorMacro { 13 | 14 | // MARK: Expansion 15 | 16 | public static func expansion( 17 | of node: AttributeSyntax, 18 | providingAccessorsOf declaration: some DeclSyntaxProtocol, 19 | in context: some MacroExpansionContext 20 | ) throws -> [AccessorDeclSyntax] { 21 | // The @MockableProperty macro is used only as a marker to provide 22 | // information for the @MockedMembers macro and therefore does not 23 | // produce an expansion. 24 | [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedMacro/MockedMacro+MacroArguments.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMacro+MacroArguments.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | import SwiftSyntaxBuilder 9 | import SwiftSyntaxMacros 10 | import SwiftSyntaxSugar 11 | 12 | extension MockedMacro { 13 | 14 | /// Arguments provided to `@Mocked`. 15 | struct MacroArguments { 16 | 17 | // MARK: Properties 18 | 19 | /// The compilation condition with which to wrap the generated mock. 20 | let compilationCondition: MockCompilationCondition 21 | 22 | /// The `Sendable` conformance to apply to the generated mock. 23 | let sendableConformance: MockSendableConformance 24 | 25 | // MARK: Initializers 26 | 27 | /// Creates macro arguments parsed from the provided `node`. 28 | /// 29 | /// - Parameter node: The node representing the macro. 30 | init(node: AttributeSyntax) { 31 | let arguments = node.arguments?.as(LabeledExprListSyntax.self) 32 | 33 | func argumentValue( 34 | named name: String, 35 | default: ArgumentValue 36 | ) -> ArgumentValue { 37 | guard 38 | let arguments, 39 | let argument = arguments.first(where: { argument in 40 | argument.label?.text == name 41 | }), 42 | let value = ArgumentValue(argument: argument) 43 | else { 44 | return `default` 45 | } 46 | 47 | return value 48 | } 49 | 50 | self.compilationCondition = argumentValue( 51 | named: "compilationCondition", 52 | default: .swiftMockingEnabled 53 | ) 54 | 55 | self.sendableConformance = argumentValue( 56 | named: "sendableConformance", 57 | default: .checked 58 | ) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedMacro/MockedMacro+MacroError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMacro+MacroError.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockedMacro { 10 | 11 | /// An error generated by ``MockedMacro``. 12 | public enum MacroError: CaseIterable, CustomStringConvertible, Error { 13 | 14 | // MARK: Cases 15 | 16 | /// An error indicating that the `@Mocked` macro can only be applied to 17 | /// protocol declarations. 18 | case canOnlyBeAppliedToProtocols 19 | 20 | /// An error indicating that the `@Mocked` macro was unable to parse a 21 | /// property binding's name. 22 | case unableToParsePropertyBindingName 23 | 24 | // MARK: Properties 25 | 26 | /// The description of the error. 27 | public var description: String { 28 | switch self { 29 | case .canOnlyBeAppliedToProtocols: 30 | "@Mocked can only be applied to protocols." 31 | case .unableToParsePropertyBindingName: 32 | "@Mocked was unable to parse a property binding name." 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedMembersMacro/MockedMembersMacro+MacroError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMembersMacro+MacroError.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | extension MockedMembersMacro { 10 | 11 | /// An error generated by ``MockedMembersMacro``. 12 | enum MacroError: CaseIterable, CustomStringConvertible, Error { 13 | 14 | // MARK: Cases 15 | 16 | /// An error indicating that the `@MockedMembers` macro can only be 17 | /// applied to class and actor declarations. 18 | case canOnlyBeAppliedToClassesAndActors 19 | 20 | /// An error indicating that the `@MockedMembers` macro was unable to 21 | /// determine a property's property type. 22 | case unableToDeterminePropertyType 23 | 24 | // MARK: Properties 25 | 26 | /// The description of the error. 27 | var description: String { 28 | switch self { 29 | case .canOnlyBeAppliedToClassesAndActors: 30 | "@MockedMembers can only be applied to classes and actors." 31 | case .unableToDeterminePropertyType: 32 | "@MockedMembers was unable to determine a property's property type." 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedMembersMacro/MockedMembersMacro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMembersMacro.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | import SwiftSyntaxBuilder 9 | import SwiftSyntaxMacros 10 | import SwiftSyntaxSugar 11 | 12 | public struct MockedMembersMacro {} 13 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedMethodMacro/MockedMethodMacro+MacroArguments.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMethodMacro+MacroArguments.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | import SwiftSyntaxBuilder 9 | import SwiftSyntaxMacros 10 | import SwiftSyntaxSugar 11 | 12 | extension MockedMethodMacro { 13 | 14 | /// Arguments provided to `@_MockedMethod`. 15 | struct MacroArguments { 16 | 17 | // MARK: Properties 18 | 19 | /// The name of the mock. 20 | let mockName: String 21 | 22 | /// A Boolean value indicating whether the mock is an actor. 23 | let isMockAnActor: Bool 24 | 25 | /// The name to use for the mock method. 26 | let mockMethodName: String 27 | 28 | // MARK: Initializers 29 | 30 | /// Creates macro arguments parsed from the provided `node`. 31 | /// 32 | /// - Parameter node: The node representing the macro. 33 | init(node: AttributeSyntax) throws { 34 | guard 35 | let arguments = node.arguments?.as(LabeledExprListSyntax.self), 36 | arguments.count > .zero 37 | else { 38 | throw MacroError.noArguments 39 | } 40 | 41 | let argument: (Int) -> LabeledExprSyntax? = { index in 42 | let argumentIndex = arguments.index(at: index) 43 | 44 | return arguments.count > index ? arguments[argumentIndex] : nil 45 | } 46 | 47 | let mockName = argument(0)? 48 | .expression 49 | .as(StringLiteralExprSyntax.self)? 50 | .representedLiteralValue 51 | let isMockAnActorTokenKind = argument(1)? 52 | .expression 53 | .as(BooleanLiteralExprSyntax.self)? 54 | .literal 55 | .tokenKind 56 | let mockMethodName = argument(2)? 57 | .expression 58 | .as(StringLiteralExprSyntax.self)? 59 | .representedLiteralValue 60 | 61 | guard let mockName else { 62 | throw MacroError.unableToParseMockNameArgument 63 | } 64 | 65 | guard let isMockAnActorTokenKind else { 66 | throw MacroError.unableToParseIsMockAnActorArgument 67 | } 68 | 69 | guard let mockMethodName else { 70 | throw MacroError.unableToParseMockMethodName 71 | } 72 | 73 | self.mockName = mockName 74 | self.isMockAnActor = isMockAnActorTokenKind == .keyword(.true) 75 | self.mockMethodName = mockMethodName 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedMethodMacro/MockedMethodMacro+MacroError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMethodMacro+MacroError.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockedMethodMacro { 10 | 11 | /// An error generated by ``MockedMethodMacro``. 12 | enum MacroError: CaseIterable, CustomStringConvertible, Error { 13 | 14 | // MARK: Cases 15 | 16 | /// An error indicating that the `@_MockedMethod` macro can only be 17 | /// applied to method declarations. 18 | case canOnlyBeAppliedToMethodDeclarations 19 | 20 | /// An error indicating that the `@_MockedMethod` macro was not passed 21 | /// any arguments. 22 | case noArguments 23 | 24 | /// An error indicating that the `@_MockedMethod` macro was unable to 25 | /// parse the provided `mockName` argument. 26 | case unableToParseMockNameArgument 27 | 28 | /// An error indicating that the `@_MockedMethod` macro was unable to 29 | /// parse the provided `isMockAnActor` argument. 30 | case unableToParseIsMockAnActorArgument 31 | 32 | /// An error indicating that the `@_MockedMethod` macro was unable to 33 | /// parse the provided `mockMethodName` argument. 34 | case unableToParseMockMethodName 35 | 36 | // MARK: Properties 37 | 38 | /// The description of the error. 39 | var description: String { 40 | switch self { 41 | case .canOnlyBeAppliedToMethodDeclarations: 42 | "@_MockedMethod can only be applied to method declarations." 43 | case .noArguments: 44 | "@_MockedMethod was not passed any arguments." 45 | case .unableToParseMockNameArgument: 46 | "@_MockedMethod was unable to parse the provided `mockName` argument." 47 | case .unableToParseIsMockAnActorArgument: 48 | "@_MockedMethod was unable to parse the provided `isMockAnActor` argument." 49 | case .unableToParseMockMethodName: 50 | "@_MockedMethod was unable to parse the provided `mockMethodName` argument." 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedMethodMacro/MockedMethodMacro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMethodMacro.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | import SwiftSyntaxBuilder 9 | import SwiftSyntaxMacros 10 | import SwiftSyntaxSugar 11 | 12 | public struct MockedMethodMacro {} 13 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedPropertyMacro/MockedPropertyMacro+MacroArguments.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyMacro+MacroArguments.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | import SwiftSyntaxBuilder 9 | import SwiftSyntaxMacros 10 | import SwiftSyntaxSugar 11 | 12 | extension MockedPropertyMacro { 13 | 14 | /// Arguments provided to `@_MockedProperty`. 15 | struct MacroArguments { 16 | 17 | // MARK: Properties 18 | 19 | /// The type of property to which the macro is attached. 20 | let propertyType: MockedPropertyType 21 | 22 | /// The name of the mock. 23 | let mockName: String 24 | 25 | /// A Boolean value indicating whether the mock is an actor. 26 | let isMockAnActor: Bool 27 | 28 | // MARK: Initializers 29 | 30 | /// Creates macro arguments parsed from the provided `node`. 31 | /// 32 | /// - Parameter node: The node representing the macro. 33 | init(node: AttributeSyntax) throws { 34 | guard 35 | let arguments = node.arguments?.as(LabeledExprListSyntax.self), 36 | arguments.count > .zero 37 | else { 38 | throw MacroError.noArguments 39 | } 40 | 41 | let argument: (Int) -> LabeledExprSyntax? = { index in 42 | let argumentIndex = arguments.index(at: index) 43 | 44 | return arguments.count > index ? arguments[argumentIndex] : nil 45 | } 46 | 47 | let propertyTypeArgument = argument(0) 48 | let mockName = argument(1)? 49 | .expression 50 | .as(StringLiteralExprSyntax.self)? 51 | .representedLiteralValue 52 | let isMockAnActorTokenKind = argument(2)? 53 | .expression 54 | .as(BooleanLiteralExprSyntax.self)? 55 | .literal 56 | .tokenKind 57 | 58 | guard let propertyTypeArgument else { 59 | throw MacroError.unableToParsePropertyTypeArgument 60 | } 61 | 62 | guard let mockName else { 63 | throw MacroError.unableToParseMockNameArgument 64 | } 65 | 66 | guard let isMockAnActorTokenKind else { 67 | throw MacroError.unableToParseIsMockAnActorArgument 68 | } 69 | 70 | self.propertyType = try MockedPropertyType(argument: propertyTypeArgument) 71 | self.mockName = mockName 72 | self.isMockAnActor = isMockAnActorTokenKind == .keyword(.true) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedPropertyMacro/MockedPropertyMacro+MacroError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyMacro+MacroError.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockedPropertyMacro { 10 | 11 | /// An error generated by ``MockedPropertyMacro``. 12 | enum MacroError: CaseIterable, CustomStringConvertible, Error { 13 | 14 | // MARK: Cases 15 | 16 | /// An error indicating that the `@_MockedProperty` macro can only be 17 | /// applied to property declarations. 18 | case canOnlyBeAppliedToPropertyDeclarations 19 | 20 | /// An error indicating that the `@_MockedProperty` macro can only be 21 | /// applied to single-binding property declarations. 22 | case canOnlyBeAppliedToSingleBindingPropertyDeclarations 23 | 24 | /// An error indicating that the `@_MockedProperty` macro was not passed 25 | /// any arguments. 26 | case noArguments 27 | 28 | /// An error indicating that the `@_MockedProperty` macro was unable to 29 | /// parse the provided `propertyType` argument. 30 | case unableToParsePropertyTypeArgument 31 | 32 | /// An error indicating that the `@_MockedProperty` macro was unable to 33 | /// parse the provided `mockName` argument. 34 | case unableToParseMockNameArgument 35 | 36 | /// An error indicating that the `@_MockedProperty` macro was unable to 37 | /// parse the provided `isMockAnActor` argument. 38 | case unableToParseIsMockAnActorArgument 39 | 40 | /// An error indicating that the `@_MockedProperty` macro was unable to 41 | /// parse the property binding's name. 42 | case unableToParsePropertyBindingName 43 | 44 | // MARK: Properties 45 | 46 | /// The description of the error. 47 | var description: String { 48 | switch self { 49 | case .canOnlyBeAppliedToPropertyDeclarations: 50 | "@_MockedProperty can only be applied to property declarations." 51 | case .canOnlyBeAppliedToSingleBindingPropertyDeclarations: 52 | "@_MockedProperty can only be applied to single-binding property declarations." 53 | case .noArguments: 54 | "@_MockedProperty was not passed any arguments." 55 | case .unableToParsePropertyTypeArgument: 56 | "@_MockedProperty was unable to parse the provided `propertyType` argument." 57 | case .unableToParseMockNameArgument: 58 | "@_MockedProperty was unable to parse the provided `mockName` argument." 59 | case .unableToParseIsMockAnActorArgument: 60 | "@_MockedProperty was unable to parse the provided `isMockAnActor` argument." 61 | case .unableToParsePropertyBindingName: 62 | "@_MockedProperty was unable to parse the property binding's name." 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Macros/MockedPropertyMacro/MockedPropertyMacro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyMacro.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | import SwiftSyntaxBuilder 9 | import SwiftSyntaxMacros 10 | import SwiftSyntaxSugar 11 | 12 | public struct MockedPropertyMacro {} 13 | -------------------------------------------------------------------------------- /Sources/MockingMacros/MockingPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockingPlugin.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftCompilerPlugin 8 | import SwiftSyntaxMacros 9 | 10 | @main 11 | struct MockingPlugin: CompilerPlugin { 12 | let providingMacros: [any Macro.Type] = [ 13 | MockedMacro.self, 14 | MockedMembersMacro.self, 15 | MockedMethodMacro.self, 16 | MockablePropertyMacro.self, 17 | MockedPropertyMacro.self, 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Models/MacroArguments/MacroArgumentValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacroArgumentValue.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | /// A protocol for argument values that can be parsed from a macro's argument 10 | /// syntax. 11 | protocol MacroArgumentValue { 12 | 13 | /// Creates an instance from the provided `argument`. 14 | /// 15 | /// - Parameter argument: The argument syntax from which to parse the 16 | /// macro argument value. 17 | init?(argument: LabeledExprSyntax) 18 | } 19 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Models/MacroArguments/MockSendableConformance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockSendableConformance.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | /// A `Sendable` conformance that can be applied to a mock declaration. 10 | enum MockSendableConformance: String, MacroArgumentValue { 11 | 12 | /// The mock conforms to the protocol it is mocking, resulting in 13 | /// checked `Sendable` conformance if the protocol inherits from 14 | /// `Sendable`. 15 | case checked 16 | 17 | /// The mock conforms to `@unchecked Sendable`. 18 | case unchecked 19 | 20 | /// Creates a `Sendable` conformance from the provided `argument`. 21 | /// 22 | /// - Parameter argument: The argument syntax from which to parse a 23 | /// `Sendable` conformance. 24 | init?(argument: LabeledExprSyntax) { 25 | guard 26 | let memberAccessExpression = argument.expression.as( 27 | MemberAccessExprSyntax.self 28 | ), 29 | let identifier = memberAccessExpression.declName.baseName.identifier 30 | else { 31 | return nil 32 | } 33 | 34 | self.init(rawValue: identifier.name) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Models/MockMethodNameComponents/MockMethodNameComponent+ID.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockMethodNameComponent+ID.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockMethodNameComponent { 10 | 11 | /// A mock method name component ID. 12 | enum ID: Hashable { 13 | 14 | // MARK: Cases 15 | 16 | /// The method's name. 17 | case methodName 18 | 19 | /// One of the method's parameter names. 20 | /// 21 | /// - Parameter index: The index of the parameter. 22 | case parameterName(Int) 23 | 24 | /// One of the method's parameter types. 25 | /// 26 | /// - Parameter index: The index of the parameter. 27 | case parameterType(Int) 28 | 29 | /// The method's `async` specifier. 30 | case asyncSpecifier 31 | 32 | /// The method's `throws` specifier. 33 | case throwsSpecifier 34 | 35 | /// The method's return type. 36 | case returnType 37 | 38 | /// One of the method's generic requirements. 39 | /// 40 | /// - Parameter index: The index of the generic requirement. 41 | case genericRequirement(Int) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Models/MockMethodNameComponents/MockMethodNameComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockMethodNameComponent.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | /// A mock method name component. 10 | struct MockMethodNameComponent: Identifiable { 11 | 12 | // MARK: Properties 13 | 14 | /// The component's ID. 15 | let id: ID 16 | 17 | /// The component's value. 18 | let value: String 19 | 20 | /// The index at which the component is inserted into the mock method's full 21 | /// name. 22 | let insertionIndex: Int 23 | 24 | // MARK: Initializers 25 | 26 | /// Creates a mock method name component. 27 | /// 28 | /// - Parameters: 29 | /// - id: The component's ID. 30 | /// - value: The component's value. 31 | /// - insertionIndex: The index at which the component is inserted into 32 | /// the mock method's full name. 33 | init( 34 | id: ID, 35 | value: String, 36 | insertionIndex: Int 37 | ) { 38 | self.id = id 39 | self.value = value 40 | self.insertionIndex = insertionIndex 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Models/MockedPropertyType/MockedPropertyType+AsyncSpecifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyType+AsyncSpecifier.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | extension MockedPropertyType { 10 | 11 | /// The `async` specifier to apply to a mocked property's accessor. 12 | enum AsyncSpecifier: String, CaseIterable { 13 | 14 | // MARK: Cases 15 | 16 | /// An `async` specifier. 17 | case async 18 | 19 | // MARK: Initializers 20 | 21 | /// Creates an `async` specifier from the provided `argument`. 22 | /// 23 | /// - Parameter argument: The argument syntax from which to parse an 24 | /// ``AsyncSpecifier``. 25 | /// - Throws: An error if a valid ``AsyncSpecifier`` cannot be parsed 26 | /// from the provided `argument`. 27 | init(argument: LabeledExprSyntax) throws { 28 | guard 29 | let memberAccessExpression = argument.expression.as( 30 | MemberAccessExprSyntax.self 31 | ), 32 | let asyncSpecifier = Self.allCases.first(where: { asyncSpecifier in 33 | let declName = memberAccessExpression.declName 34 | 35 | return declName.baseName.tokenKind == .identifier( 36 | asyncSpecifier.rawValue 37 | ) 38 | }) 39 | else { 40 | throw ParsingError.unableToParseAsyncEffectSpecifier 41 | } 42 | 43 | self = asyncSpecifier 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Models/MockedPropertyType/MockedPropertyType+ParsingError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyType+ParsingError.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | 9 | extension MockedPropertyType { 10 | 11 | /// A parsing error generated by ``MockedPropertyType``. 12 | enum ParsingError: CaseIterable, CustomStringConvertible, Error { 13 | 14 | // MARK: Cases 15 | 16 | /// An error indicating that ``MockedPropertyType`` was unable to parse 17 | /// a valid instance from the provided macro argument. 18 | case unableToParsePropertyType 19 | 20 | /// An error indicating that ``MockedPropertyType`` was unable to parse 21 | /// a valid async effect specifier. 22 | case unableToParseAsyncEffectSpecifier 23 | 24 | /// An error indicating that ``MockedPropertyType`` was unable to parse 25 | /// a valid throws effect specifier. 26 | case unableToParseThrowsEffectSpecifier 27 | 28 | // MARK: Properties 29 | 30 | /// The description of the error. 31 | var description: String { 32 | switch self { 33 | case .unableToParsePropertyType: 34 | "Unable to parse property type." 35 | case .unableToParseAsyncEffectSpecifier: 36 | "Unable to parse async effect specifier." 37 | case .unableToParseThrowsEffectSpecifier: 38 | "Unable to parse throws effect specifier." 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Models/MockedPropertyType/MockedPropertyType+ThrowsSpecifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyType+ThrowsSpecifier.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | extension MockedPropertyType { 10 | 11 | /// The `throws` specifier to apply to a mocked property's accessor. 12 | enum ThrowsSpecifier: String, CaseIterable { 13 | 14 | // MARK: Cases 15 | 16 | /// A `throws` specifier. 17 | case `throws` 18 | 19 | // MARK: Initializers 20 | 21 | /// Creates a `throws` specifier from the provided `argument`. 22 | /// 23 | /// - Parameter argument: The argument syntax from which to parse a 24 | /// ``ThrowsSpecifier``. 25 | /// - Throws: An error if a valid ``ThrowsSpecifier`` cannot be parsed 26 | /// from the provided `argument`. 27 | init(argument: LabeledExprSyntax) throws { 28 | guard 29 | let memberAccessExpression = argument.expression.as( 30 | MemberAccessExprSyntax.self 31 | ), 32 | let throwsSpecifier = Self.allCases.first(where: { throwsSpecifier in 33 | let declName = memberAccessExpression.declName 34 | 35 | return declName.baseName.tokenKind == .identifier( 36 | throwsSpecifier.rawValue 37 | ) 38 | }) 39 | else { 40 | throw ParsingError.unableToParseAsyncEffectSpecifier 41 | } 42 | 43 | self = throwsSpecifier 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/MockingMacros/Models/MockedPropertyType/MockedPropertyType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedPropertyType.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | /// The type of property being mocked. 10 | enum MockedPropertyType { 11 | 12 | // MARK: Cases 13 | 14 | /// A read-only property. 15 | /// 16 | /// - Parameters: 17 | /// - asyncSpecifier: The getter's `async` specifier. 18 | /// - throwsSpecifier: The getter's `throws` specifier. 19 | case readOnly(AsyncSpecifier? = nil, ThrowsSpecifier? = nil) 20 | 21 | /// A read-write property. 22 | case readWrite 23 | 24 | // MARK: Properties 25 | 26 | /// The getter's `async` specifier, if there is one. 27 | var getterAsyncSpecifier: AsyncSpecifier? { 28 | switch self { 29 | case let .readOnly(asyncSpecifier, _): 30 | asyncSpecifier 31 | case .readWrite: 32 | nil 33 | } 34 | } 35 | 36 | /// The getter's `throws` specifier, if there is one. 37 | var getterThrowsSpecifier: ThrowsSpecifier? { 38 | switch self { 39 | case let .readOnly(_, throwsSpecifier): 40 | throwsSpecifier 41 | case .readWrite: 42 | nil 43 | } 44 | } 45 | 46 | // MARK: Initializers 47 | 48 | /// Creates a ``MockedPropertyType`` from the provided `argument`. 49 | /// 50 | /// - Parameter argument: The argument syntax from which to parse a 51 | /// ``MockedPropertyType``. 52 | /// - Throws: An error if a valid ``MockedPropertyType`` cannot be parsed 53 | /// from the provided `argument`. 54 | init(argument: LabeledExprSyntax) throws { 55 | let ( 56 | memberAccessExpression, 57 | arguments 58 | ): ( 59 | MemberAccessExprSyntax, 60 | LabeledExprListSyntax? 61 | ) = if 62 | let functionCallExpression = argument.expression.as( 63 | FunctionCallExprSyntax.self 64 | ), 65 | let memberAccessExpression = functionCallExpression.calledExpression.as( 66 | MemberAccessExprSyntax.self 67 | ) 68 | { 69 | (memberAccessExpression, functionCallExpression.arguments) 70 | } else if 71 | let memberAccessExpression = argument.expression.as( 72 | MemberAccessExprSyntax.self 73 | ) 74 | { 75 | (memberAccessExpression, nil) 76 | } else { 77 | throw ParsingError.unableToParsePropertyType 78 | } 79 | 80 | let declarationNameTokenKind = memberAccessExpression.declName.baseName.tokenKind 81 | 82 | if declarationNameTokenKind == .identifier("readOnly") { 83 | var asyncSpecifier: AsyncSpecifier? 84 | var throwsSpecifier: ThrowsSpecifier? 85 | 86 | if let arguments { 87 | for argument in arguments { 88 | if asyncSpecifier == nil { 89 | asyncSpecifier = try? AsyncSpecifier(argument: argument) 90 | } 91 | 92 | if throwsSpecifier == nil { 93 | throwsSpecifier = try? ThrowsSpecifier(argument: argument) 94 | } 95 | } 96 | } 97 | 98 | self = .readOnly(asyncSpecifier, throwsSpecifier) 99 | } else if declarationNameTokenKind == .identifier("readWrite") { 100 | self = .readWrite 101 | } else { 102 | throw ParsingError.unableToParsePropertyType 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/MockedMacro_MacroErrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMacro_MacroErrorTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct MockedMacro_MacroErrorTests { 12 | 13 | // MARK: Typealiases 14 | 15 | typealias SUT = MockedMacro.MacroError 16 | 17 | // MARK: Description Tests 18 | 19 | @Test(arguments: SUT.allCases) 20 | func description(sut: SUT) { 21 | let expectedDescription = switch sut { 22 | case .canOnlyBeAppliedToProtocols: 23 | "@Mocked can only be applied to protocols." 24 | case .unableToParsePropertyBindingName: 25 | "@Mocked was unable to parse a property binding name." 26 | } 27 | 28 | #expect(sut.description == expectedDescription) 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_AccessLevelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocked_AccessLevelTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct Mocked_AccessLevelTests { 12 | 13 | // MARK: Access Level Tests 14 | 15 | @Test(arguments: mockedTestConfigurations) 16 | func protocolAccessLevels( 17 | interface: InterfaceConfiguration, 18 | mock: MockConfiguration 19 | ) { 20 | assertMocked( 21 | """ 22 | \(interface.accessLevel) protocol Dependency {} 23 | """, 24 | generates: """ 25 | #if SWIFT_MOCKING_ENABLED 26 | @MockedMembers 27 | \(mock.modifiers)class DependencyMock: Dependency { 28 | } 29 | #endif 30 | """ 31 | ) 32 | } 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_InheritanceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocked_InheritanceTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct Mocked_InheritanceTests { 12 | 13 | // MARK: Unconstrained Tests 14 | 15 | @Test(arguments: mockedTestConfigurations) 16 | func unconstrainedProtocol( 17 | interface: InterfaceConfiguration, 18 | mock: MockConfiguration 19 | ) { 20 | assertMocked( 21 | """ 22 | \(interface.accessLevel) protocol Dependency {} 23 | """, 24 | generates: """ 25 | #if SWIFT_MOCKING_ENABLED 26 | @MockedMembers 27 | \(mock.modifiers)class DependencyMock: Dependency { 28 | } 29 | #endif 30 | """ 31 | ) 32 | } 33 | 34 | // MARK: Actor Constrained Tests 35 | 36 | @Test(arguments: mockedTestConfigurations) 37 | func actorConstrainedProtocol( 38 | interface: InterfaceConfiguration, 39 | mock: MockConfiguration 40 | ) { 41 | assertMocked( 42 | """ 43 | \(interface.accessLevel) protocol Dependency: Actor {} 44 | """, 45 | generates: """ 46 | #if SWIFT_MOCKING_ENABLED 47 | @MockedMembers 48 | \(mock.modifiers)actor DependencyMock: Dependency { 49 | } 50 | #endif 51 | """ 52 | ) 53 | } 54 | 55 | // MARK: Class Constrained Tests 56 | 57 | @Test(arguments: mockedTestConfigurations) 58 | func classConstrainedProtocol( 59 | interface: InterfaceConfiguration, 60 | mock: MockConfiguration 61 | ) { 62 | assertMocked( 63 | """ 64 | \(interface.accessLevel) protocol Dependency: AnyObject {} 65 | """, 66 | generates: """ 67 | #if SWIFT_MOCKING_ENABLED 68 | @MockedMembers 69 | \(mock.modifiers)class DependencyMock: Dependency { 70 | } 71 | #endif 72 | """ 73 | ) 74 | } 75 | 76 | // MARK: Actor & Class Constrained Tests 77 | 78 | @Test(arguments: mockedTestConfigurations) 79 | func actorAndClassConstrainedProtocol( 80 | interface: InterfaceConfiguration, 81 | mock: MockConfiguration 82 | ) { 83 | assertMocked( 84 | """ 85 | \(interface.accessLevel) protocol Dependency: Actor, AnyObject {} 86 | """, 87 | generates: """ 88 | #if SWIFT_MOCKING_ENABLED 89 | @MockedMembers 90 | \(mock.modifiers)actor DependencyMock: Dependency { 91 | } 92 | #endif 93 | """ 94 | ) 95 | } 96 | 97 | @Test(arguments: mockedTestConfigurations) 98 | func classAndActorConstrainedProtocol( 99 | interface: InterfaceConfiguration, 100 | mock: MockConfiguration 101 | ) { 102 | assertMocked( 103 | """ 104 | \(interface.accessLevel) protocol Dependency: AnyObject, Actor {} 105 | """, 106 | generates: """ 107 | #if SWIFT_MOCKING_ENABLED 108 | @MockedMembers 109 | \(mock.modifiers)actor DependencyMock: Dependency { 110 | } 111 | #endif 112 | """ 113 | ) 114 | } 115 | } 116 | #endif 117 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_InitializerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocked_InitializerTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct Mocked_InitializerTests { 12 | 13 | // MARK: Default Init Tests 14 | 15 | @Test(arguments: mockedTestConfigurations) 16 | func defaultInit( 17 | interface: InterfaceConfiguration, 18 | mock: MockConfiguration 19 | ) { 20 | assertMocked( 21 | """ 22 | \(interface.accessLevel) protocol Dependency {} 23 | """, 24 | generates: """ 25 | #if SWIFT_MOCKING_ENABLED 26 | @MockedMembers 27 | \(mock.modifiers)class DependencyMock: Dependency { 28 | } 29 | #endif 30 | """ 31 | ) 32 | } 33 | 34 | // MARK: Init Conformance Tests 35 | 36 | @Test(arguments: mockedTestConfigurations) 37 | func initConformance( 38 | interface: InterfaceConfiguration, 39 | mock: MockConfiguration 40 | ) { 41 | assertMocked( 42 | """ 43 | \(interface.accessLevel) protocol Dependency { 44 | init(parameter: Int) 45 | init(parameters: Int...) 46 | init(parameter1: Int, parameter2: Int) 47 | } 48 | """, 49 | generates: """ 50 | #if SWIFT_MOCKING_ENABLED 51 | @MockedMembers 52 | \(mock.modifiers)class DependencyMock: Dependency { 53 | \(mock.memberModifiers)init(parameter: Int) { 54 | } 55 | \(mock.memberModifiers)init(parameters: Int...) { 56 | } 57 | \(mock.memberModifiers)init(parameter1: Int, parameter2: Int) { 58 | } 59 | } 60 | #endif 61 | """ 62 | ) 63 | } 64 | } 65 | #endif 66 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_MacroArgumentsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocked_MacroArgumentsTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct Mocked_MacroArgumentsTests { 12 | 13 | // MARK: Argument parsing tests 14 | 15 | @Test("Doesn't parse values from unknown argument labels.") 16 | func unknownLabel() { 17 | let node = node( 18 | arguments: [ 19 | .macroArgumentSyntax( 20 | label: "sendability", 21 | base: nil, 22 | name: "unchecked" 23 | ), 24 | ] 25 | ) 26 | let arguments = MockedMacro.MacroArguments(node: node) 27 | 28 | #expect(arguments.sendableConformance == .checked) 29 | } 30 | 31 | @Test("Sets default values when no arguments received.") 32 | func defaultValues() { 33 | let node = node(arguments: []) 34 | let arguments = MockedMacro.MacroArguments(node: node) 35 | 36 | #expect(arguments.compilationCondition == .swiftMockingEnabled) 37 | #expect(arguments.sendableConformance == .checked) 38 | } 39 | 40 | @Test("Partial argument lists use default values for missing arguments.") 41 | func partialArgumentList() { 42 | let node = node( 43 | arguments: [ 44 | .macroArgumentSyntax( 45 | label: "sendableConformance", 46 | base: nil, 47 | name: "unchecked" 48 | ), 49 | ] 50 | ) 51 | let arguments = MockedMacro.MacroArguments(node: node) 52 | 53 | #expect(arguments.compilationCondition == .swiftMockingEnabled) 54 | #expect(arguments.sendableConformance == .unchecked) 55 | } 56 | 57 | // MARK: Helper functions 58 | 59 | private func node(arguments: [LabeledExprSyntax]) -> AttributeSyntax { 60 | AttributeSyntax( 61 | atSign: .atSignToken(), 62 | attributeName: IdentifierTypeSyntax(name: "Mocked"), 63 | arguments: .init(LabeledExprListSyntax(arguments)) 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_SendableConformanceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocked_SendableConformanceTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct Mocked_SendableConformanceTests { 12 | 13 | @Test( 14 | "Default conformance doesn't modify inheritance clause.", 15 | arguments: mockedTestConfigurations 16 | ) 17 | func defaultSendableConformance( 18 | interface: InterfaceConfiguration, 19 | mock: MockConfiguration 20 | ) { 21 | assertMocked( 22 | """ 23 | \(interface.accessLevel) protocol Dependency: Sendable {} 24 | """, 25 | sendableConformance: nil, 26 | generates: """ 27 | #if SWIFT_MOCKING_ENABLED 28 | @MockedMembers 29 | \(mock.modifiers)\ 30 | class DependencyMock: Dependency { 31 | } 32 | #endif 33 | """ 34 | ) 35 | } 36 | 37 | @Test( 38 | "Checked conformance doesn't modify inheritance clause.", 39 | arguments: mockedTestConfigurations 40 | ) 41 | func checkedSendableConformance( 42 | interface: InterfaceConfiguration, 43 | mock: MockConfiguration 44 | ) { 45 | assertMocked( 46 | """ 47 | \(interface.accessLevel) protocol Dependency: Sendable {} 48 | """, 49 | sendableConformance: ".checked", 50 | generates: """ 51 | #if SWIFT_MOCKING_ENABLED 52 | @MockedMembers 53 | \(mock.modifiers)\ 54 | class DependencyMock: Dependency { 55 | } 56 | #endif 57 | """ 58 | ) 59 | } 60 | 61 | @Test( 62 | "Unchecked conformance adds @unchecked Sendable to inheritance clause.", 63 | arguments: mockedTestConfigurations 64 | ) 65 | func uncheckedSendableConformance( 66 | interface: InterfaceConfiguration, 67 | mock: MockConfiguration 68 | ) { 69 | assertMocked( 70 | """ 71 | \(interface.accessLevel) protocol Dependency: Sendable {} 72 | """, 73 | sendableConformance: ".unchecked", 74 | generates: """ 75 | #if SWIFT_MOCKING_ENABLED 76 | @MockedMembers 77 | \(mock.modifiers)\ 78 | class DependencyMock: @unchecked Sendable, Dependency { 79 | } 80 | #endif 81 | """ 82 | ) 83 | } 84 | 85 | @Test( 86 | "Argument is valid when MockSendableConformance base is included.", 87 | arguments: mockedTestConfigurations 88 | ) 89 | func argumentWithoutDotNotation( 90 | interface: InterfaceConfiguration, 91 | mock: MockConfiguration 92 | ) { 93 | assertMocked( 94 | """ 95 | \(interface.accessLevel) protocol Dependency: Sendable {} 96 | """, 97 | sendableConformance: "MockSendableConformance.unchecked", 98 | generates: """ 99 | #if SWIFT_MOCKING_ENABLED 100 | @MockedMembers 101 | \(mock.modifiers)\ 102 | class DependencyMock: @unchecked Sendable, Dependency { 103 | } 104 | #endif 105 | """ 106 | ) 107 | } 108 | } 109 | #endif 110 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/TestHelpers/AssertMocked.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertMocked.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import SwiftSyntaxMacroExpansion 9 | import SwiftSyntaxMacrosGenericTestSupport 10 | import Testing 11 | @testable import MockingMacros 12 | 13 | func assertMocked( 14 | _ interface: String, 15 | compilationCondition: String? = nil, 16 | sendableConformance: String? = nil, 17 | generates mock: String, 18 | diagnostics: [DiagnosticSpec] = [], 19 | applyFixIts: [String]? = nil, 20 | fixedSource: String? = nil, 21 | fileID: StaticString = #fileID, 22 | filePath: StaticString = #filePath, 23 | line: UInt = #line, 24 | column: UInt = #column 25 | ) { 26 | var arguments: [String] = [] 27 | 28 | if let compilationCondition { 29 | arguments.append("compilationCondition: \(compilationCondition)") 30 | } 31 | 32 | if let sendableConformance { 33 | arguments.append("sendableConformance: \(sendableConformance)") 34 | } 35 | 36 | var macro = "@Mocked" 37 | 38 | if !arguments.isEmpty { 39 | macro += "(" + arguments.joined(separator: ", ") + ")" 40 | } 41 | 42 | assertMacroExpansion( 43 | """ 44 | \(macro) 45 | \(interface) 46 | """, 47 | expandedSource: """ 48 | \(interface) 49 | 50 | \(mock) 51 | """, 52 | diagnostics: diagnostics, 53 | macroSpecs: [ 54 | "Mocked": MacroSpec(type: MockedMacro.self), 55 | ], 56 | applyFixIts: applyFixIts, 57 | fixedSource: fixedSource, 58 | failureHandler: { testFailure in 59 | Issue.record( 60 | "\(testFailure.message)", 61 | sourceLocation: SourceLocation( 62 | fileID: testFailure.location.fileID, 63 | filePath: testFailure.location.filePath, 64 | line: testFailure.location.line, 65 | column: testFailure.location.column 66 | ) 67 | ) 68 | }, 69 | fileID: fileID, 70 | filePath: filePath, 71 | line: line, 72 | column: column 73 | ) 74 | } 75 | #endif 76 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/TestHelpers/InterfaceConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceConfiguration.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import SwiftSyntaxSugar 9 | 10 | struct InterfaceConfiguration { 11 | 12 | // MARK: Properties 13 | 14 | let accessLevel: AccessLevelSyntax 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/TestHelpers/MockConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockConfiguration.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import SwiftSyntaxSugar 9 | 10 | struct MockConfiguration { 11 | 12 | // MARK: Properties 13 | 14 | let accessLevel: AccessLevelSyntax 15 | let modifiers: String 16 | let memberModifiers: String 17 | 18 | // MARK: Initializers 19 | 20 | init(interfaceAccessLevel: AccessLevelSyntax) { 21 | let accessLevel: AccessLevelSyntax = switch interfaceAccessLevel { 22 | case .fileprivate, .internal, .open, .package, .public: 23 | interfaceAccessLevel 24 | case .private: 25 | .fileprivate 26 | } 27 | 28 | let modifiers: String 29 | let memberModifiers: String 30 | 31 | switch accessLevel { 32 | case .fileprivate, .open, .package, .private, .public: 33 | modifiers = "\(accessLevel) final " 34 | memberModifiers = "\(accessLevel) " 35 | case .internal: 36 | modifiers = "final " 37 | memberModifiers = "" 38 | } 39 | 40 | self.accessLevel = accessLevel 41 | self.modifiers = modifiers 42 | self.memberModifiers = memberModifiers 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMacro/TestHelpers/MockedTestConfigurations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedTestConfigurations.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import SwiftSyntaxSugar 9 | 10 | var mockedTestConfigurations: [(InterfaceConfiguration, MockConfiguration)] { 11 | AccessLevelSyntax.allCases.map { accessLevel in 12 | ( 13 | InterfaceConfiguration(accessLevel: accessLevel), 14 | MockConfiguration(interfaceAccessLevel: accessLevel) 15 | ) 16 | } 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMembersMacro/MockedMembers_InitializerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMembers_InitializerTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct MockedMembers_InitializerTests { 12 | 13 | // MARK: Initializer Tests 14 | 15 | @Test 16 | func noInitializer() { 17 | assertMockedMembers( 18 | """ 19 | final class Mock { 20 | } 21 | """, 22 | generates: """ 23 | final class Mock { 24 | 25 | init() { 26 | } 27 | } 28 | """ 29 | ) 30 | } 31 | 32 | @Test 33 | func emptyInitializer() { 34 | assertMockedMembers( 35 | """ 36 | final class Mock { 37 | init() { 38 | } 39 | } 40 | """, 41 | generates: """ 42 | final class Mock { 43 | init() { 44 | } 45 | } 46 | """ 47 | ) 48 | } 49 | 50 | @Test 51 | func nonEmptyInitializer() { 52 | assertMockedMembers( 53 | """ 54 | final class Mock { 55 | init(parameter: Int) { 56 | } 57 | } 58 | """, 59 | generates: """ 60 | final class Mock { 61 | init(parameter: Int) { 62 | } 63 | 64 | init() { 65 | } 66 | } 67 | """ 68 | ) 69 | } 70 | } 71 | #endif 72 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMembersMacro/MockedMembers_MethodOverloadsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMembers_MethodOverloadsTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct MockedMembers_MethodOverloadsTests { 12 | 13 | // MARK: Method Overloads Tests 14 | 15 | @Test 16 | func methodOverloads() { 17 | assertMockedMembers( 18 | """ 19 | final class Mock { 20 | func method() 21 | func method() -> String 22 | } 23 | """, 24 | generates: """ 25 | final class Mock { 26 | @_MockedMethod( 27 | \tmockName: "Mock", 28 | \tisMockAnActor: false, 29 | \tmockMethodName: "method" 30 | ) 31 | func method() 32 | @_MockedMethod( 33 | \tmockName: "Mock", 34 | \tisMockAnActor: false, 35 | \tmockMethodName: "methodReturningString" 36 | ) 37 | func method() -> String 38 | 39 | init() { 40 | } 41 | } 42 | """ 43 | ) 44 | } 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMembersMacro/TestHelpers/AssertMockedMembers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertMockedMembers.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import SwiftSyntaxMacroExpansion 9 | import SwiftSyntaxMacrosGenericTestSupport 10 | import Testing 11 | @testable import MockingMacros 12 | 13 | func assertMockedMembers( 14 | _ mock: String, 15 | generates expandedMock: String, 16 | diagnostics: [DiagnosticSpec] = [], 17 | applyFixIts: [String]? = nil, 18 | fixedSource: String? = nil, 19 | fileID: StaticString = #fileID, 20 | filePath: StaticString = #filePath, 21 | line: UInt = #line, 22 | column: UInt = #column 23 | ) { 24 | assertMacroExpansion( 25 | """ 26 | @MockedMembers 27 | \(mock) 28 | """, 29 | expandedSource: expandedMock, 30 | diagnostics: diagnostics, 31 | macroSpecs: [ 32 | "MockedMembers": MacroSpec(type: MockedMembersMacro.self), 33 | ], 34 | applyFixIts: applyFixIts, 35 | fixedSource: fixedSource, 36 | failureHandler: { testFailure in 37 | Issue.record( 38 | "\(testFailure.message)", 39 | sourceLocation: SourceLocation( 40 | fileID: testFailure.location.fileID, 41 | filePath: testFailure.location.filePath, 42 | line: testFailure.location.line, 43 | column: testFailure.location.column 44 | ) 45 | ) 46 | }, 47 | fileID: fileID, 48 | filePath: filePath, 49 | line: line, 50 | column: column 51 | ) 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMethodMacro/MockedMethod_VariadicParameterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedMethod_VariadicParameterTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct MockedMethod_VariadicParameterTests { 12 | 13 | // MARK: Variadic Parameter Tests 14 | 15 | @Test 16 | func variadicParameters() { 17 | assertMockedMethod( 18 | """ 19 | func method(strings: String..., integers: Int...) 20 | """, 21 | named: "method", 22 | generates: """ 23 | func method(strings: String..., integers: Int...) { 24 | self.__method.recordInput( 25 | ( 26 | strings, 27 | integers 28 | ) 29 | ) 30 | let _invoke = self.__method.closure() 31 | _invoke?( 32 | strings, 33 | integers 34 | ) 35 | } 36 | 37 | /// An implementation for `DependencyMock._method`. 38 | enum MethodImplementation< 39 | \tArguments 40 | >: @unchecked Sendable, MockVoidParameterizedMethodImplementation { 41 | 42 | /// The implementation's closure type. 43 | typealias Closure = ([String], [Int]) -> Void 44 | 45 | /// Does nothing when invoked. 46 | case unimplemented 47 | 48 | /// Invokes the provided closure when invoked. 49 | /// 50 | /// - Parameter closure: The closure to invoke. 51 | case uncheckedInvokes(_ closure: Closure) 52 | 53 | /// Invokes the provided closure when invoked. 54 | /// 55 | /// - Parameter closure: The closure to invoke. 56 | static func invokes( 57 | \t_ closure: @Sendable @escaping ([String], [Int]) -> Void 58 | ) -> Self where Arguments: Sendable { 59 | .uncheckedInvokes(closure) 60 | } 61 | 62 | /// The implementation as a closure, or `nil` if unimplemented. 63 | var _closure: Closure? { 64 | switch self { 65 | case .unimplemented: 66 | nil 67 | case let .uncheckedInvokes(closure): 68 | closure 69 | } 70 | } 71 | } 72 | 73 | private let __method = MockVoidParameterizedMethod< 74 | \tMethodImplementation< 75 | \t\t(strings: [String], integers: [Int]) 76 | \t> 77 | >.makeMethod() 78 | 79 | var _method: MockVoidParameterizedMethod< 80 | \tMethodImplementation< 81 | \t\t(strings: [String], integers: [Int]) 82 | \t> 83 | > { 84 | self.__method.method 85 | } 86 | """ 87 | ) 88 | } 89 | } 90 | #endif 91 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedMethodMacro/TestHelpers/AssertMockedMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertMockedMethod.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import SwiftSyntaxMacroExpansion 9 | import SwiftSyntaxMacrosGenericTestSupport 10 | import Testing 11 | @testable import MockingMacros 12 | 13 | func assertMockedMethod( 14 | _ method: String, 15 | named mockMethodName: String, 16 | generates expandedSource: String, 17 | diagnostics: [DiagnosticSpec] = [], 18 | applyFixIts: [String]? = nil, 19 | fixedSource: String? = nil, 20 | fileID: StaticString = #fileID, 21 | filePath: StaticString = #filePath, 22 | line: UInt = #line, 23 | column: UInt = #column 24 | ) { 25 | assertMacroExpansion( 26 | """ 27 | @_MockedMethod( 28 | mockName: "DependencyMock", 29 | isMockAnActor: false, 30 | mockMethodName: "\(mockMethodName)" 31 | ) 32 | \(method) 33 | """, 34 | expandedSource: expandedSource, 35 | diagnostics: diagnostics, 36 | macroSpecs: [ 37 | "_MockedMethod": MacroSpec(type: MockedMethodMacro.self), 38 | ], 39 | applyFixIts: applyFixIts, 40 | fixedSource: fixedSource, 41 | failureHandler: { testFailure in 42 | Issue.record( 43 | "\(testFailure.message)", 44 | sourceLocation: SourceLocation( 45 | fileID: testFailure.location.fileID, 46 | filePath: testFailure.location.filePath, 47 | line: testFailure.location.line, 48 | column: testFailure.location.column 49 | ) 50 | ) 51 | }, 52 | fileID: fileID, 53 | filePath: filePath, 54 | line: line, 55 | column: column 56 | ) 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedPropertyMacro/MockedProperty_ReadWritePropertyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockedProperty_ReadWritePropertyTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct MockedProperty_ReadWritePropertyTests { 12 | 13 | // MARK: Read-Write Property Tests 14 | 15 | @Test 16 | func readWriteProperty() { 17 | assertMockedProperty( 18 | """ 19 | var property: String 20 | """, 21 | ofType: ".readWrite", 22 | generates: """ 23 | var property: String { 24 | get { 25 | self.__property.get() 26 | } 27 | set { 28 | self.__property.set(newValue) 29 | } 30 | } 31 | 32 | private let __property = MockReadWriteProperty< 33 | \tString 34 | >.makeProperty( 35 | exposedPropertyDescription: MockImplementationDescription( 36 | type: DependencyMock.self, 37 | member: "_property" 38 | ) 39 | ) 40 | 41 | var _property: MockReadWriteProperty< 42 | \tString 43 | > { 44 | self.__property.property 45 | } 46 | """ 47 | ) 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Macros/MockedPropertyMacro/TestHelpers/AssertMockedProperty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertMockedProperty.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | #if canImport(MockingMacros) 8 | import SwiftSyntaxMacroExpansion 9 | import SwiftSyntaxMacrosGenericTestSupport 10 | import Testing 11 | @testable import MockingMacros 12 | 13 | func assertMockedProperty( 14 | _ property: String, 15 | ofType propertyType: String, 16 | generates expandedSource: String, 17 | diagnostics: [DiagnosticSpec] = [], 18 | applyFixIts: [String]? = nil, 19 | fixedSource: String? = nil, 20 | fileID: StaticString = #fileID, 21 | filePath: StaticString = #filePath, 22 | line: UInt = #line, 23 | column: UInt = #column 24 | ) { 25 | assertMacroExpansion( 26 | """ 27 | @MockedProperty( 28 | \(propertyType), 29 | mockName: "DependencyMock", 30 | isMockAnActor: false 31 | ) 32 | \(property) 33 | """, 34 | expandedSource: expandedSource, 35 | diagnostics: diagnostics, 36 | macroSpecs: [ 37 | "MockedProperty": MacroSpec(type: MockedPropertyMacro.self), 38 | ], 39 | applyFixIts: applyFixIts, 40 | fixedSource: fixedSource, 41 | failureHandler: { testFailure in 42 | Issue.record( 43 | "\(testFailure.message)", 44 | sourceLocation: SourceLocation( 45 | fileID: testFailure.location.fileID, 46 | filePath: testFailure.location.filePath, 47 | line: testFailure.location.line, 48 | column: testFailure.location.column 49 | ) 50 | ) 51 | }, 52 | fileID: fileID, 53 | filePath: filePath, 54 | line: line, 55 | column: column 56 | ) 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/Models/MockSendableConformanceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockSendableConformanceTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | import Testing 9 | @testable import MockingMacros 10 | 11 | struct MockSendableConformanceTests { 12 | 13 | // MARK: Init from argument tests 14 | 15 | @Test("Initializes as .checked from a valid checked argument.") 16 | func initCheckedFromArgument() { 17 | let sendableConformance = MockSendableConformance( 18 | argument: .macroArgumentSyntax( 19 | label: "sendableConformance", 20 | base: nil, 21 | name: "checked" 22 | ) 23 | ) 24 | #expect(sendableConformance == .checked) 25 | } 26 | 27 | @Test("Initializes as .unchecked from a valid unchecked argument.") 28 | func initUncheckedFromArgument() { 29 | let sendableConformance = MockSendableConformance( 30 | argument: .macroArgumentSyntax( 31 | label: "sendableConformance", 32 | base: nil, 33 | name: "unchecked" 34 | ) 35 | ) 36 | #expect(sendableConformance == .unchecked) 37 | } 38 | 39 | @Test("Initializes as nil from an unrecognized argument.") 40 | func initNilFromArgument() { 41 | let sendableConformance = MockSendableConformance( 42 | argument: .macroArgumentSyntax( 43 | label: "sendableConformance", 44 | base: nil, 45 | name: "unrecognized" 46 | ) 47 | ) 48 | #expect(sendableConformance == nil) 49 | } 50 | 51 | @Test("Initializes as nil from argument with invalid name token.") 52 | func initNilFromNamelessArgument() { 53 | let sendableConformance = MockSendableConformance( 54 | argument: LabeledExprSyntax( 55 | label: .identifier("sendableConformance"), 56 | colon: .colonToken(), 57 | expression: MemberAccessExprSyntax( 58 | period: .periodToken(), 59 | declName: DeclReferenceExprSyntax(baseName: .commaToken()) 60 | ) 61 | ) 62 | ) 63 | #expect(sendableConformance == nil) 64 | } 65 | 66 | @Test("Initializes from argument when base is included.") 67 | func initFromArgumentWithBase() { 68 | let sendableConformance = MockSendableConformance( 69 | argument: .macroArgumentSyntax( 70 | label: "sendableConformance", 71 | base: "MockSendableConformance", 72 | name: "checked" 73 | ) 74 | ) 75 | #expect(sendableConformance == .checked) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/MockingMacrosTests/TestHelpers/Extensions/LabeledExprSyntax+MacroArgumentSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LabeledExprSyntax+MacroArgumentSyntax.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import SwiftSyntax 8 | 9 | extension LabeledExprSyntax { 10 | 11 | /// Returns the argument syntax for the provided label, base, and name. 12 | /// 13 | /// ```swift 14 | /// argument(label: "enumArgument", base: "SomeEnum", name: "someCase") 15 | /// // Represents 16 | /// enumArgument: SomeEnum.someCase 17 | /// ``` 18 | static func macroArgumentSyntax( 19 | label: String, 20 | base: String?, 21 | name: String 22 | ) -> LabeledExprSyntax { 23 | LabeledExprSyntax( 24 | label: .identifier(label), 25 | colon: .colonToken(), 26 | expression: MemberAccessExprSyntax( 27 | base: base.map { 28 | DeclReferenceExprSyntax(baseName: .identifier($0)) 29 | }, 30 | period: .periodToken(), 31 | declName: DeclReferenceExprSyntax(baseName: .identifier(name)) 32 | ) 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockImplementationDescription/MockImplementationDescriptionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockImplementationDescriptionTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import XCTest 8 | @testable import Mocking 9 | 10 | final class MockImplementationDescriptionTests: XCTestCase { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = MockImplementationDescription 15 | 16 | // MARK: Debug Description Tests 17 | 18 | func testDebugDescription() { 19 | let sut = SUT(type: Self.self, member: "sut") 20 | 21 | XCTAssertEqual( 22 | sut.debugDescription, 23 | "MockImplementationDescriptionTests.sut" 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethodTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedAsyncMethodTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Testing 8 | @testable import Mocking 9 | 10 | struct MockReturningNonParameterizedAsyncMethodTests { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = MockReturningNonParameterizedAsyncMethod 15 | typealias ReturnValue = Int 16 | 17 | // MARK: Implementation Tests 18 | 19 | @Test 20 | func implementation() async { 21 | let (sut, invoke, reset) = self.sut() 22 | 23 | guard case .unimplemented = sut.implementation else { 24 | Issue.record("Expected implementation to equal .unimplemented") 25 | return 26 | } 27 | 28 | await confirmation(expectedCount: 1) { confirmation in 29 | sut.implementation = .uncheckedInvokes { 30 | confirmation.confirm() 31 | return 5 32 | } 33 | 34 | _ = await invoke() 35 | } 36 | 37 | reset() 38 | 39 | guard case .unimplemented = sut.implementation else { 40 | Issue.record("Expected implementation to equal .unimplemented") 41 | return 42 | } 43 | } 44 | 45 | // MARK: Call Count Tests 46 | 47 | @Test 48 | func callCount() async { 49 | let (sut, invoke, reset) = self.sut() 50 | 51 | sut.implementation = .uncheckedInvokes { 5 } 52 | 53 | #expect(sut.callCount == .zero) 54 | 55 | _ = await invoke() 56 | #expect(sut.callCount == 1) 57 | 58 | reset() 59 | #expect(sut.callCount == .zero) 60 | } 61 | 62 | // MARK: Returned Values Tests 63 | 64 | @Test 65 | func returnedValues() async { 66 | let (sut, invoke, reset) = self.sut() 67 | 68 | sut.implementation = .uncheckedInvokes { 5 } 69 | 70 | #expect(sut.returnedValues.isEmpty) 71 | 72 | _ = await invoke() 73 | #expect(sut.returnedValues == [5]) 74 | 75 | sut.implementation = .uncheckedInvokes { 10 } 76 | 77 | _ = await invoke() 78 | #expect(sut.returnedValues == [5, 10]) 79 | 80 | reset() 81 | #expect(sut.returnedValues.isEmpty) 82 | } 83 | 84 | // MARK: Last Returned Value Tests 85 | 86 | @Test 87 | func lastReturnedValue() async { 88 | let (sut, invoke, reset) = self.sut() 89 | 90 | sut.implementation = .uncheckedInvokes { 5 } 91 | 92 | #expect(sut.lastReturnedValue == nil) 93 | 94 | _ = await invoke() 95 | #expect(sut.lastReturnedValue == 5) 96 | 97 | sut.implementation = .uncheckedInvokes { 10 } 98 | 99 | _ = await invoke() 100 | #expect(sut.lastReturnedValue == 10) 101 | 102 | reset() 103 | #expect(sut.lastReturnedValue == nil) 104 | } 105 | } 106 | 107 | // MARK: - Helpers 108 | 109 | extension MockReturningNonParameterizedAsyncMethodTests { 110 | private func sut() -> ( 111 | method: SUT, 112 | invoke: () async -> ReturnValue, 113 | reset: () -> Void 114 | ) { 115 | SUT.makeMethod( 116 | exposedMethodDescription: MockImplementationDescription( 117 | type: Self.self, 118 | member: "sut" 119 | ) 120 | ) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethod_ImplementationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedAsyncMethod_ImplementationTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Testing 8 | @testable import Mocking 9 | 10 | struct MockReturningNonParameterizedAsyncMethod_ImplementationTests { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = Method.Implementation 15 | typealias Method = MockReturningNonParameterizedAsyncMethod 16 | typealias ReturnValue = Int 17 | 18 | // MARK: Call As Function Tests 19 | 20 | @Test 21 | func unimplemented() async { 22 | let sut: SUT = .unimplemented 23 | let returnValue = await sut() 24 | 25 | #expect(returnValue == nil) 26 | } 27 | 28 | @Test 29 | func uncheckedInvokes() async { 30 | let sut: SUT = .uncheckedInvokes { 5 } 31 | let returnValue = await sut() 32 | 33 | #expect(returnValue == 5) 34 | } 35 | 36 | @Test 37 | func invokes() async { 38 | let sut: SUT = .invokes { 5 } 39 | let returnValue = await sut() 40 | 41 | #expect(returnValue == 5) 42 | } 43 | 44 | @Test 45 | func uncheckedReturns() async { 46 | let sut: SUT = .uncheckedReturns(5) 47 | let returnValue = await sut() 48 | 49 | #expect(returnValue == 5) 50 | } 51 | 52 | @Test 53 | func returns() async { 54 | let sut: SUT = .returns(5) 55 | let returnValue = await sut() 56 | 57 | #expect(returnValue == 5) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethod_ImplementationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedAsyncThrowingMethod_ImplementationTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Testing 9 | @testable import Mocking 10 | 11 | struct MockReturningNonParameterizedAsyncThrowingMethod_ImplementationTests { 12 | 13 | // MARK: Typealiases 14 | 15 | typealias SUT = Method.Implementation 16 | typealias Method = MockReturningNonParameterizedAsyncThrowingMethod 17 | typealias ReturnValue = Int 18 | 19 | // MARK: Call As Function Tests 20 | 21 | @Test 22 | func unimplemented() async throws { 23 | let sut: SUT = .unimplemented 24 | let returnValue = try await sut() 25 | 26 | #expect(returnValue == nil) 27 | } 28 | 29 | @Test 30 | func uncheckedInvokes() async throws { 31 | let sut: SUT = .uncheckedInvokes { 5 } 32 | let returnValue = try await sut() 33 | 34 | #expect(returnValue == 5) 35 | } 36 | 37 | @Test 38 | func invokes() async throws { 39 | let sut: SUT = .invokes { 5 } 40 | let returnValue = try await sut() 41 | 42 | #expect(returnValue == 5) 43 | } 44 | 45 | @Test 46 | func `throws`() async throws { 47 | let sut: SUT = .throws(URLError(.badURL)) 48 | 49 | await #expect(throws: URLError(.badURL)) { 50 | _ = try await sut() 51 | } 52 | } 53 | 54 | @Test 55 | func uncheckedReturns() async throws { 56 | let sut: SUT = .uncheckedReturns(5) 57 | let returnValue = try await sut() 58 | 59 | #expect(returnValue == 5) 60 | } 61 | 62 | @Test 63 | func returns() async throws { 64 | let sut: SUT = .returns(5) 65 | let returnValue = try await sut() 66 | 67 | #expect(returnValue == 5) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethodTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedMethodTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Testing 8 | @testable import Mocking 9 | 10 | struct MockReturningNonParameterizedMethodTests { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = MockReturningNonParameterizedMethod 15 | typealias ReturnValue = Int 16 | 17 | // MARK: Implementation Tests 18 | 19 | @Test 20 | func implementation() async { 21 | let (sut, invoke, reset) = self.sut() 22 | 23 | guard case .unimplemented = sut.implementation else { 24 | Issue.record("Expected implementation to equal .unimplemented") 25 | return 26 | } 27 | 28 | await confirmation(expectedCount: 1) { confirmation in 29 | sut.implementation = .uncheckedInvokes { 30 | confirmation.confirm() 31 | return 5 32 | } 33 | 34 | _ = invoke() 35 | } 36 | 37 | reset() 38 | 39 | guard case .unimplemented = sut.implementation else { 40 | Issue.record("Expected implementation to equal .unimplemented") 41 | return 42 | } 43 | } 44 | 45 | // MARK: Call Count Tests 46 | 47 | @Test 48 | func callCount() { 49 | let (sut, invoke, reset) = self.sut() 50 | 51 | sut.implementation = .uncheckedInvokes { 5 } 52 | 53 | #expect(sut.callCount == .zero) 54 | 55 | _ = invoke() 56 | #expect(sut.callCount == 1) 57 | 58 | reset() 59 | #expect(sut.callCount == .zero) 60 | } 61 | 62 | // MARK: Returned Values Tests 63 | 64 | @Test 65 | func returnedValues() { 66 | let (sut, invoke, reset) = self.sut() 67 | 68 | sut.implementation = .uncheckedInvokes { 5 } 69 | 70 | #expect(sut.returnedValues.isEmpty) 71 | 72 | _ = invoke() 73 | #expect(sut.returnedValues == [5]) 74 | 75 | sut.implementation = .uncheckedInvokes { 10 } 76 | 77 | _ = invoke() 78 | #expect(sut.returnedValues == [5, 10]) 79 | 80 | reset() 81 | #expect(sut.returnedValues.isEmpty) 82 | } 83 | 84 | // MARK: Last Returned Value Tests 85 | 86 | @Test 87 | func lastReturnedValue() { 88 | let (sut, invoke, reset) = self.sut() 89 | 90 | sut.implementation = .uncheckedInvokes { 5 } 91 | 92 | #expect(sut.lastReturnedValue == nil) 93 | 94 | _ = invoke() 95 | #expect(sut.lastReturnedValue == 5) 96 | 97 | sut.implementation = .uncheckedInvokes { 10 } 98 | 99 | _ = invoke() 100 | #expect(sut.lastReturnedValue == 10) 101 | 102 | reset() 103 | #expect(sut.lastReturnedValue == nil) 104 | } 105 | } 106 | 107 | // MARK: - Helpers 108 | 109 | extension MockReturningNonParameterizedMethodTests { 110 | private func sut() -> ( 111 | method: SUT, 112 | invoke: () -> ReturnValue, 113 | reset: () -> Void 114 | ) { 115 | SUT.makeMethod( 116 | exposedMethodDescription: MockImplementationDescription( 117 | type: Self.self, 118 | member: "sut" 119 | ) 120 | ) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethod_ImplementationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedMethod_ImplementationTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Testing 8 | @testable import Mocking 9 | 10 | struct MockReturningNonParameterizedMethod_ImplementationTests { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = Method.Implementation 15 | typealias Method = MockReturningNonParameterizedMethod 16 | typealias ReturnValue = Int 17 | 18 | // MARK: Call As Function Tests 19 | 20 | @Test 21 | func unimplemented() { 22 | let sut: SUT = .unimplemented 23 | let returnValue = sut() 24 | 25 | #expect(returnValue == nil) 26 | } 27 | 28 | @Test 29 | func uncheckedInvokes() { 30 | let sut: SUT = .uncheckedInvokes { 5 } 31 | let returnValue = sut() 32 | 33 | #expect(returnValue == 5) 34 | } 35 | 36 | @Test 37 | func invokes() { 38 | let sut: SUT = .invokes { 5 } 39 | let returnValue = sut() 40 | 41 | #expect(returnValue == 5) 42 | } 43 | 44 | @Test 45 | func uncheckedReturns() { 46 | let sut: SUT = .uncheckedReturns(5) 47 | let returnValue = sut() 48 | 49 | #expect(returnValue == 5) 50 | } 51 | 52 | @Test 53 | func returns() { 54 | let sut: SUT = .returns(5) 55 | let returnValue = sut() 56 | 57 | #expect(returnValue == 5) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethod_ImplementationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReturningNonParameterizedThrowingMethod_ImplementationTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Testing 9 | @testable import Mocking 10 | 11 | struct MockReturningNonParameterizedThrowingMethod_ImplementationTests { 12 | 13 | // MARK: Typealiases 14 | 15 | typealias SUT = Method.Implementation 16 | typealias Method = MockReturningNonParameterizedThrowingMethod 17 | typealias ReturnValue = Int 18 | 19 | // MARK: Call As Function Tests 20 | 21 | @Test 22 | func unimplemented() throws { 23 | let sut: SUT = .unimplemented 24 | let returnValue = try sut() 25 | 26 | #expect(returnValue == nil) 27 | } 28 | 29 | @Test 30 | func uncheckedInvokes() throws { 31 | let sut: SUT = .uncheckedInvokes { 5 } 32 | let returnValue = try sut() 33 | 34 | #expect(returnValue == 5) 35 | } 36 | 37 | @Test 38 | func invokes() throws { 39 | let sut: SUT = .invokes { 5 } 40 | let returnValue = try sut() 41 | 42 | #expect(returnValue == 5) 43 | } 44 | 45 | @Test 46 | func `throws`() throws { 47 | let sut: SUT = .throws(URLError(.badURL)) 48 | 49 | #expect(throws: URLError(.badURL)) { 50 | _ = try sut() 51 | } 52 | } 53 | 54 | @Test 55 | func uncheckedReturns() throws { 56 | let sut: SUT = .uncheckedReturns(5) 57 | let returnValue = try sut() 58 | 59 | #expect(returnValue == 5) 60 | } 61 | 62 | @Test 63 | func returns() throws { 64 | let sut: SUT = .returns(5) 65 | let returnValue = try sut() 66 | 67 | #expect(returnValue == 5) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethodTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedAsyncMethodTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Testing 8 | @testable import Mocking 9 | 10 | struct MockVoidNonParameterizedAsyncMethodTests { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = MockVoidNonParameterizedAsyncMethod 15 | 16 | // MARK: Implementation Tests 17 | 18 | @Test 19 | func implementation() async { 20 | let (sut, invoke, reset) = SUT.makeMethod() 21 | 22 | guard case .unimplemented = sut.implementation else { 23 | Issue.record("Expected implementation to equal .unimplemented") 24 | return 25 | } 26 | 27 | await confirmation(expectedCount: 1) { confirmation in 28 | sut.implementation = .uncheckedInvokes { 29 | confirmation.confirm() 30 | } 31 | 32 | _ = await invoke() 33 | } 34 | 35 | reset() 36 | 37 | guard case .unimplemented = sut.implementation else { 38 | Issue.record("Expected implementation to equal .unimplemented") 39 | return 40 | } 41 | } 42 | 43 | // MARK: Call Count Tests 44 | 45 | @Test 46 | func callCount() async { 47 | let (sut, invoke, reset) = SUT.makeMethod() 48 | 49 | #expect(sut.callCount == .zero) 50 | 51 | await invoke() 52 | #expect(sut.callCount == 1) 53 | 54 | reset() 55 | #expect(sut.callCount == .zero) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethod_ImplementationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedAsyncMethod_ImplementationTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Testing 8 | @testable import Mocking 9 | 10 | struct MockVoidNonParameterizedAsyncMethod_ImplementationTests { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = Method.Implementation 15 | typealias Method = MockVoidNonParameterizedAsyncMethod 16 | 17 | // MARK: Call As Function Tests 18 | 19 | @Test 20 | func unimplemented() async { 21 | await confirmation(expectedCount: .zero) { _ in 22 | let sut: SUT = .unimplemented 23 | 24 | await sut() 25 | } 26 | } 27 | 28 | @Test 29 | func uncheckedInvokes() async { 30 | await confirmation(expectedCount: 1) { confirmation in 31 | let sut: SUT = .uncheckedInvokes { 32 | confirmation.confirm() 33 | } 34 | 35 | await sut() 36 | } 37 | } 38 | 39 | @Test 40 | func invokes() async { 41 | await confirmation(expectedCount: 1) { confirmation in 42 | let sut: SUT = .invokes { 43 | confirmation.confirm() 44 | } 45 | 46 | await sut() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethod_ImplementationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedAsyncThrowingMethod_ImplementationTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Testing 9 | @testable import Mocking 10 | 11 | struct MockVoidNonParameterizedAsyncThrowingMethod_ImplementationTests { 12 | 13 | // MARK: Typealiases 14 | 15 | typealias SUT = Method.Implementation 16 | typealias Method = MockVoidNonParameterizedAsyncThrowingMethod 17 | 18 | // MARK: Call As Function Tests 19 | 20 | @Test 21 | func unimplemented() async throws { 22 | try await confirmation(expectedCount: .zero) { _ in 23 | let sut: SUT = .unimplemented 24 | 25 | try await sut() 26 | } 27 | } 28 | 29 | @Test 30 | func uncheckedInvokes() async throws { 31 | try await confirmation(expectedCount: 1) { confirmation in 32 | let sut: SUT = .uncheckedInvokes { 33 | confirmation.confirm() 34 | } 35 | 36 | try await sut() 37 | } 38 | } 39 | 40 | @Test 41 | func invokes() async throws { 42 | try await confirmation(expectedCount: 1) { confirmation in 43 | let sut: SUT = .invokes { 44 | confirmation.confirm() 45 | } 46 | 47 | try await sut() 48 | } 49 | } 50 | 51 | @Test 52 | func `throws`() async throws { 53 | let sut: SUT = .throws(URLError(.badURL)) 54 | 55 | await #expect(throws: URLError(.badURL)) { 56 | _ = try await sut() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethodTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedMethodTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Testing 8 | @testable import Mocking 9 | 10 | struct MockVoidNonParameterizedMethodTests { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = MockVoidNonParameterizedMethod 15 | 16 | // MARK: Implementation Tests 17 | 18 | @Test 19 | func implementation() async { 20 | let (sut, invoke, reset) = SUT.makeMethod() 21 | 22 | guard case .unimplemented = sut.implementation else { 23 | Issue.record("Expected implementation to equal .unimplemented") 24 | return 25 | } 26 | 27 | await confirmation(expectedCount: 1) { confirmation in 28 | sut.implementation = .uncheckedInvokes { 29 | confirmation.confirm() 30 | } 31 | 32 | _ = invoke() 33 | } 34 | 35 | reset() 36 | 37 | guard case .unimplemented = sut.implementation else { 38 | Issue.record("Expected implementation to equal .unimplemented") 39 | return 40 | } 41 | } 42 | 43 | // MARK: Call Count Tests 44 | 45 | @Test 46 | func callCount() { 47 | let (sut, invoke, reset) = SUT.makeMethod() 48 | 49 | #expect(sut.callCount == .zero) 50 | 51 | invoke() 52 | #expect(sut.callCount == 1) 53 | 54 | reset() 55 | #expect(sut.callCount == .zero) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethod_ImplementationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedMethod_ImplementationTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Testing 8 | @testable import Mocking 9 | 10 | struct MockVoidNonParameterizedMethod_ImplementationTests { 11 | 12 | // MARK: Typealiases 13 | 14 | typealias SUT = Method.Implementation 15 | typealias Method = MockVoidNonParameterizedMethod 16 | 17 | // MARK: Call As Function Tests 18 | 19 | @Test 20 | func unimplemented() async { 21 | await confirmation(expectedCount: .zero) { _ in 22 | let sut: SUT = .unimplemented 23 | 24 | sut() 25 | } 26 | } 27 | 28 | @Test 29 | func uncheckedInvokes() async { 30 | await confirmation(expectedCount: 1) { confirmation in 31 | let sut: SUT = .uncheckedInvokes { 32 | confirmation.confirm() 33 | } 34 | 35 | sut() 36 | } 37 | } 38 | 39 | @Test 40 | func invokes() async { 41 | await confirmation(expectedCount: 1) { confirmation in 42 | let sut: SUT = .invokes { 43 | confirmation.confirm() 44 | } 45 | 46 | sut() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethod_ImplementationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockVoidNonParameterizedThrowingMethod_ImplementationTests.swift 3 | // 4 | // Copyright © 2025 Fetch. 5 | // 6 | 7 | import Foundation 8 | import Testing 9 | @testable import Mocking 10 | 11 | struct MockVoidNonParameterizedThrowingMethod_ImplementationTests { 12 | 13 | // MARK: Typealiases 14 | 15 | typealias SUT = Method.Implementation 16 | typealias Method = MockVoidNonParameterizedThrowingMethod 17 | 18 | // MARK: Call As Function Tests 19 | 20 | @Test 21 | func unimplemented() async throws { 22 | try await confirmation(expectedCount: .zero) { _ in 23 | let sut: SUT = .unimplemented 24 | 25 | try sut() 26 | } 27 | } 28 | 29 | @Test 30 | func uncheckedInvokes() async throws { 31 | try await confirmation(expectedCount: 1) { confirmation in 32 | let sut: SUT = .uncheckedInvokes { 33 | confirmation.confirm() 34 | } 35 | 36 | try sut() 37 | } 38 | } 39 | 40 | @Test 41 | func invokes() async throws { 42 | try await confirmation(expectedCount: 1) { confirmation in 43 | let sut: SUT = .invokes { 44 | confirmation.confirm() 45 | } 46 | 47 | try sut() 48 | } 49 | } 50 | 51 | @Test 52 | func `throws`() throws { 53 | let sut: SUT = .throws(URLError(.badURL)) 54 | 55 | #expect(throws: URLError(.badURL)) { 56 | _ = try sut() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Tests" 3 | --------------------------------------------------------------------------------