Repository: Kolos65/Mockable Branch: main Commit: bd20509c34ca Files: 113 Total size: 458.9 KB Directory structure: gitextract_fbsz8xau/ ├── .github/ │ └── workflows/ │ ├── doc.yml │ └── pr.yml ├── .gitignore ├── .swiftlint.yml ├── LICENSE ├── Package.swift ├── README.md ├── Scripts/ │ ├── doc.sh │ ├── open.sh │ ├── test.sh │ └── utils.sh ├── Sources/ │ ├── Mockable/ │ │ ├── Builder/ │ │ │ ├── Builder.swift │ │ │ ├── FunctionBuilders/ │ │ │ │ ├── FunctionActionBuilder.swift │ │ │ │ ├── FunctionReturnBuilder.swift │ │ │ │ ├── FunctionVerifyBuilder.swift │ │ │ │ ├── ThrowingFunctionActionBuilder.swift │ │ │ │ ├── ThrowingFunctionReturnBuilder.swift │ │ │ │ └── ThrowingFunctionVerifyBuilder.swift │ │ │ ├── MockableService.swift │ │ │ └── PropertyBuilders/ │ │ │ ├── PropertyActionBuilder.swift │ │ │ ├── PropertyReturnBuilder.swift │ │ │ └── PropertyVerifyBuilder.swift │ │ ├── Documentation/ │ │ │ └── Documentation.docc/ │ │ │ ├── Configuration.md │ │ │ ├── Installation.md │ │ │ ├── Mockable.md │ │ │ └── Usage.md │ │ ├── Helpers/ │ │ │ ├── Async+Timeout.swift │ │ │ ├── AsyncSubject.swift │ │ │ └── LockedValue.swift │ │ ├── Macro/ │ │ │ └── MockableMacro.swift │ │ ├── Matcher/ │ │ │ └── Matcher.swift │ │ ├── Mocker/ │ │ │ ├── CaseIdentifiable.swift │ │ │ ├── Matchable.swift │ │ │ ├── MemberAction.swift │ │ │ ├── MemberReturn.swift │ │ │ ├── Mocked.swift │ │ │ ├── Mocker.swift │ │ │ ├── MockerFallback.swift │ │ │ ├── MockerPolicy.swift │ │ │ └── MockerScope.swift │ │ ├── Models/ │ │ │ ├── Count.swift │ │ │ ├── GenericValue.swift │ │ │ ├── Parameter+Match.swift │ │ │ ├── Parameter.swift │ │ │ ├── ReturnValue.swift │ │ │ └── TimeoutDuration.swift │ │ └── Utils/ │ │ └── Utils.swift │ └── MockableMacro/ │ ├── Extensions/ │ │ ├── AttributeListSyntax+Extensions.swift │ │ ├── AttributeSyntax+Extensions.swift │ │ ├── DeclModifierListSyntax+Extensions.swift │ │ ├── FunctionDeclSyntax+Extensions.swift │ │ ├── FunctionParameterSyntax+Extensions.swift │ │ ├── GenericArgumentSyntax+Extensions.swift │ │ ├── ProtocolDeclSyntax+Extensions.swift │ │ ├── String+Extensions.swift │ │ ├── TokenSyntax+Extensions.swift │ │ └── VariableDeclSyntax+Extensions.swift │ ├── Factory/ │ │ ├── Buildable/ │ │ │ ├── Buildable.swift │ │ │ ├── Function+Buildable.swift │ │ │ └── Variable+Buildable.swift │ │ ├── BuilderFactory.swift │ │ ├── Caseable/ │ │ │ ├── Caseable.swift │ │ │ ├── Function+Caseable.swift │ │ │ └── Variable+Caseable.swift │ │ ├── ConformanceFactory.swift │ │ ├── EnumFactory.swift │ │ ├── Factory.swift │ │ ├── MemberFactory.swift │ │ ├── MockFactory.swift │ │ └── Mockable/ │ │ ├── Function+Mockable.swift │ │ ├── Initializer+Mockable.swift │ │ ├── Mockable.swift │ │ └── Variable+Mockable.swift │ ├── MockableMacro.swift │ ├── MockableMacroError.swift │ ├── MockableMacroWarning.swift │ ├── Plugin.swift │ ├── Requirements/ │ │ ├── FunctionRequirement.swift │ │ ├── InitializerRequirement.swift │ │ ├── Requirements.swift │ │ └── VariableRequirement.swift │ └── Utils/ │ ├── Availability.swift │ ├── Messages.swift │ ├── Namespace.swift │ ├── SwiftVersionHelper.swift │ └── TokenFinder.swift └── Tests/ ├── MockableMacroTests/ │ ├── AccessModifierTests.swift │ ├── ActorConformanceTests.swift │ ├── AssociatedTypeTests.swift │ ├── AttributesTests.swift │ ├── DocCommentsTests.swift │ ├── ExoticParameterTests.swift │ ├── FunctionEffectTests.swift │ ├── GenericFunctionTests.swift │ ├── InheritedTypeMappingTests.swift │ ├── InitRequirementTests.swift │ ├── NameCollisionTests.swift │ ├── PropertyRequirementTests.swift │ ├── TypedThrowsTests_Swift6.swift │ └── Utils/ │ └── MockableMacroTestCase.swift └── MockableTests/ ├── ActionTests.swift ├── BuildTests.swift ├── ErrorShadowingCompileTests.swift ├── GivenTests.swift ├── GivenTests_Swift6.swift ├── Helpers/ │ └── Task+Sleep.swift ├── PolicyTests.swift ├── Protocols/ │ ├── Models/ │ │ ├── Product.swift │ │ ├── User.swift │ │ └── UserError.swift │ ├── TestService.swift │ └── TestService_Swift6.swift └── VerifyTests.swift ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/doc.yml ================================================ name: update-documentation on: push: branches: - main jobs: update_docs: name: Update Documentation runs-on: macos-latest env: MOCKABLE_DOC: true steps: - name: Checkout main branch uses: actions/checkout@v4 with: fetch-depth: 0 - name: Checkout documentation branch run: | git checkout -b documentation - name: Update documentation run: | Scripts/doc.sh git config user.name github-actions git config user.email github-actions@github.com git add docs git commit -m 'chore: update documentation' git push origin documentation --force ================================================ FILE: .github/workflows/pr.yml ================================================ name: pull-request-validation on: pull_request: branches: - "*" jobs: commit-lint: name: Lint Commit Messages runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Node.js and NPM uses: actions/setup-node@v4 with: node-version: "14" - name: Install commitlint run: npm install -g @commitlint/cli@11.0.0 @commitlint/config-conventional@11.0.0 - name: Run commitlint run: commitlint -x @commitlint/config-conventional --from=${{ github.event.pull_request.base.sha }} --to=${{ github.event.pull_request.head.sha }} swift-lint: name: SwiftLint runs-on: macos-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install SwiftLint run: brew install swiftlint - name: Run SwiftLint run: swiftlint lint --strict Sources Tests/MockableTests linux-tests: name: Build and Test on Linux uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: enable_windows_checks: false linux_env_vars: | MOCKABLE_LINT=false MOCKABLE_TEST=true macOS-tests: name: Build and Test on macos-latest runs-on: macos-latest env: MOCKABLE_LINT: false MOCKABLE_TEST: true steps: - name: Checkout code uses: actions/checkout@v4 - name: Swift Version run: | swift -version - name: Run Tests run: | swift build && swift test ================================================ FILE: .gitignore ================================================ .DS_Store /.build /Packages Package.resolved xcuserdata/ DerivedData/ .swiftpm .netrc .env .build ================================================ FILE: .swiftlint.yml ================================================ disabled_rules: - dynamic_inline - private_unit_test - type_body_length - valid_ibinspectable - function_default_parameter_at_end - nesting - switch_case_alignment - void_function_in_ternary - type_name opt_in_rules: - closure_spacing - convenience_type - discouraged_object_literal - empty_string - fallthrough - fatal_error_message - first_where - multiline_arguments - multiline_parameters - overridden_super_call - override_in_extension - required_enum_case - vertical_parameter_alignment_on_call - yoda_condition - array_init - explicit_init - function_default_parameter_at_end - redundant_nil_coalescing - closure_body_length - collection_alignment - conditional_returns_on_newline - contains_over_filter_count - contains_over_filter_is_empty - contains_over_first_not_nil - contains_over_range_nil_comparison - empty_collection_literal - enum_case_associated_values_count - file_name_no_space - flatmap_over_map_reduce - identical_operands - joined_default_parameter - last_where - literal_expression_end_indentation - no_extension_access_modifier - nslocalizedstring_key - operator_usage_whitespace - private_action - private_outlet - prohibited_super_call - reduce_into - redundant_type_annotation - sorted_first_last - static_operator - toggle_bool - unavailable_function - unneeded_parentheses_in_closure_argument - vertical_whitespace_closing_braces - closure_end_indentation - lower_acl_than_parent - force_unwrapping - weak_delegate analyzer_rules: - unused_declaration - unused_import closing_brace: severity: error closure_body_length: warning: 30 error: 40 closure_end_indentation: severity: error colon: severity: error flexible_right_spacing: false apply_to_dictionaries: true comma: severity: error conditional_returns_on_newline: severity: error if_only: true control_statement: severity: error cyclomatic_complexity: warning: 8 error: 9 ignores_case_statements: true empty_parameters: severity: error explicit_init: severity: error file_length: ignore_comment_only_lines: true warning: 400 error: 500 force_cast: severity: error force_try: severity: error force_unwrapping: severity: error function_parameter_count: warning: 5 error: 6 implicit_getter: severity: error large_tuple: warning: 3 error: 5 leading_whitespace: severity: error legacy_cggeometry_functions: severity: error legacy_constant: severity: error legacy_constructor: severity: error legacy_nsgeometry_functions: severity: error line_length: ignores_comments: true warning: 120 error: 120 mark: severity: error opening_brace: severity: error operator_usage_whitespace: severity: error operator_whitespace: severity: error overridden_super_call: severity: error redundant_nil_coalescing: severity: error redundant_optional_initialization: severity: error redundant_string_enum_value: severity: error redundant_void_return: severity: error return_arrow_whitespace: severity: error syntactic_sugar: severity: error todo: severity: warning trailing_comma: severity: error trailing_newline: severity: error trailing_semicolon: severity: error trailing_whitespace: severity: warning unused_closure_parameter: severity: error identifier_name: excluded: - to - id allowed_symbols: - _ vertical_parameter_alignment: severity: error vertical_whitespace: severity: error void_return: severity: error weak_delegate: severity: error lower_acl_than_parent: severity: error first_where: severity: error private_action: severity: error private_outlet: severity: error closure_spacing: severity: error array_init: severity: error statement_position: severity: error unused_enumerated: severity: error vertical_parameter_alignment_on_call: severity: error empty_string: severity: error unneeded_break_in_switch: severity: error fatal_error_message: severity: error empty_parentheses_with_trailing_closure: severity: error contains_over_first_not_nil: severity: error ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 Kolos Foltanyi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Package.swift ================================================ // swift-tools-version: 5.9 import PackageDescription import CompilerPluginSupport let test = Context.environment["MOCKABLE_TEST"].flatMap(Bool.init) ?? false let lint = Context.environment["MOCKABLE_LINT"].flatMap(Bool.init) ?? false let doc = Context.environment["MOCKABLE_DOC"].flatMap(Bool.init) ?? false func when(_ condition: Bool, _ list: [T]) -> [T] { condition ? list : [] } let devDependencies: [Package.Dependency] = when(test, [ .package(url: "https://github.com/pointfreeco/swift-macro-testing", exact: "0.6.4") ]) + when(lint, [ .package(url: "https://github.com/realm/SwiftLint", exact: "0.57.1"), ]) + when(doc, [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0") ]) let devPlugins: [Target.PluginUsage] = when(lint, [ .plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint") ]) let devTargets: [Target] = when(test, [ .testTarget( name: "MockableTests", dependencies: ["Mockable"], swiftSettings: [ .define("MOCKING"), .enableExperimentalFeature("StrictConcurrency"), .enableUpcomingFeature("ExistentialAny") ], plugins: devPlugins ), .testTarget( name: "MockableMacroTests", dependencies: [ "MockableMacro", .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), .product(name: "MacroTesting", package: "swift-macro-testing"), ], swiftSettings: [.define("MOCKING")] ) ]) let package = Package( name: "Mockable", platforms: [.macOS(.v12), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], products: [ .library( name: "Mockable", targets: ["Mockable"] ) ], dependencies: devDependencies + [ .package(url: "https://github.com/swiftlang/swift-syntax.git", "509.0.0"..<"603.0.0"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", .upToNextMajor(from: "1.6.0")) ], targets: devTargets + [ .target( name: "Mockable", dependencies: [ "MockableMacro", .product(name: "IssueReporting", package: "xctest-dynamic-overlay") ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency"), .enableUpcomingFeature("ExistentialAny") ], plugins: devPlugins ), .macro( name: "MockableMacro", dependencies: [ .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), .product(name: "SwiftCompilerPlugin", package: "swift-syntax") ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency"), .enableUpcomingFeature("ExistentialAny") ], plugins: devPlugins ) ], swiftLanguageVersions: [.v5, .version("6")] ) ================================================ FILE: README.md ================================================

@Mockable

**Mockable** is a [Swift macro](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) driven testing framework that provides automatic mock implementations for your protocols. It offers an intuitive **declarative syntax** that simplifies the process of mocking services in unit tests. The generated mock implementations can be excluded from release builds using compile conditions. ## Table of Contents - [Sponsorship](#Sponsorship) - [Documentation](#Documentation) - [Installation](#Installation) - [Configuration](#Configuration) - [Usage](#Usage) - [Example](#Example) - [Syntax](#Syntax) - [Parameters](#Parameters) - [Given](#Given) - [When](#When) - [Verify](#Verify) - [Relaxed Mode](#Relaxed-Mode) - [Non-equatable Types](#Working-with-non-equatable-Types) - [Supported Features](#Supported-Features) - [Limitations](#Limitations) - [Contribution](#Contribution) - [License](#License) ## Sponsorship I'm working on **Mockable** as a fun side project. I believe in the power of open source, and I'm thrilled to share it with you. If **Mockable** saves you time and you’d like to show your appreciation, I would be incredibly grateful for your support. Buy Me A Coffee ## Documentation Read **Mockable**'s [documentation](https://kolos65.github.io/Mockable/documentation/mockable) for detailed installation and configuration guides as well as usage examples. ## Installation The library can be installed using Swift Package Manager. Add the **Mockable** target to all targets that contain protocols you want to mock. **Mockable** does not depend on the `XCTest` framework so it can be added to any target. When using the plugin for the first time, be sure to **trust and enable** it when prompted. For unattended usage (e.g. on the CI), macro validation can be disabled with either of the following: * Using an **xcodebuild** flag: `-skipMacroValidation` * Setting Xcode defaults: `defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES` Read the [installation guide](https://kolos65.github.io/Mockable/documentation/mockable/installation/) of the documentation for more details on how to integrate **Mockable** with your project. ## Configuration Since `@Mockable` is a [peer macro](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/#attached), the generated code will always be at the same scope as the protocol it is attached to. To solve this, the macro expansion is enclosed in a pre-defined compile-time flag called **`MOCKING`** that can be leveraged to exclude generated mock implementations from release builds. > ⚠️ Since the **`MOCKING`** flag is not defined in your project by default, you won't be able to use mock implementations unless you configure it. ### When using framework modules or a non-modular project: Define the flag in your target's build settings for debug build configuration(s): 1. Open your **Xcode project**. 2. Go to your target's **Build Settings**. 3. Find **Swift Compiler - Custom Flags**. 4. Add the **MOCKING** flag under the debug configuration(s). ### When using SPM modules or testing a package: In your module's package manifest under the target definition, you can define the **`MOCKING`** compile-time condition if the build configuration is set to **`debug`**: ```swift .target( ... swiftSettings: [ .define("MOCKING", .when(configuration: .debug)) ] ) ``` ### When using [XcodeGen](https://github.com/yonaskolb/XcodeGen): Define the flag in your **XcodeGen** specification: ```yml settings: ... configs: debug: SWIFT_ACTIVE_COMPILATION_CONDITIONS: MOCKING ``` ### When using [Tuist](https://tuist.io/): If you use Tuist, you can define the `MOCKING` flag in your target's settings under `configurations`. ```swift .target( ... settings: .settings( configurations: [ .debug( name: .debug, settings: [ "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "$(inherited) MOCKING" ] ) ] ) ) ``` Read the [configuration guide](https://kolos65.github.io/Mockable/documentation/mockable/configuration/) of the documentation for more details on how to setup the **`MOCKING`** flag in your project. ## Usage ### Example Given a protocol annotated with the `@Mockable` macro: ```swift import Mockable @Mockable protocol ProductService { var url: URL? { get set } func fetch(for id: UUID) async throws -> Product func checkout(with product: Product) throws } ``` A mock implementation named `MockProductService` will be generated, that can be used in unit tests like: ```swift import Mockable lazy var productService = MockProductService() lazy var cartService = CartServiceImpl(productService: productService) func testCartService() async throws { let mockURL = URL(string: "apple.com") let mockError: ProductError = .notFound let mockProduct = Product(name: "iPhone 15 Pro") given(productService) .fetch(for: .any).willReturn(mockProduct) .checkout(with: .any).willThrow(mockError) try await cartService.checkout(with: mockProduct, using: mockURL) verify(productService) .fetch(for: .value(mockProduct.id)).called(.atLeastOnce) .checkout(with: .value(mockProduct)).called(.once) .url(newValue: .value(mockURL)).setCalled(.once) } ``` ### Syntax **Mockable** has a declarative syntax that utilizes builders to construct `given`, `when`, and `verify` clauses. When constructing any of these clauses, you always follow the same syntax: `clause type`(`service`).`function builder`.`behavior builder` In the following example where we use the previously introduced product service: ```swift let id = UUID() let error: ProductError = .notFound given(productService).fetch(for: .value(id)).willThrow(error) ``` We specify the following: * **`given`**: we want to register **return values** * **`(productService)`**: we specify what mockable service we want to register return values for * **`.fetch(for: .value(id))`**: we want to mock the `fetch(for:)` method and constrain our behavior on calls with matching `id` parameters * **`.willThrow(error)`**: if `fetch(for:)` is called with the specified parameter value, we want an **error** to be thrown ### Parameters Function builders have **all parameters** from the original requirement but **encapsulate them** within the [`Parameter`](https://kolos65.github.io/Mockable/documentation/mockable/parameter) type. When constructing mockable clauses, you have to **specify parameter conditions** for every parameter of a function. There are three available options: * **`.any`**: Matches every call to the specified function, disregarding the actual parameter values. * **`.value(Value)`**: Matches to calls with an identical value in the specified parameter. * **`.matching((Value) -> Bool)`**: Uses the provided closure to filter functions calls. > Computed properties have no parameters, but mutable properties get a `(newValue:)` parameter in function builders that can be used to constraint functionality on property assignment with a match condition. These `newValue` conditions will only effect the `performOnGet`, `performOnSet`, `getCalled` and `setCalled` clauses but will have no effect on return clauses. Here are examples of using different parameter conditions: ```swift // throw an error when `fetch(for:)` is called with `id` given(productService).fetch(for: .value(id)).willThrow(error) // print "Ouch!" if product service is called with a product named "iPhone 15 Pro" when(productService) .checkout(with: .matching { $0.name == "iPhone 15 Pro" }) .perform { print("Ouch!") } // assert if the fetch(for:) was called exactly once regardless of what id parameter it was called with verify(productService).fetch(for: .any).called(.once) ``` ### Given Return values can be specified using a `given(_ service:)` clause. There are three return builders available: * [`willReturn(_ value:)`](https://kolos65.github.io/Mockable/documentation/mockable/functionreturnbuilder/willreturn(_:)): Will store the given return value and use it to mock subsequent calls. * [`willThrow(_ error:)`](https://kolos65.github.io/Mockable/documentation/mockable/throwingfunctionreturnbuilder/willthrow(_:)): Will store the given error and throw it in subsequent calls. Only available for throwing functions and properties. * [`willProduce(_ producer:)`](https://kolos65.github.io/Mockable/documentation/mockable/throwingfunctionreturnbuilder/willproduce(_:)): Will use the provided closure for mocking. The closure has the same signature as the mocked function, so for example a function that takes an integer returns a string and can throw will accept a closure of type `(Int) throws -> String`. * [`willHandle(_ result:)`](https://kolos65.github.io/Mockable/documentation/mockable/throwingfunctionreturnbuilder/willhandle(_:)): Registers a result type that automatically emits a value when successful or throws a failure. The provided return values are used up in FIFO order and the last one is always kept for any further calls. Here are examples of using return clauses: ```swift // Throw an error for the first call and then return 'product' for every other call given(productService) .fetch(for: .any).willThrow(error) .fetch(for: .any).willReturn(product) // Throw an error if the id parameter ends with a 0, return a product otherwise given(productService) .fetch(for: .any).willProduce { id in if id.uuidString.last == "0" { throw error } else { return product } } ``` ### When Side effects can be added using `when(_ service:)` clauses. There are three kind of side effects: * [`perform(_ action)`](https://kolos65.github.io/Mockable/documentation/mockable/functionactionbuilder/perform(_:)): Will register an operation to perform on invocations of the mocked function. * [`performOnGet(_ action:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyactionbuilder/performonget(_:)): Available for mutable properties only, will perform the provided operation on property access. * [`performOnSet(_ action:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyactionbuilder/performonset(_:)): Available for mutable properties only, will perform the provided operation on property assignment. Some examples of using side effects are: ```swift // log calls to fetch(for:) when(productService).fetch(for: .any).perform { print("fetch(for:) was called") } // log when url is accessed when(productService).url().performOnGet { print("url accessed") } // log when url is set to nil when(productService).url(newValue: .value(nil)).performOnSet { print("url set to nil") } ``` ### Verify You can verify invocations of your mock service using the `verify(_ service:)` clause. > **Mockable** supports both *XCTest* and *Swift Testing* by using Pointfree's [swift-issue-reporting](https://github.com/pointfreeco/swift-issue-reporting) to dynamically report test failures with the appropriate test framework. There are three kind of verifications: * [`called(_:)`](https://kolos65.github.io/Mockable/documentation/mockable/functionverifybuilder/called(_:fileid:filepath:line:column:)): Asserts invocation count based on the given value. * [`getCalled(_:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyverifybuilder/getcalled(_:fileid:filepath:line:column:)): Available for mutable properties only, asserts property access count. * [`setCalled(_:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyverifybuilder/setcalled(_:fileid:filepath:line:column:)): Available for mutable properties only, asserts property assignment count. Here are some example assertions: ```swift verify(productService) // assert fetch(for:) was called between 1 and 5 times .fetch(for: .any).called(.from(1, to: 5)) // assert checkout(with:) was called between exactly 10 times .checkout(with: .any).called(10) // assert url property was accessed at least 2 times .url().getCalled(.moreOrEqual(to: 2)) // assert url property was never set to nil .url(newValue: .value(nil)).setCalled(.never) ``` If you are testing asynchronous code and cannot write sync assertions you can use the async counterparts of the above verifications: * [`calledEventually(_:before:)`](https://kolos65.github.io/Mockable/documentation/mockable/functionverifybuilder/calledeventually(_:before:fileid:filepath:line:column:)): Wait until timeout or invocation count satisfied. * [`getCalledEventually(_:before:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyverifybuilder/getcalledeventually(_:before:fileid:filepath:line:column:)): Wait until timeout or property access count satisfied. * [`setSalledEventually(_:before:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyverifybuilder/setcalledeventually(_:before:fileid:filepath:line:column:)): Wait until timeout or property assignment count satisfied. Here are some examples of async verifications: ```swift await verify(productService) // assert fetch(for:) was called between 1 and 5 times before default timeout (1 second) .fetch(for: .any).calledEventually(.from(1, to: 5)) // assert checkout(with:) was called between exactly 10 times before 3 seconds .checkout(with: .any).calledEventually(10, before: .seconds(3)) // assert url property was accessed at least 2 times before default timeout (1 second) .url().getCalledEventually(.moreOrEqual(to: 2)) // assert url property was set to nil once .url(newValue: .value(nil)).setCalledEventually(.once) ``` ### Relaxed Mode By default, you must specify a return value for all requirements; otherwise, a fatal error will be thrown. The reason for this is to aid in the discovery (and thus the verification) of every called function when writing unit tests. However, it is common to prefer avoiding this strict default behavior in favor of a more relaxed setting, where, for example, void or optional return values do not need explicit `given` registration. Use [`MockerPolicy`](https://kolos65.github.io/Mockable/documentation/mockable/mockerpolicy) (which is an [option set](https://developer.apple.com/documentation/swift/optionset)) to implicitly mock: * only one kind of return value: `.relaxedMocked` * construct a custom set of policies: `[.relaxedVoid, .relaxedOptional]` * or opt for a fully relaxed mode: `.relaxed`. You have two options to override the default strict behavior of the library: * at **mock implementation level** you can override the mocker policy for each individual mock implementation in the initializer: ```swift let relaxedMock = MockService(policy: [.relaxedOptional, .relaxedVoid]) ``` * at **project level** you can set a custom default policy to use in every scenario by changing the default property of **MockerPolicy**: ```swift MockerPolicy.default = .relaxedVoid ``` The `.relaxedMocked` policy in combination with the [`Mocked`](https://kolos65.github.io/Mockable/documentation/mockable/mocked) protocol can be used to set an implicit return value for custom (or even built in) types: ```swift struct Car { var name: String var seats: Int } extension Car: Mocked { static var mock: Car { Car(name: "Mock Car", seats: 4) } // Defaults to [mock] but we can // provide a custom array of cars: static var mocks: [Car] { [ Car(name: "Mock Car 1", seats: 4), Car(name: "Mock Car 2", seats: 4) ] } } @Mockable protocol CarService { func getCar() -> Car func getCars() -> [Car] } func testCarService() { func test() { let mock = MockCarService(policy: .relaxedMocked) // Implictly mocked without a given registration: let car = mock.getCar() let cars = mock.getCars() } } ``` > ⚠️ Relaxed mode will not work with generic return values as the type system is unable to locate the appropriate generic overload. ### Working with non-equatable Types **Mockable** uses a [`Matcher`](https://kolos65.github.io/Mockable/documentation/mockable/matcher) internally to compare parameters. By default the matcher is able to compare any custom type that conforms to `Equatable` (except when used in a generic function). In special cases, when you * have non-equatable parameter types * need testing specific equality logic for a custom type * have generic functions that are used with custom concrete types you can register your custom types with the `Matcher.register()` functions. Here is how to do it: ```swift // register an equatable type to the matcher because we use it in a generic function Matcher.register(SomeEquatableType.self) // register a non-equatable type to the matcher Matcher.register(Product.self, match: { $0.name == $1.name }) // register a meta-type to the matcher Matcher.register(HomeViewController.Type.self) // remove all previously registered custom types Matcher.reset() ``` If you see an error during tests like: > No comparator found for type "SomeType". All non-equatable types must be registered using Matcher.register(_). Remember to add the noted type to your `Matcher` using the `register()` function. ## Supported Features - [x] **Zero-boilerplate** mock generation
- [x] **Exclude** mock implementations **from production** target
- [x] Protocols with **associated types**
- [x] Protocols with **constrained associated types**
- [x] **Init** requirements
- [x] **Generic function parameters** and **return values**
- [x] Generic functions with **where clauses**
- [x] Computed and mutable **property requirements**
- [x] **@escaping closure** parameters
- [x] **Implicitly unwrapped** optionals
- [x] **throwing, rethrowing and async** requirements
- [x] Custom **non-equatable** types ## Limitations - [ ] **Static Requirements**: Static members cannot be used on protocols and are not supported.
- [ ] **Protocol Inheritance**: Due to limitations of the macro system, inherited protocol requirements won't be implemented.
- [ ] **Return Type Overloads**: Requirements with overloaded return types are not supported in favour of supporting generic return types. - [ ] **Rethrows functions**: Rethrowing function requirements are always implemented with non-throwing functions.
- [ ] **Non-escaping function parameters**: Non-escaping closure parameters cannot be stored and are not supported.
- [ ] **Subscripts** are not supported (yet).
- [ ] **Operators** are not supported (yet).
## Contribution If you encounter any issues with the project or have suggestions for improvement, please feel free to open an issue. I value your feedback and am committed to making this project as robust and user-friendly as possible. The package manifest is set up to only contain test targets and test dependencies if an environment variable named `MOCKABLE_DEV` is set to true. This is done to prevent the overly zealous Swift Package Manager from downloading test dependencies and plugins, such as `swift-macro-testing` or `SwiftLint`, when someone uses **Mockable** as a package dependency. To open the package with Xcode in "development mode", you need the `MOCKABLE_DEV=true` environment variable to be set. Use `Scripts/open.sh` to open the project (or copy its contents into your terminal) to be able to run tests and lint your code when contributing. ## License **Mockable** is made available under the MIT License. Please see the [LICENSE](https://raw.githubusercontent.com/Kolos65/Mockable/main/LICENSE) file for more details. ================================================ FILE: Scripts/doc.sh ================================================ #!/bin/bash swift package \ --allow-writing-to-directory ./docs \ generate-documentation \ --target Mockable \ --output-path ./docs \ --transform-for-static-hosting \ --hosting-base-path Mockable ================================================ FILE: Scripts/open.sh ================================================ #!/bin/bash export MOCKABLE_LINT=true export MOCKABLE_TEST=true open Package.swift ================================================ FILE: Scripts/test.sh ================================================ #!/bin/bash set -eo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(dirname "$SCRIPT_DIR")" source $ROOT_DIR/Scripts/utils.sh # When --vm is passed, the build and test commands are executed in a virtualized container. if [[ " $* " == *" --vm "* ]]; then CONTAINER_RUNTIME=$(get_container_runtime) $CONTAINER_RUNTIME run --rm \ --volume "$ROOT_DIR:/package" \ --workdir "/package" \ -e MOCKABLE_TEST=$MOCKABLE_TEST \ swiftlang/swift:nightly-6.1-focal \ /bin/bash -c \ "swift build --build-path ./.build/linux" else swift build --package-path "$ROOT_DIR" swift test --package-path "$ROOT_DIR" fi ================================================ FILE: Scripts/utils.sh ================================================ #!/bin/bash # Function to determine the container runtime get_container_runtime() { if command -v podman &> /dev/null; then echo "podman" elif command -v docker &> /dev/null; then echo "docker" else echo "Neither podman nor docker is installed. Please install one of them to proceed." >&2 exit 1 fi } ================================================ FILE: Sources/Mockable/Builder/Builder.swift ================================================ // // Builder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 21.. // /// Used to specify members of a protocol when building /// given or when clauses of a mock service. public protocol Builder { /// The mock service associated with the Builder. associatedtype Service: MockableService /// Initializes a new instance of the builder with the provided `Mocker`. /// /// - Parameter mocker: The associated service's `Mocker` to provide for subsequent builders. init(mocker: Mocker) } ================================================ FILE: Sources/Mockable/Builder/FunctionBuilders/FunctionActionBuilder.swift ================================================ // // FunctionActionBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A builder for specifying actions to be performed when mocking a function. /// /// This builder is used within the context of a higher-level builder (e.g., an `ActionBuilder`) /// to specify a desired action to perform when a particular function of a mock service is called. public struct FunctionActionBuilder> { /// Convenient type for the associated service's Member. public typealias Member = T.Member /// The member being mocked. private var member: Member /// The associated service's mocker. private var mocker: Mocker /// Initializes a new instance of `FunctionActionBuilder`. /// /// - Parameters: /// - mocker: The `Mocker` instance of the associated mock service. /// - kind: The member being mocked. public init(_ mocker: Mocker, kind member: Member) { self.member = member self.mocker = mocker } /// Registers an action to be performed when the provided member is called. /// /// - Parameter action: The closure representing the action to be performed. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func perform(_ action: @escaping () -> Void) -> ParentBuilder { mocker.addAction(action, for: member) return .init(mocker: mocker) } } ================================================ FILE: Sources/Mockable/Builder/FunctionBuilders/FunctionReturnBuilder.swift ================================================ // // FunctionReturnBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 14.. // /// A builder for specifying return values or producers when mocking a function. /// /// This builder is typically used within the context of a higher-level builder (e.g., a `ReturnBuilder`) /// to specify the desired return value or a return value producer for a particular function of a mock service. public struct FunctionReturnBuilder, ReturnType, ProduceType> { /// Convenient type for the associated service's Member. public typealias Member = T.Member /// The member being mocked. private var member: Member /// The associated service's mocker. private var mocker: Mocker /// Initializes a new instance of `FunctionReturnBuilder`. /// /// - Parameters: /// - mocker: The `Mocker` instance of the associated mock service. /// - kind: The member being mocked. public init(_ mocker: Mocker, kind member: Member) { self.member = member self.mocker = mocker } /// Registers a return value to provide when the mocked member is called. /// /// - Parameter value: The return value to use for mocking the specified member. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func willReturn(_ value: ReturnType) -> ParentBuilder { mocker.addReturnValue(.return(value), for: member) return .init(mocker: mocker) } /// Registers a return value producing closure to use when the mocked member is called. /// /// - Parameter producer: A closure that produces a return value for the mocked member. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func willProduce(_ producer: ProduceType) -> ParentBuilder { mocker.addReturnValue(.produce(producer), for: member) return .init(mocker: mocker) } } extension FunctionReturnBuilder where ReturnType == Void { /// Specifies that the void function will return normally when the mocked member is called. @discardableResult public func willReturn() -> ParentBuilder { mocker.addReturnValue(.return(()), for: member) return .init(mocker: mocker) } } ================================================ FILE: Sources/Mockable/Builder/FunctionBuilders/FunctionVerifyBuilder.swift ================================================ // // FunctionVerifyBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A builder for verifying the number of times a mocked member was called. /// /// This builder is typically used within the context of a higher-level builder (e.g., a `VerifyBuilder`) /// to verify the expected number of invocations for a particular function of a mock service. public struct FunctionVerifyBuilder> { /// Convenient type for the associated service's Member. public typealias Member = T.Member /// The associated service's mocker. private var mocker: Mocker /// The member being verified. private var member: Member /// Initializes a new instance of `FunctionVerifyBuilder`. /// /// - Parameters: /// - mocker: The `Mocker` instance of the associated mock service. /// - kind: The member being verified. public init(_ mocker: Mocker, kind member: Member) { self.member = member self.mocker = mocker } /// Asserts the number of invocations of the specified member using `count`. /// /// - Parameter count: Specifies the expected invocation count. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func called( _ count: Count, fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column) -> ParentBuilder { mocker.verify( member: member, count: count, fileID: fileID, filePath: filePath, line: line, column: column ) return .init(mocker: mocker) } /// Asynchronously waits at most `timeout` interval for a successfuly assertion of /// `count` invocations, then fails. /// /// - Parameters: /// - count: Specifies the expected invocation count. /// - timeout: The maximum time it will wait for assertion to be true. Default 1 second. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func calledEventually(_ count: Count, before timeout: TimeoutDuration = .seconds(1), fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column) async -> ParentBuilder { await mocker.verify( member: member, count: count, timeout: timeout, fileID: fileID, filePath: filePath, line: line, column: column ) return .init(mocker: mocker) } } ================================================ FILE: Sources/Mockable/Builder/FunctionBuilders/ThrowingFunctionActionBuilder.swift ================================================ // // ThrowingFunctionActionBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A builder for specifying actions to be performed when mocking a throwing function. /// /// This builder is used within the context of a higher-level builder (e.g., an `ActionBuilder`) /// to specify a desired action to perform when a particular throwing function of a mock service is called. public typealias ThrowingFunctionActionBuilder> = FunctionActionBuilder ================================================ FILE: Sources/Mockable/Builder/FunctionBuilders/ThrowingFunctionReturnBuilder.swift ================================================ // // ThrowingFunctionReturnBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A builder for specifying return values or producers when mocking a function. /// /// This builder is typically used within the context of a higher-level builder (e.g., a `ReturnBuilder`) /// to specify the desired return value or a return value producer for a throwing function of a mock service. public struct ThrowingFunctionReturnBuilder< T: MockableService, ParentBuilder: Builder, ReturnType, ErrorType: Error, ProduceType > { /// Convenient type for the associated service's Member. public typealias Member = T.Member /// The member being mocked. private var member: Member /// The associated service's mocker. private var mocker: Mocker /// Initializes a new instance of `FunctionReturnBuilder`. /// /// - Parameters: /// - mocker: The `Mocker` instance of the associated mock service. /// - kind: The member being mocked. public init(_ mocker: Mocker, kind member: Member) { self.member = member self.mocker = mocker } /// Registers a return value to provide when the mocked member is called. /// /// - Parameter value: The return value to use for mocking the specified member. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func willReturn(_ value: ReturnType) -> ParentBuilder { mocker.addReturnValue(.return(value), for: member) return .init(mocker: mocker) } /// Registers an error to be thrown when the mocked member is called. /// /// - Parameter error: The error to be thrown for mocking the specified member. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func willThrow(_ error: ErrorType) -> ParentBuilder { mocker.addReturnValue(.throw(error), for: member) return .init(mocker: mocker) } /// Registers a return value producing closure to use when the mocked member is called. /// /// - Parameter producer: A closure that produces a return value or throws an error. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func willProduce(_ producer: ProduceType) -> ParentBuilder { mocker.addReturnValue(.produce(producer), for: member) return .init(mocker: mocker) } /// Registers a result type that automatically emits a value when successful or throws a failure. /// /// - Parameter result: The result type wrapping a value or error. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func willHandle(_ result: Result) -> ParentBuilder { switch result { case let .success(value): mocker.addReturnValue(.return(value), for: member) case let .failure(error): mocker.addReturnValue(.throw(error), for: member) } return .init(mocker: mocker) } } extension ThrowingFunctionReturnBuilder where ReturnType == Void { /// Specifies that the void function will return normally when the mocked member is called. @discardableResult public func willReturn() -> ParentBuilder { mocker.addReturnValue(.return(()), for: member) return .init(mocker: mocker) } } ================================================ FILE: Sources/Mockable/Builder/FunctionBuilders/ThrowingFunctionVerifyBuilder.swift ================================================ // // ThrowingFunctionVerifyBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A builder for verifying the number of times a throwing function was called. /// /// This builder is typically used within the context of a higher-level builder (e.g., a `VerifyBuilder`) /// to verify the expected number of invocations for a throwing function of a mock service. public typealias ThrowingFunctionVerifyBuilder> = FunctionVerifyBuilder ================================================ FILE: Sources/Mockable/Builder/MockableService.swift ================================================ // // MockableService.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 13.. // /// A protocol defining the structure for a mocked service. /// /// Conforming types must provide a `Member` type representing their members /// as well as builders for specifying return values, actions, and verifications. public protocol MockableService { /// A type representing the members of the mocked protocol. associatedtype Member: Matchable, CaseIdentifiable, Sendable /// A builder responsible for registering return values. associatedtype ReturnBuilder: Builder /// A builder responsible for registering side-effects. associatedtype ActionBuilder: Builder /// A builder responsible for asserting member invocations. associatedtype VerifyBuilder: Builder /// Encapsulates member return values. typealias Return = MemberReturn /// Encapsulates member side-effects. typealias Action = MemberAction /// A builder proxy for specifying return values. var _given: ReturnBuilder { get } /// A builder proxy for specifying actions. var _when: ActionBuilder { get } /// The builder proxy for verifying invocations. var _verify: VerifyBuilder { get } } ================================================ FILE: Sources/Mockable/Builder/PropertyBuilders/PropertyActionBuilder.swift ================================================ // // PropertyActionBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A builder for specifying actions to be performed when mocking the getter and setter of a property. /// /// This builder is typically used within the context of a higher-level builder (e.g., an `ActionBuilder`) /// to specify the behavior of the getter and setter of a particular property of a mock. public struct PropertyActionBuilder> { /// Convenient type for the associated service's Member. public typealias Member = T.Member /// The member representing the getter of the property. var getMember: Member /// The member representing the setter of the property. var setMember: Member /// The associated service's mocker. var mocker: Mocker /// Initializes a new instance of `PropertyActionBuilder`. /// /// - Parameters: /// - mocker: The `Mocker` instance of the associated mock service. /// - getKind: The member representing the getter of the property. /// - setKind: The member representing the setter of the property. public init(_ mocker: Mocker, kind getMember: Member, setKind setMember: Member) { self.getMember = getMember self.setMember = setMember self.mocker = mocker } /// Specifies the action to be performed when the getter of the property is called. /// /// - Parameter action: The closure representing the action to be performed. /// - Returns: The parent builder, typically used for chaining additional specifications. @discardableResult public func performOnGet(_ action: @escaping () -> Void) -> ParentBuilder { mocker.addAction(action, for: getMember) return .init(mocker: mocker) } /// Specifies the action to be performed when the setter of the property is called. /// /// - Parameter action: The closure representing the action to be performed. /// - Returns: The parent builder, typically used for chaining additional specifications. @discardableResult public func performOnSet(_ action: @escaping () -> Void) -> ParentBuilder { mocker.addAction(action, for: setMember) return .init(mocker: mocker) } } ================================================ FILE: Sources/Mockable/Builder/PropertyBuilders/PropertyReturnBuilder.swift ================================================ // // PropertyReturnBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A builder for specifying return values or producers when mocking the getter of a mutable property. /// /// This builder is typically used within the context of a higher-level builder (e.g., a `ReturnBuilder`) /// to specify the desired return value or a return value producer for the getter /// of a particular property of a mock. public struct PropertyReturnBuilder, ReturnType> { /// Convenient type for the associated service's Member. public typealias Member = T.Member /// The member representing the getter of the property. var getMember: Member /// The associated service's mocker. var mocker: Mocker /// Initializes a new instance of `PropertyReturnBuilder`. /// /// - Parameters: /// - mocker: The `Mocker` instance of the associated mock service. /// - getKind: The member representing the getter of the property. public init(_ mocker: Mocker, kind getMember: Member) { self.getMember = getMember self.mocker = mocker } /// Specifies the return value when the getter of the property is called. /// /// - Parameter value: The return value to use for mocking the specified getter. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func willReturn(_ value: ReturnType) -> ParentBuilder { mocker.addReturnValue(.return(value), for: getMember) return .init(mocker: mocker) } /// Specifies the return value producing closure to use when the getter of the property is called. /// /// - Parameter producer: A closure that produces a return value for the getter. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func willProduce(_ producer: @escaping () -> ReturnType) -> ParentBuilder { mocker.addReturnValue(.produce(producer), for: getMember) return .init(mocker: mocker) } } ================================================ FILE: Sources/Mockable/Builder/PropertyBuilders/PropertyVerifyBuilder.swift ================================================ // // PropertyVerifyBuilder.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A builder for verifying the number of times the getter and setter of a property are called. /// /// This builder is typically used within the context of a higher-level builder (e.g., a `VerifyBuilder`) /// to verify the expected number of invocations for the getter and setter of a particular property of a mock. public struct PropertyVerifyBuilder> { /// Convenient type for the associated service's Member. public typealias Member = T.Member /// The associated service's mocker. var mocker: Mocker /// The member representing the getter of the property. var getMember: Member /// The member representing the setter of the property. var setMember: Member /// Initializes a new instance of `PropertyVerifyBuilder`. /// /// - Parameters: /// - mocker: The `Mocker` instance of the associated mock service. /// - getKind: The member representing the getter of the property. /// - setKind: The member representing the setter of the property. public init(_ mocker: Mocker, kind getMember: Member, setKind setMember: Member) { self.getMember = getMember self.setMember = setMember self.mocker = mocker } /// Specifies the expected number of times the getter of the property should be called. /// /// - Parameter count: The `Count` object specifying the expected invocation count. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func getCalled( _ count: Count, fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column ) -> ParentBuilder { mocker.verify( member: getMember, count: count, fileID: fileID, filePath: filePath, line: line, column: column ) return .init(mocker: mocker) } /// Asynchronously waits at most `timeout` interval for a successfuly assertion of /// `count` invocations of the property's getter, then fails. /// /// - Parameters: /// - count: Specifies the expected invocation count. /// - timeout: The maximum time it will wait for assertion to be true. Default 1 second. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func getCalledEventually(_ count: Count, before timeout: TimeoutDuration = .seconds(1), fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column) async -> ParentBuilder { await mocker.verify( member: getMember, count: count, timeout: timeout, fileID: fileID, filePath: filePath, line: line, column: column ) return .init(mocker: mocker) } /// Specifies the expected number of times the setter of the property should be called. /// /// - Parameter count: The `Count` object specifying the expected invocation count. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func setCalled( _ count: Count, fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column) -> ParentBuilder { mocker.verify( member: setMember, count: count, fileID: fileID, filePath: filePath, line: line, column: column ) return .init(mocker: mocker) } /// Asynchronously waits at most `timeout` interval for a successfuly assertion of /// `count` invocations fot the property's setter, then fails. /// /// - Parameters: /// - count: Specifies the expected invocation count. /// - timeout: The maximum time it will wait for assertion to be true. Default 1 second. /// - Returns: The parent builder, used for chaining additional specifications. @discardableResult public func setCalledEventually(_ count: Count, before timeout: TimeoutDuration = .seconds(1), fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column) async -> ParentBuilder { await mocker.verify( member: setMember, count: count, timeout: timeout, fileID: fileID, filePath: filePath, line: line, column: column ) return .init(mocker: mocker) } } ================================================ FILE: Sources/Mockable/Documentation/Documentation.docc/Configuration.md ================================================ # Configuration Learn how to configure build settings so generated mock implementations are excluded from release builds. ## Overview Since `@Mockable` is a peer macro the generated code will always be at the same scope as the protocol it is attached to. From **Mockable**'s perspective this is unfortunate as we don't want to include our generated mock implementations in the release bundle. To solve this, the macro expansion is enclosed in a pre-defined compile-time flag called **`MOCKING`** that can be leveraged to exclude generated mock implementations from release builds: ```swift #if MOCKING public final class MockService: Service, Mockable { // generated code... } #endif ``` > Since the **`MOCKING`** flag is not defined in your project by default, you won't be able to use mock implementations unless you configure it. There are many ways to define the flag depending on how your project is set up or what tool you use for build setting generation. Below you can find how to define the `MOCKING` flag in three common scenarios. ## Define the flag using Xcode If your porject relies on Xcode build settings, define the flag in your target's build settings for debug build configuration(s): 1. Open your **Xcode project**. 2. Go to your target's **Build Settings**. 3. Find **Swift Compiler - Custom Flags**. 4. Add the **MOCKING** flag under the debug build configuration(s). 5. Repeat these steps for all of your targets where you want to use the `@Mockable` macro. ## Using a Package.swift manifest If you are using SPM modules or working with a package, define the **`MOCKING`** compile-time condition in your package manifest. Using a `.when(configuration:)` build setting condition you can define the flag only if the build configuration is set to `debug`. ```swift .target( ... swiftSettings: [ .define("MOCKING", .when(configuration: .debug)) ] ) ``` ## Using XcodeGen If you use XcodeGen to generate you project, you can define the `MOCKING` flag in your yaml file under the `configs` definition. ```yml settings: ... configs: debug: SWIFT_ACTIVE_COMPILATION_CONDITIONS: MOCKING ``` ## Using [Tuist](https://tuist.io/): If you use Tuist, you can define the `MOCKING` flag in your target's settings under `configurations`. ```swift .target( ... settings: .settings( configurations: [ .debug( name: .debug, settings: [ "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "$(inherited) MOCKING" ] ) ] ) ) ``` ## Summary By defining the `MOCKING` condition in the debug build configuration you ensured that generated mock implementations are excluded from release builds and kept available for your unit tests that use the debug configuration to build tested targets. ================================================ FILE: Sources/Mockable/Documentation/Documentation.docc/Installation.md ================================================ # Installation Learn how to install **Mockable** and integrate into your targets. ## Overview **Mockable** can be installed using Swift Package Manager. Add the **Mockable** target to all targets that contain protocols you want to mock. **Mockable** does not depend on the `XCTest` framework so it can be added to any target. ## Add **Mockable** using Xcode 1. Open your Xcode project. 2. Navigate to the **File** menu and select **Add Package Dependencies...** 3. Enter the following URL: **`https://github.com/Kolos65/Mockable`** 4. Specify the version you want to use and click **Add Package**. 6. Click **Add Package** > If you have multiple targets or multiple test targets: > Navigate to each target's **General** settings and add Mockable under the **Frameworks and Libraries** settings. ### Using a Package.swift manifest: If you have SPM modules or you want to test an SPM package, add **Mockable** as a package dependency in the manifest file. In you target definitions add the **Mockable** product to your main and test targets. ```swift let package = Package( ... dependencies: [ .package(url: "https://github.com/Kolos65/Mockable", from: "0.0.1"), ], targets: [ .target( ... dependencies: [ .product(name: "Mockable", package: "Mockable") ] ), .testTarget( ... dependencies: [ .product(name: "Mockable", package: "Mockable") ] ) ] ) ``` ### Using XcodeGen: Add Mockable to the `packages` definition and the `targets` definition. ```yaml packages: Mockable: url: https://github.com/Kolos65/Mockable from: "0.0.1" targets: MyApp: ... dependencies: - package: Mockable product: Mockable MyAppUnitTests: ... dependencies: - package: Mockable product: Mockable ``` ================================================ FILE: Sources/Mockable/Documentation/Documentation.docc/Mockable.md ================================================ # ``Mockable`` A macro driven testing framework that provides automatic mock implementations for your protocols. It offers an intuitive declarative syntax that simplifies the process of mocking services in unit tests. ## Overview **Mockable** utilizes the new Swift macro system to generate code that eliminates the need for external dependencies like Sourcery. It has a declarative API that enables you to rapidly specify return values and verify invocations in a readable format. Associated types, generic functions, where clauses and constrained generic arguments are all supported. The generated mock implementations can be excluded from release builds using a built in compile condition. ## Topics ### Guides - - - ================================================ FILE: Sources/Mockable/Documentation/Documentation.docc/Usage.md ================================================ # Usage Larn how to use **Mockable** to write readable and concise unit tests. ## Overview Given a protocol annotated with the `@Mockable` macro: ```swift import Mockable @Mockable protocol ProductService { var url: URL? { get set } func fetch(for id: UUID) async throws -> Product func checkout(with product: Product) throws } ``` A mock implementation named `MockProductService` will be generated, that can be used in unit tests like this: ```swift import Mockable lazy var productService = MockProductService() lazy var cartService = CartServiceImpl(productService: productService) func testCartService() async throws { let mockURL = URL(string: "apple.com") let mockError: ProductError = .notFound let mockProduct = Product(name: "iPhone 15 Pro") given(productService) .fetch(for: .any).willReturn(mockProduct) .checkout(with: .any).willThrow(mockError) try await cartService.checkout(with: mockProduct, using: mockURL) verify(productService) .fetch(for: .value(mockProduct.id)).called(.atLeastOnce) .checkout(with: .value(mockProduct)).called(.once) .url(newValue: .value(mockURL)).setCalled(.once) } ``` ## Syntax **Mockable** has a declarative syntax that utilizes builders to construct `given`, `when`, and `verify` clauses. When constructing any of these clauses, you always follow the same syntax: `clause type`(`service`).`function builder`.`behavior builder` In the following example where we use the previously introduced product service: ```swift let id = UUID() let error: ProductError = .notFound given(productService).fetch(for: .value(id)).willThrow(error) ``` We specify the following: * **`given`**: we want to register **return values** * **`(productService)`**: we specify what mockable service we want to register return values for * **`.fetch(for: .value(id))`**: we want to mock the `fetch(for:)` method and constrain our behavior on calls with matching `id` parameters * **`.willThrow(error)`**: if `fetch(for:)` is called with the specified parameter value, we want an **error** to be thrown ## Parameters Function builders have **all parameters** from the original requirement but **encapsulate them** within the [`Parameter`](https://kolos65.github.io/Mockable/documentation/mockable/parameter) type. When constructing mockable clauses, you have to **specify parameter conditions** for every parameter of a function. There are three available options: * **`.any`**: Matches every call to the specified function, disregarding the actual parameter values. * **`.value(Value)`**: Matches to calls with an identical value in the specified parameter. * **`.matching((Value) -> Bool)`**: Uses the provided closure to filter functions calls. > Computed properties have no parameters, but mutable properties get a `(newValue:)` parameter in function builders that can be used to constraint functionality on property assignment with a match condition. These `newValue` conditions will only effect the `performOnGet`, `performOnSet`, `getCalled` and `setCalled` clauses but will have no effect on return clauses. Here are examples of using different parameter conditions: ```swift // throw an error when `fetch(for:)` is called with `id` given(productService).fetch(for: .value(id)).willThrow(error) // print "Ouch!" if product service is called with a product named "iPhone 15 Pro" when(productService) .checkout(with: .matching { $0.name == "iPhone 15 Pro" }) .perform { print("Ouch!") } // assert if the fetch(for:) was called exactly once regardless of what id parameter it was called with verify(productService).fetch(for: .any).called(.once) ``` ## Given Return values can be specified using a `given(_ service:)` clause. There are three return builders available: * [`willReturn(_ value:)`](https://kolos65.github.io/Mockable/documentation/mockable/functionreturnbuilder/willreturn(_:)): Will store the given return value and use it to mock subsequent calls. * [`willThrow(_ error:)`](https://kolos65.github.io/Mockable/documentation/mockable/throwingfunctionreturnbuilder/willthrow(_:)): Will store the given error and throw it in subsequent calls. Only available for throwing functions and properties. * [`willProduce(_ producer:)`](https://kolos65.github.io/Mockable/documentation/mockable/throwingfunctionreturnbuilder/willproduce(_:)): Will use the provided closure for mocking. The closure has the same signature as the mocked function, so for example a function that takes an integer returns a string and can throw will accept a closure of type `(Int) throws -> String`. * [`willHandle(_ result:)`](https://kolos65.github.io/Mockable/documentation/mockable/throwingfunctionreturnbuilder/willhandle(_:)): Registers a result type that automatically emits a value when successful or throws a failure. The provided return values are used up in FIFO order and the last one is always kept for any further calls. Here are examples of using return clauses: ```swift // Throw an error for the first call and then return 'product' for every other call given(productService) .fetch(for: .any).willThrow(error) .fetch(for: .any).willReturn(product) // Throw an error if the id parameter ends with a 0, return a product otherwise given(productService) .fetch(for: .any).willProduce { id in if id.uuidString.last == "0" { throw error } else { return product } } ``` ## When Side effects can be added using `when(_ service:)` clauses. There are three kind of side effects: * [`perform(_ action)`](https://kolos65.github.io/Mockable/documentation/mockable/functionactionbuilder/perform(_:)): Will register an operation to perform on invocations of the mocked function. * [`performOnGet(_ action:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyactionbuilder/performonget(_:)): Available for mutable properties only, will perform the provided operation on property access. * [`performOnSet(_ action:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyactionbuilder/performonset(_:)): Available for mutable properties only, will perform the provided operation on property assignment. Some examples of using side effects are: ```swift // log calls to fetch(for:) when(productService).fetch(for: .any).perform { print("fetch(for:) was called") } // log when url is accessed when(productService).url().performOnGet { print("url accessed") } // log when url is set to nil when(productService).url(newValue: .value(nil)).performOnSet { print("url set to nil") } ``` ## Verify You can verify invocations of your mock service using the `verify(_ service:)` clause. > **Mockable** supports both *XCTest* and *Swift Testing* by using Pointfree's [swift-issue-reporting](https://github.com/pointfreeco/swift-issue-reporting) to dynamically report test failures with the appropriate test framework. There are three kind of verifications: * [`called(_:)`](https://kolos65.github.io/Mockable/documentation/mockable/functionverifybuilder/called(_:file:line:)): Asserts invocation count based on the given value. * [`getCalled(_:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyverifybuilder/getcalled(_:file:line:)): Available for mutable properties only, asserts property access count. * [`setCalled(_:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyverifybuilder/setcalled(_:file:line:)): Available for mutable properties only, asserts property assignment count. Here are some example assertions: ```swift verify(productService) // assert fetch(for:) was called between 1 and 5 times .fetch(for: .any).called(.from(1, to: 5)) // assert checkout(with:) was called between exactly 10 times .checkout(with: .any).called(10) // assert url property was accessed at least 2 times .url().get(.moreOrEqual(to: 2)) // assert url property was never set to nil .url(newValue: .value(nil)).setCalled(.never) ``` If you are testing asynchronous code and cannot write sync assertions you can use the async counterparts of the above verifications: * [`calledEventually(_:before:)`](https://kolos65.github.io/Mockable/documentation/mockable/functionverifybuilder/calledeventually(_:before:file:line:)): Wait until timeout or invocation count satisfied. * [`getCalledEventually(_:before:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyverifybuilder/getcalledeventually(_:before:file:line:)): Wait until timeout or property access count satisfied. * [`setSalledEventually(_:before:)`](https://kolos65.github.io/Mockable/documentation/mockable/propertyverifybuilder/setcalledeventually(_:before:file:line:)): Wait until timeout or property assignment count satisfied. Here are some examples of async verifications: ```swift await verify(productService) // assert fetch(for:) was called between 1 and 5 times before default timeout (1 second) .fetch(for: .any).calledEventually(.from(1, to: 5)) // assert checkout(with:) was called between exactly 10 times before 3 seconds .checkout(with: .any).calledEventually(10, before: .seconds(3)) // assert url property was accessed at least 2 times before default timeout (1 second) .url().getCalledEventually(.moreOrEqual(to: 2)) // assert url property was set to nil once .url(newValue: .value(nil)).setCalledEventually(.once) ``` ## Relaxed Mode By default, you must specify a return value for all requirements; otherwise, a fatal error will be thrown. The reason for this is to aid in the discovery (and thus the verification) of every called function when writing unit tests. However, it is common to prefer avoiding this strict default behavior in favor of a more relaxed setting, where, for example, void or optional return values do not need explicit `given` registration. Use the Use [`MockerPolicy`](https://kolos65.github.io/Mockable/documentation/mockable/mockerpolicy) (which is an [option set](https://developer.apple.com/documentation/swift/optionset)) to implicitly mock: * only one kind of return value: `.relaxedMocked` * construct a custom set of policies: `[.relaxedVoid, .relaxedOptional]` * or opt for a fully relaxed mode: `.relaxed`. You have two options to override the default strict behavior of the library: * At **mock implementation level** you can override the mocker policy for each individual mock implementation in the initializer: ```swift let relaxedMock = MockService(policy: [.relaxedOptional, .relaxedVoid]) ``` * At **project level** you can set a custom default policy to use in every scenario by changing the default property of **MockerPolicy**: ```swift MockerPolicy.default = .relaxedVoid ``` The `.relaxedMocked` policy in combination with the [`Mocked`](https://kolos65.github.io/Mockable/documentation/mockable/mocked) protocol can be used to set an implicit return value for custom (or even built in) types: ```swift struct Car { var name: String var seats: Int } extension Car: Mocked { static var mock: Car { Car(name: "Mock Car", seats: 4) } // Defaults to [mock] but we can // provide a custom array of cars: static var mocks: [Car] { [ Car(name: "Mock Car 1", seats: 4), Car(name: "Mock Car 2", seats: 4) ] } } @Mockable protocol CarService { func getCar() -> Car func getCars() -> [Car] } func testCarService() { func test() { let mock = MockCarService(policy: .relaxedMocked) // Implictly mocked without a given registration: let car = mock.getCar() let cars = mock.getCars() } } ``` > ⚠️ Relaxed mode will not work with generic return values as the type system is unable to locate the appropriate generic overload. ## Working with non-equatable Types **Mockable** uses a [`Matcher`](https://kolos65.github.io/Mockable/documentation/mockable/matcher) internally to compare parameters. By default the matcher is able to compare any custom type that conforms to `Equatable` (except when used in a generic function). In special cases, when you * have non-equatable parameter types * need testing specific equality logic for a custom type * have generic functions that are used with custom concrete types you can register your custom types with the `Matcher.register()` functions. Here is how to do it: ```swift // register an equatable type to the matcher because we use it in a generic function Matcher.register(SomeEquatableType.self) // register a non-equatable type to the matcher Matcher.register(Product.self, match: { $0.name == $1.name }) // register a meta-type to the matcher Matcher.register(HomeViewController.Type.self) // remove all previously registered custom types Matcher.reset() ``` If you see this error during tests: ``` No comparator found for type XYZ. All non-equatable types must be registered using Matcher.register(_). ``` remember to add the noted type to your `Matcher` using the `register()` function. ================================================ FILE: Sources/Mockable/Helpers/Async+Timeout.swift ================================================ // // Async+Timeout.swift // Mockable // // Created by Kolos Foltanyi on 2024. 04. 04.. // import Foundation public struct TimeoutError: Error {} /// Runs an async task with a timeout. /// /// - Parameters: /// - maxDuration: The duration in seconds `work` is allowed to run before timing out. /// - work: The async operation to perform. /// - Returns: Returns the result of `work` if it completed in time. /// - Throws: Throws ``TimedOutError`` if the timeout expires before `work` completes. /// If `work` throws an error before the timeout expires, that error is propagated to the caller. @discardableResult func withTimeout( after maxDuration: TimeInterval, _ operation: @Sendable @escaping () async throws -> Value ) async throws -> Value { try await withThrowingTaskGroup(of: Value.self) { group in group.addTask(operation: operation) group.addTask { try await Task.sleep(nanoseconds: UInt64(maxDuration * 1_000_000_000)) throw TimeoutError() } let result = try await group.next()! // swiftlint:disable:this force_unwrapping group.cancelAll() return result } } ================================================ FILE: Sources/Mockable/Helpers/AsyncSubject.swift ================================================ // // AsyncSubject.swift // Mockable // // Created by Kolos Foltanyi on 2024. 12. 16.. // actor AsyncSubject: AsyncSequence { typealias Failure = Never // MARK: Types struct Subscription: Equatable { let id: UInt64 let continuation: AsyncStream.Continuation let stream: AsyncStream static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id } } // MARK: Private properties private(set) var value: Element private var ids: UInt64 = 0 private var subscriptions = [Subscription]() // MARK: Init init(_ initialValue: Element) { self.value = initialValue } deinit { for subscription in subscriptions { subscription.continuation.finish() } } func finish() { for subscription in subscriptions { subscription.continuation.finish() } subscriptions.removeAll() } nonisolated func makeAsyncIterator() -> AsyncIterator { AsyncIterator(parent: self) } func generateId() -> UInt64 { defer { ids &+= 1 } return ids } fileprivate func subscribe() -> (Subscription, Element) { let (stream, continuation) = AsyncStream.makeStream() let subscription = Subscription(id: generateId(), continuation: continuation, stream: stream) subscriptions.append(subscription) return (subscription, value) } fileprivate func remove(_ subscription: Subscription) { subscriptions.removeAll { $0 == subscription } subscription.continuation.finish() } func send(_ value: Element) { self.value = value for subscription in subscriptions { subscription.continuation.yield(value) } } func update(with block: (inout Element) -> Void) { block(&value) send(value) } class AsyncIterator: AsyncIteratorProtocol { private weak var parent: AsyncSubject? private var subscription: Subscription? private var iterator: AsyncStream.AsyncIterator? fileprivate init(parent: AsyncSubject) { self.parent = parent } deinit { cancelSubscription() } func next() async -> Element? { if iterator != nil { return await iterator?.next() } else if let parent { let (subscription, value) = await parent.subscribe() self.subscription = subscription iterator = subscription.stream.makeAsyncIterator() return value } else { return nil } } private func cancelSubscription() { guard let parent, let subscription else { return } Task { await parent.remove(subscription) } } } } ================================================ FILE: Sources/Mockable/Helpers/LockedValue.swift ================================================ // // LockedValue.swift // Mockable // // Created by Kolos Foltanyi on 2024. 12. 16.. // import Foundation /// A generic wrapper for isolating a mutable value with a lock. /// /// If you trust the sendability of the underlying value, consider using ``UncheckedSendable``, /// instead. @dynamicMemberLookup final class LockedValue: @unchecked Sendable { private var _value: Value private let lock = NSRecursiveLock() private var didSet: ((Value) -> Void)? /// Initializes lock-isolated state around a value. /// /// - Parameter value: A value to isolate with a lock. init(_ value: @autoclosure @Sendable () throws -> Value) rethrows { self._value = try value() } subscript(dynamicMember keyPath: KeyPath) -> Subject { self.lock.criticalRegion { self._value[keyPath: keyPath] } } /// Perform an operation with isolated access to the underlying value. /// /// Useful for modifying a value in a single transaction. /// /// ```swift /// // Isolate an integer for concurrent read/write access: /// var count = LockedValue(0) /// /// func increment() { /// // Safely increment it: /// self.count.withValue { $0 += 1 } /// } /// ``` /// /// - Parameter operation: An operation to be performed on the the underlying value with a lock. /// - Returns: The result of the operation. func withValue( _ operation: (inout Value) throws -> T ) rethrows -> T { try self.lock.criticalRegion { var value = self._value defer { self._value = value self.didSet?(self._value) } return try operation(&value) } } /// Overwrite the isolated value with a new value. /// /// ```swift /// // Isolate an integer for concurrent read/write access: /// var count = LockedValue(0) /// /// func reset() { /// // Reset it: /// self.count.setValue(0) /// } /// ``` /// /// > Tip: Use ``withValue(_:)`` instead of ``setValue(_:)`` if the value being set is derived /// > from the current value. That is, do this: /// > /// > ```swift /// > self.count.withValue { $0 += 1 } /// > ``` /// > /// > ...and not this: /// > /// > ```swift /// > self.count.setValue(self.count + 1) /// > ``` /// > /// > ``withValue(_:)`` isolates the entire transaction and avoids data races between reading and /// > writing the value. /// /// - Parameter newValue: The value to replace the current isolated value with. func setValue(_ newValue: @autoclosure () throws -> Value) rethrows { try self.lock.criticalRegion { self._value = try newValue() self.didSet?(self._value) } } } extension LockedValue where Value: Sendable { var value: Value { self.lock.criticalRegion { self._value } } /// Initializes lock-isolated state around a value. /// /// - Parameter value: A value to isolate with a lock. /// - Parameter didSet: A callback to invoke when the value changes. convenience init( _ value: @autoclosure @Sendable () throws -> Value, didSet: (@Sendable (Value) -> Void)? = nil ) rethrows { try self.init(value()) self.didSet = didSet } } extension NSRecursiveLock { @inlinable @discardableResult func criticalRegion(work: () throws -> R) rethrows -> R { self.lock() defer { self.unlock() } return try work() } } ================================================ FILE: Sources/Mockable/Macro/MockableMacro.swift ================================================ // // MockableMacro.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 20.. // /// A peer macro that generates a mock implementation for the protocol it is attached to. /// /// The generated implementation is named with a "Mock" prefix followed by the protocol name. /// By default, the generated code is enclosed in an `#if MOCKING` condition, ensuring it is only accessible /// in modules where the `MOCKING` compile-time condition is set. /// /// Example usage: /// /// ```swift /// @Mockable /// protocol UserService { /// func get(id: UUID) -> User /// func remove(id: UUID) throws /// } /// /// var mockUserService: MockUserService /// /// func test() { /// let error: UserError = .invalidId /// let mockUser = User(id: UUID()) /// /// given(mockUserService) /// .get(id: .any).willReturn(mockUser) /// .remove(id: .any).willThrow(error) /// /// try await loginService.login() /// /// verify(mockUserService) /// .get(id: .value(mockUser.id)).called(count: .once) /// .remove(id: .any).called(count: .never) /// } /// ``` @attached(peer, names: prefixed(Mock)) public macro Mockable() = #externalMacro(module: "MockableMacro", type: "MockableMacro") ================================================ FILE: Sources/Mockable/Matcher/Matcher.swift ================================================ // // Matcher.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 14.. // import Foundation /// A utility for defining matchers used in mock assertions. public class Matcher { // MARK: Public Types /// A closure type representing a comparator function for elements of type `T`. /// /// - Parameters: /// - lhs: The left-hand side element for comparison. /// - rhs: The right-hand side element for comparison. /// - Returns: `true` if the elements match according to the comparison criteria; otherwise, `false`. public typealias Comparator = (T, T) -> Bool /// A closure type representing a comparator function for elements of a sequence of type `T`. /// /// - Parameters: /// - lhs: The left-hand side element for comparison. /// - rhs: The right-hand side element for comparison. /// - Returns: `true` if the elements match according to the comparison criteria; otherwise, `false`. public typealias ElementComparator = (T.Element, T.Element) -> Bool // MARK: Private Types private typealias MatcherType = (mirror: Mirror, comparator: Any) // MARK: Private Properties private let matchers = LockedValue<[MatcherType]>([]) #if swift(>=6) nonisolated(unsafe) private static var `default` = Matcher() #else private static var `default` = Matcher() #endif // MARK: Init private init() { registerDefaultTypes() registerCustomTypes() } // MARK: - Reset /// Reset the default state of the matcher by removing all registered types. public static func reset() { `default` = Matcher() } // MARK: - Register /// Registers comparator for given type **T**. /// /// Comparator is a closure of `(T,T) -> Bool`. /// /// When several comparators for same type are registered to common /// **Matcher** instance - it will resolve the most receont one. /// /// - Parameters: /// - valueType: compared type /// - match: comparator closure public static func register(_ valueType: T.Type, match: @escaping Comparator) { Self.default.register(valueType, match: match) } /// Registers comparator for type, like comparing Int.self to Int.self. These types of comparators always returns true. Register like: `Matcher.default.register(CustomType.Type.self)` /// /// - Parameter valueType: Type.Type.self public static func register(_ valueType: T.Type.Type) { Self.default.register(valueType) } /// Register default comparator for Equatable types. Required for generic mocks to work. /// /// - Parameter valueType: Equatable type public static func register(_ valueType: T.Type) where T: Equatable { Self.default.register(valueType) } // MARK: - Comparator /// Returns comparator closure for given type (if any). /// /// Comparator is a closure of `(T,T) -> Bool`. /// /// When several comparators for same type are registered to common /// **Matcher** instance - it will resolve the most receont one. /// /// - Parameter valueType: compared type /// - Returns: comparator closure public static func comparator(for valueType: T.Type) -> Comparator? { Self.default.comparator(for: valueType) } /// Default Equatable comparator, compares if elements are equal. /// /// - Parameter valueType: Equatable type /// - Returns: comparator closure public static func comparator(for valueType: T.Type) -> Comparator? where T: Equatable { Self.default.comparator(for: valueType) } /// Default Equatable Sequence comparator, compares count, and then for every element equal element. /// /// - Parameter valueType: Equatable Sequence type /// - Returns: comparator closure public static func comparator(for valueType: T.Type) -> Comparator? where T: Equatable, T: Sequence { Self.default.comparator(for: valueType) } /// Default Sequence comparator, compares count, and then depending on sequence type: /// - for Arrays, elements will be compared element by element (verifying order as well) /// - other Sequences would be treated as unordered, so every element has to have matching element /// /// - Parameter valueType: Sequence type /// - Returns: comparator closure public static func comparator(for valueType: T.Type) -> Comparator? where T: Sequence { Self.default.comparator(for: valueType) } } // MARK: - Register extension Matcher { private func register(_ valueType: T.Type, match: @escaping Comparator) { let mirror = Mirror(reflecting: valueType) matchers.withValue { $0.append((mirror, match as Any)) } } private func register(_ valueType: T.Type.Type) { register(valueType, match: { _, _ in true }) } private func register(_ valueType: T.Type) where T: Equatable { let mirror = Mirror(reflecting: valueType) let comparator = comparator(for: T.self) matchers.withValue { $0.append((mirror, comparator as Any)) } } } // MARK: - Comparator extension Matcher { private func comparator(for valueType: T.Type) -> Comparator? { let mirror = Mirror(reflecting: valueType) return comparator(by: mirror) as? (T, T) -> Bool } private func comparator(for valueType: T.Type) -> Comparator? where T: Equatable { { $0 == $1 } } private func comparator(for valueType: T.Type) -> Comparator? where T: Equatable, T: Sequence { { $0 == $1 } } private func comparator(for valueType: T.Type) -> Comparator? where T: Sequence { let mirror = Mirror(reflecting: valueType) if let directComparator = comparator(by: mirror) as? Comparator { return directComparator } guard let elementComparator = comparator(for: T.Element.self) else { return nil } return sequenceComparator(for: valueType, elementComparator: elementComparator) } } // MARK: - Helpers extension Matcher { private func comparator(by mirror: Mirror) -> Any? { let snapshot = matchers.withValue { $0 } return snapshot.reversed().first { matcher -> Bool in matcher.mirror.subjectType == mirror.subjectType }?.comparator } private func sequenceComparator( for valueType: T.Type, elementComparator: @escaping ElementComparator ) -> Comparator? { { (left: T, right: T) -> Bool in let left = Array(left) let right = Array(right) guard left.count == right.count else { return false } if valueType is [T.Element].Type { return self.orderedCompare(left: left, right: right, comparator: elementComparator) } else { return self.unorderedCompare(left: left, right: right, comparator: elementComparator) } } } private func orderedCompare(left: [T], right: [T], comparator: Comparator) -> Bool { left.enumerated().allSatisfy { index, element in comparator(element, right[index]) } } private func unorderedCompare(left: [T], right: [T], comparator: Comparator) -> Bool { var buffer = right for element in left { let index = buffer.firstIndex { comparator(element, $0) } guard let index else { return false } buffer.remove(at: index) } return buffer.isEmpty } } // MARK: - Defaults extension Matcher { private func registerCustomTypes() { register(GenericValue.self) { left, right -> Bool in left.comparator(left.value, right.value) } } private func registerDefaultTypes() { registerBasicTypes() registerArrays() registerMetaTypes() } private func registerBasicTypes() { register(Bool.self) register(String.self) register(Float.self) register(Double.self) register(Character.self) register(Int.self) register(Int8.self) register(Int16.self) register(Int32.self) register(Int64.self) register(UInt.self) register(UInt8.self) register(UInt16.self) register(UInt32.self) register(UInt64.self) register(Data.self) register(UUID.self) register(Bool?.self) register(String?.self) register(Float?.self) register(Double?.self) register(Character?.self) register(Int?.self) register(Int8?.self) register(Int16?.self) register(Int32?.self) register(Int64?.self) register(UInt?.self) register(UInt8?.self) register(UInt16?.self) register(UInt32?.self) register(UInt64?.self) register(Data?.self) register(UUID?.self) } private func registerArrays() { register([Bool].self) register([String].self) register([Float].self) register([Double].self) register([Character].self) register([Int].self) register([Int8].self) register([Int16].self) register([Int32].self) register([Int64].self) register([UInt].self) register([UInt8].self) register([UInt16].self) register([UInt32].self) register([UInt64].self) register([Data].self) register([UUID].self) register([Bool?].self) register([String?].self) register([Float?].self) register([Double?].self) register([Character?].self) register([Int?].self) register([Int8?].self) register([Int16?].self) register([Int32?].self) register([Int64?].self) register([UInt?].self) register([UInt8?].self) register([UInt16?].self) register([UInt32?].self) register([UInt64?].self) register([Data?].self) register([UUID?].self) } private func registerMetaTypes() { register(Any.Type.self) { _, _ in true } register(Bool.Type.self) register(String.Type.self) register(Float.Type.self) register(Double.Type.self) register(Character.Type.self) register(Int.Type.self) register(Int8.Type.self) register(Int16.Type.self) register(Int32.Type.self) register(Int64.Type.self) register(UInt.Type.self) register(UInt8.Type.self) register(UInt16.Type.self) register(UInt32.Type.self) register(UInt64.Type.self) register(Data.Type.self) register(Any?.Type.self) { _, _ in true } register(Bool?.Type.self) register(String?.Type.self) register(Float?.Type.self) register(Double?.Type.self) register(Character?.Type.self) register(Int?.Type.self) register(Int8?.Type.self) register(Int16?.Type.self) register(Int32?.Type.self) register(Int64?.Type.self) register(UInt?.Type.self) register(UInt8?.Type.self) register(UInt16?.Type.self) register(UInt32?.Type.self) register(UInt64?.Type.self) register(Data?.Type.self) } } ================================================ FILE: Sources/Mockable/Mocker/CaseIdentifiable.swift ================================================ // // CaseIdentifiable.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 22.. // /// A protocol for enumerations that can be identified by a unique identifier. /// /// Enumerations conforming to `CaseIdentifiable` must provide a computed property `id` /// that returns a unique identifier, typically derived from the type name. public protocol CaseIdentifiable: Equatable, Hashable { /// A computed property that returns a unique identifier for the case. var id: String { get } /// A computed property that returns a human readably name for member enum cases. var name: String { get } /// A computed property that returns a human readably description of member parameters. var parameters: String { get } } extension CaseIdentifiable { /// A default implementation of the `id` property, deriving the identifier from the case name. /// /// The default implementation removes any additional information such as parameters /// by splitting the type name at the opening parenthesis. public var id: String { String(String(describing: self).split(separator: "(")[0]) } /// A default implementation of the `name` property, deriving a human readable name for member enum cases. public var name: String { guard let lastToken = id.split(separator: "_").last else { return id } return String(lastToken) } /// A default implementation of the `parameters` property, deriving a human readable name for member parameters. public var parameters: String { let description = String(describing: self) guard let index = description.firstIndex(of: "(") else { return "no parameters" } return String(description[index...]) } } extension CaseIdentifiable { /// Compares two `CaseIdentifiable` instances for equality based on their unique identifiers. /// /// - Parameters: /// - lhs: The left-hand side `CaseIdentifiable`. /// - rhs: The right-hand side `CaseIdentifiable`. /// - Returns: `true` if the instances have the same identifier; otherwise, `false`. public static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id } /// Hashes a `CaseIdentifiable` instance using its unique identifier. /// /// - Parameter hasher: The hasher to use for combining the hash value. public func hash(into hasher: inout Hasher) { hasher.combine(id) } } ================================================ FILE: Sources/Mockable/Mocker/Matchable.swift ================================================ // // Matchable.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 14.. // /// A protocol for types that can be used as matchers in mock assertions. public protocol Matchable { /// Determines if the receiver matches another instance of the same type according to custom criteria. /// /// - Parameter other: The instance to compare against. /// - Returns: `true` if the receiver matches the specified instance; otherwise, `false`. func match(_ other: Self) -> Bool } ================================================ FILE: Sources/Mockable/Mocker/MemberAction.swift ================================================ // // MemberAction.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 14.. // /// A class representing an action to be performed on a member. /// /// `MemberAction` associates a member of type `Member` with a closure (`action`) /// that can be executed when needed. public class MemberAction { /// The member associated with the action. public let member: Member /// The closure representing the action to be performed. public let action: () -> Void /// Initializes a new instance of `MemberAction`. /// /// - Parameters: /// - member: The member to associate with the action. /// - action: The closure representing the action to be performed. public init(member: Member, action: @escaping () -> Void) { self.member = member self.action = action } } ================================================ FILE: Sources/Mockable/Mocker/MemberReturn.swift ================================================ // // MemberReturn.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 14.. // /// A class representing the return value associated with a member. /// /// `MemberReturn` associates a member of type `Member` with a `ReturnValue` object, /// encapsulating the information about the expected return value or behavior. public class MemberReturn { /// The member associated with the return value. public let member: Member /// The `ReturnValue` object encapsulating information about the expected return value or behavior. public let returnValue: ReturnValue /// Initializes a new instance of `MemberReturn`. /// /// - Parameters: /// - member: The member to associate with the return value. /// - returnValue: The `ReturnValue` object representing the expected return value or behavior. public init(member: Member, returnValue: ReturnValue) { self.member = member self.returnValue = returnValue } } ================================================ FILE: Sources/Mockable/Mocker/Mocked.swift ================================================ // // Mocked.swift // Mocked // // Created by Kolos Foltanyi on 2024. 04. 03.. // /// A protocol that represents auto-mocked types. /// /// `Mocked` in combination with a `relaxedMocked` option of `MockerPolicy `can be used /// to set an implicit return value for custom types: /// /// ```swift /// struct Car { /// var name: String /// var seats: Int /// } /// /// extension Car: Mocked { /// static var mock: Car { /// Car(name: "Mock Car", seats: 4) /// } /// /// // Defaults to [mock] but we can /// // provide a custom array of cars: /// static var mocks: [Car] { /// [ /// Car(name: "Mock Car 1", seats: 4), /// Car(name: "Mock Car 2", seats: 4) /// ] /// } /// } /// /// @Mockable /// protocol CarService { /// func getCar() -> Car /// func getCars() -> [Car] /// } /// /// func testCarService() { /// func test() { /// let mock = MockCarService(policy: .relaxedMocked) /// // Implictly mocked without a given registration: /// let car = mock.getCar() /// let cars = mock.getCars() /// } /// } /// ``` public protocol Mocked { /// A default mock return value to use when `.relaxedMocked` policy is set. static var mock: Self { get } /// An array of mock values to use as return values when `.relaxedMocked` policy is set. /// Defaults to `[Self.mock]`. static var mocks: [Self] { get } } extension Mocked { public static var mocks: [Self] { [mock] } } extension Array: Mocked where Element: Mocked { public static var mock: Self { Element.mocks } } extension Optional: Mocked where Wrapped: Mocked { public static var mock: Self { Wrapped.mock } } ================================================ FILE: Sources/Mockable/Mocker/Mocker.swift ================================================ // // Mocker.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 14.. // import Foundation import IssueReporting /// A class responsible for mocking and verifying interactions with a mockable service. /// /// The `Mocker` class keeps track of invocations, expected return values, and actions associated with /// specific members of a mockable service. public class Mocker: @unchecked Sendable { // MARK: Public Properties /// The associated type representing a member of the mockable service. public typealias Member = Service.Member /// The associated type representing the return value of a member. public typealias Return = Service.Return /// The associated type representing an action to be performed on a member. public typealias Action = Service.Action /// Custom relaxation policy to use when missing return values. public var policy: MockerPolicy? // MARK: Private Properties /// Dictionary to store expected return values for each member. private var returns = LockedValue<[Member: [Return]]>([:]) /// Dictionary to store actions to be performed on each member. private var actions = LockedValue<[Member: [Action]]>([:]) /// Array to store invocations of members. private lazy var invocations = LockedValue<[Member]>([]) { [invocationsSubject] newValue in Task { #if swift(>=6.0) && !swift(>=6.1) invocationsSubject.send(newValue) #else await invocationsSubject.send(newValue) #endif } } /// Subject to track invocations. private var invocationsSubject = AsyncSubject<[Member]>([]) /// Resolved relaxation policy to use when missing return values. private var currentPolicy: MockerPolicy { policy ?? .default } // MARK: Init /// Initializes a new instance of `Mocker`. public init(policy: MockerPolicy? = nil) { self.policy = policy } // MARK: Public Methods /// Adds an invocation for a member to the list of invocations. /// /// - Parameter member: The member for which the invocation is added. public func addInvocation(for member: Member) { invocations.withValue { invocations in invocations.append(member) } } /// Specifies an expected return value for a member. /// /// - Parameters: /// - member: The member for which the return value is specified. /// - returnValue: The expected return value. public func addReturnValue(_ returnValue: ReturnValue, for member: Member) { let given = Return(member: member, returnValue: returnValue) returns.withValue { returns in returns[member] = (returns[member] ?? []) + [given] } } /// Specifies an action to be performed on a member. /// /// - Parameters: /// - member: The member for which the action is specified. /// - action: The action to be performed. public func addAction(_ action: @escaping () -> Void, for member: Member) { let action = Action(member: member, action: action) actions.withValue { actions in actions[action.member] = (actions[action.member] ?? []) + [action] } } /// Verifies the number of times a member has been called. /// /// - Parameters: /// - member: The member to verify. /// - count: The expected number of invocations. public func verify(member: Member, count: Count, fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column) { let matches = invocations.value.filter(member.match) let message = """ Expected \(count) invocation(s) of \(member.name), but was \(matches.count).", """ guard count.satisfies(matches.count) else { reportIssue(message, fileID: fileID, filePath: filePath, line: line, column: column) return } } /// Verifies the number of times a member should be called. /// /// - Parameters: /// - member: The member to verify. /// - count: The expected number of invocations. /// - timeout: The maximum time it will wait for assertion to be true public func verify(member: Member, count: Count, timeout: TimeoutDuration, fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, column: UInt = #column) async { do { try await withTimeout(after: timeout.duration) { [invocationsSubject] in for await invocations in invocationsSubject { let matches = invocations.filter(member.match) if count.satisfies(matches.count) { break } else { continue } } } } catch { let matches = invocations.value.filter(member.match) let message = """ Expected \(count) invocation(s) of \(member.name) before \(timeout.duration) s, but was \(matches.count). """ guard count.satisfies(matches.count) else { reportIssue(message, fileID: fileID, filePath: filePath, line: line, column: column) return } } } /// Resets the state of the mocker. /// /// - Parameter scopes: The set of scopes to reset (given, effect, verify). public func reset(scopes: Set) { MockerScope.allCases.forEach { scope in guard scopes.contains(scope) else { return } switch scope { case .given: returns.setValue([:]) case .when: actions.setValue([:]) case .verify: invocations.setValue([]) } } } /// Performs actions associated with a member. /// /// - Parameter member: The member for which actions should be performed. public func performActions(for member: Member) { actions.withValue { actions in guard let actions = actions[member] else { return } let matches = actions.filter { member.match($0.member) } matches.forEach { $0.action() } } } /// Mocks a member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mock(_ member: Member, producerResolver: (Any) throws -> V) -> V { // swiftlint:disable:next force_try return try! mock(member, producerResolver, .none) } /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mockThrowing(_ member: Member, producerResolver: (Any) throws -> V) throws -> V { return try mock(member, producerResolver, .none) } #if swift(>=6) /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mockThrowing(_ member: Member, error: E.Type, producerResolver: (Any) throws -> V) throws(E) -> V { do { return try mock(member, producerResolver, .none) } catch { throw error as! E // swiftlint:disable:this force_cast } } #endif } // MARK: - Helpers extension Mocker { private func mock( _ member: Member, _ producerResolver: (Any) throws -> V, _ fallback: MockerFallback ) throws -> V { addInvocation(for: member) performActions(for: member) // swiftlint:disable:next closure_body_length return try returns.withValue { returns in let matchCount = returns[member]? .filter { member.match($0.member) } .count ?? 0 guard var candidates = returns[member], matchCount != 0 else { if case .value(let value) = fallback { return value } else { let message = notMockedMessage(member, value: V.self) reportIssue(message) fatalError(message) } } for index in candidates.indices { let match = candidates[index] guard member.match(match.member) else { continue } let removeMatch: (inout [Member: [Return]]) -> Void = { returns in guard matchCount > 1 else { return } candidates.remove(at: index) returns[member] = candidates } switch match.returnValue { case .return(let value): guard let value = value as? V else { continue } removeMatch(&returns) return value case .throw(let error): removeMatch(&returns) throw error case .produce(let producer): do { let value = try producerResolver(producer) removeMatch(&returns) return value } catch ProducerCastError.typeMismatch { continue } catch { removeMatch(&returns) throw error } } } let message = genericNotMockedMessage(member, value: V.self) reportIssue(message) fatalError(message) } } } // MARK: - Error Messages extension Mocker { private func notMockedMessage(_ member: Member, value: V.Type) -> String { """ No return value found for member "\(member.name)" of "\(Service.self)" \ with parameter conditions: \(member.parameters) \ At least one return value of type "\(V.self)" must be provided \ matching the given parameters. Use a "given" clause to provide return values. """ } private func genericNotMockedMessage(_ member: Member, value: V.Type) -> String { """ No generic return value of type \(V.self) found for member "\(member.name)" of "\(Service.self)" \ with parameter conditions: \(member.parameters) \ At least one return value of type "\(V.self)" must be provided \ matching the given parameters. Use a "given" clause to provide return values. """ } } // MARK: - Void extension Mocker { /// Mocks a member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. public func mock(_ member: Member, producerResolver: (Any) throws -> Void) { let relaxed = currentPolicy.contains(.relaxedNonThrowingVoid) // swiftlint:disable:next force_try return try! mock(member, producerResolver, relaxed ? .value(()) : .none) } /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. public func mockThrowing(_ member: Member, producerResolver: (Any) throws -> Void) throws { let relaxed = currentPolicy.contains(.relaxedThrowingVoid) return try mock(member, producerResolver, relaxed ? .value(()) : .none) } #if swift(>=6) /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. public func mockThrowing(_ member: Member, error: E.Type, producerResolver: (Any) throws -> Void) throws(E) { do { let relaxed = currentPolicy.contains(.relaxedThrowingVoid) return try mock(member, producerResolver, relaxed ? .value(()) : .none) } catch { throw error as! E // swiftlint:disable:this force_cast } } #endif } // MARK: - Optional extension Mocker { /// Mocks a member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mock( _ member: Member, producerResolver: (Any) throws -> V ) -> V where V: ExpressibleByNilLiteral { let relaxed = currentPolicy.contains(.relaxedOptional) // swiftlint:disable:next force_try return try! mock(member, producerResolver, relaxed ? .value(nil) : .none) } /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mockThrowing( _ member: Member, producerResolver: (Any) throws -> V ) throws -> V where V: ExpressibleByNilLiteral { let relaxed = currentPolicy.contains(.relaxedOptional) return try mock(member, producerResolver, relaxed ? .value(nil) : .none) } #if swift(>=6) /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mockThrowing( _ member: Member, error: E.Type, producerResolver: (Any) throws -> V ) throws(E) -> V where V: ExpressibleByNilLiteral { do { let relaxed = currentPolicy.contains(.relaxedThrowingVoid) return try mock(member, producerResolver, relaxed ? .value(nil) : .none) } catch { throw error as! E // swiftlint:disable:this force_cast } } #endif } // MARK: - Mockable extension Mocker { /// Mocks a member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mock( _ member: Member, producerResolver: (Any) throws -> V ) -> V where V: Mocked { let relaxed = currentPolicy.contains(.relaxedMocked) // swiftlint:disable:next force_try return try! mock(member, producerResolver, relaxed ? .value(.mock) : .none) } /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mockThrowing( _ member: Member, producerResolver: (Any) throws -> V ) throws -> V where V: Mocked { let relaxed = currentPolicy.contains(.relaxedMocked) return try mock(member, producerResolver, relaxed ? .value(.mock) : .none) } #if swift(>=6) /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mockThrowing( _ member: Member, error: E, producerResolver: (Any) throws -> V ) throws(E) -> V where V: Mocked { do { let relaxed = currentPolicy.contains(.relaxedMocked) return try mock(member, producerResolver, relaxed ? .value(.mock) : .none) } catch { throw error as! E // swiftlint:disable:this force_cast } } #endif } // MARK: - Mockable + Optional extension Mocker { /// Mocks a member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mock( _ member: Member, producerResolver: (Any) throws -> V ) -> V where V: Mocked, V: ExpressibleByNilLiteral { // swiftlint:disable force_try if currentPolicy.contains(.relaxedMocked) { return try! mock(member, producerResolver, .value(.mock)) } else if currentPolicy.contains(.relaxedOptional) { return try! mock(member, producerResolver, .value(nil)) } else { return try! mock(member, producerResolver, .none) } // swiftlint:enable force_try } /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mockThrowing( _ member: Member, producerResolver: (Any) throws -> V ) throws -> V where V: Mocked, V: ExpressibleByNilLiteral { if currentPolicy.contains(.relaxedMocked) { return try mock(member, producerResolver, .value(.mock)) } else if currentPolicy.contains(.relaxedOptional) { return try mock(member, producerResolver, .value(nil)) } else { return try mock(member, producerResolver, .none) } } #if swift(>=6) /// Mocks a throwing member, performing associated actions and providing the expected return value. /// /// - Parameters: /// - member: The member to mock. /// - producerResolver: A closure resolving the produced value. /// - Returns: The expected return value. @discardableResult public func mockThrowing( _ member: Member, error: E.Type, producerResolver: (Any) throws -> V ) throws(E) -> V where V: Mocked, V: ExpressibleByNilLiteral { do { if currentPolicy.contains(.relaxedMocked) { return try mock(member, producerResolver, .value(.mock)) } else if currentPolicy.contains(.relaxedOptional) { return try mock(member, producerResolver, .value(nil)) } else { return try mock(member, producerResolver, .none) } } catch { throw error as! E // swiftlint:disable:this force_cast } } #endif } ================================================ FILE: Sources/Mockable/Mocker/MockerFallback.swift ================================================ // // MockerFallback.swift // Mockable // // Created by Kolos Foltanyi on 2024. 03. 17.. // /// Describes an optional default value to use when no stored /// return value found during mocking. enum MockerFallback { /// Specifies a default value to be used when no stored return value is found. case value(V) /// Specifies that no default value should be used when no stored return value is found. /// This results in a fatal error. This is the default behavior. case none } ================================================ FILE: Sources/Mockable/Mocker/MockerPolicy.swift ================================================ // // MockerPolicy.swift // Mockable // // Created by Kolos Foltanyi on 2024. 04. 01.. // /// A policy that controls how the library handles when no return value is found during mocking. /// /// MockerPolicy can be used to customize mocking behavior and disable the requirement of /// return value registration in case of certain types. public struct MockerPolicy: OptionSet, Sendable { /// Default policy to use when none was explicitly specified for a mock. /// /// Change this property to set the default policy to use for all mocks. Defaults to `strict`. #if swift(>=6) nonisolated(unsafe) public static var `default`: Self = .strict #else public static var `default`: Self = .strict #endif /// All return values must be registered, a fatal error will occur otherwise. public static let strict: Self = [] /// Every literal expressible requirement will return a default value. public static let relaxed: Self = [ .relaxedOptional, .relaxedThrowingVoid, .relaxedNonThrowingVoid, .relaxedMocked ] /// Every void function will run normally without a registration public static let relaxedVoid: Self = [ .relaxedNonThrowingVoid, .relaxedThrowingVoid ] /// Throwing Void functions will run without return value registration. public static let relaxedThrowingVoid = Self(rawValue: 1 << 0) /// Non-throwing Void functions will run without return value registration. public static let relaxedNonThrowingVoid = Self(rawValue: 1 << 1) /// Optional return values will default to nil. public static let relaxedOptional = Self(rawValue: 1 << 2) /// Types conforming to the `Mocked` protocol will default to their mock value. public static let relaxedMocked = Self(rawValue: 1 << 3) /// Option set raw value. public let rawValue: Int /// Creates a new option set from the given raw value. public init(rawValue: Int) { self.rawValue = rawValue } } ================================================ FILE: Sources/Mockable/Mocker/MockerScope.swift ================================================ // // MockerScope.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 21.. // /// An enumeration representing different scopes of the Mocker state. /// /// Scopes can be used to only reset specific states of a mock service. public enum MockerScope: CaseIterable { /// The scope for storing expected return values. case given /// The scope for storing actions to be performed on members. case when /// The scope for storing invocations to be verified. case verify } extension Set where Element == MockerScope { /// A convenience property representing a set containing all available scopes. public static var all: Self { Set(MockerScope.allCases) } } ================================================ FILE: Sources/Mockable/Models/Count.swift ================================================ // // Count.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 13.. // /// An enumeration representing different counting conditions for verifying invocations. /// /// Use `Count` in `verify` clauses to write assertions: /// ```swift /// // Assert `fetch(for)` was called between 1 and 5 times: /// verify(productService) /// .fetch(for: .any) /// .called(.from(1, to: 5)) /// ``` public enum Count: ExpressibleByIntegerLiteral, Sendable { /// The associated type for the integer literal. public typealias IntegerLiteralType = Int /// Initializes a new instance of `Count` using an integer literal. /// /// - Parameter value: The integer literal value. public init(integerLiteral value: IntegerLiteralType) { self = .exactly(value) } /// The member was called at least once. case atLeastOnce /// The member was called exactly once. case once /// The member was called a specific number of times. case exactly(Int) /// The member was called within a specific range of times. case from(Int, to: Int) /// The member was called less than a specific number of times. case less(than: Int) /// The member was called less than or equal to a specific number of times. case lessOrEqual(to: Int) /// The member was called more than a specific number of times. case more(than: Int) /// The member was called more than or equal to a specific number of times. case moreOrEqual(to: Int) /// The member was never called. case never /// Checks if the given count satisfies the specified condition. /// /// - Parameter count: The actual count to be compared. /// - Returns: `true` if the condition is satisfied; otherwise, `false`. func satisfies(_ count: Int) -> Bool { switch self { case .atLeastOnce: return count >= 1 case .once: return count == 1 case .exactly(let times): return count == times case .from(let from, let to): return count >= from && count <= to case .less(let than): return count < than case .lessOrEqual(let to): return count <= to case .more(let than): return count > than case .moreOrEqual(let to): return count >= to case .never: return count == 0 } } } extension Count: CustomStringConvertible { public var description: String { switch self { case .atLeastOnce: return "at least 1" case .once: return "exactly one" case .exactly(let value): return "exactly \(value)" case .from(let lowerBound, let upperBound): return "from \(lowerBound) to \(upperBound)" case .less(let value): return "less than \(value)" case .lessOrEqual(let value): return "less than or equal to \(value)" case .more(let value): return "more than \(value)" case .moreOrEqual(let value): return "more than or equal to \(value)" case .never: return "none" } } } ================================================ FILE: Sources/Mockable/Models/GenericValue.swift ================================================ // // GenericAttribute.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 17.. // /// A type erased wrapper for generic parameters. /// /// `GenericValue` encapsulates an arbitrary generic value along with a custom comparator closure. /// The comparator is used to determine equality between two instances of `GenericValue`. public struct GenericValue { /// The encapsulated value of type `Any`. public let value: Any /// The comparator closure used to determine equality between two instances of `GenericValue`. public let comparator: (Any, Any) -> Bool /// Initializes a new instance of `GenericValue`. /// /// - Parameters: /// - value: The value to be encapsulated. /// - comparator: The closure used to determine equality between two instances of `GenericValue`. public init(value: Any, comparator: @escaping (Any, Any) -> Bool) { self.value = value self.comparator = comparator } } ================================================ FILE: Sources/Mockable/Models/Parameter+Match.swift ================================================ // // Parameter+Match.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 25.. // import IssueReporting extension Parameter { private func match(_ parameter: Parameter, using comparator: Matcher.Comparator?) -> Bool { switch (self, parameter) { case (.any, _): return true case (_, .any): return true case (.value(let value), .matching(let matcher)): return matcher(value) case (.matching(let matcher), .value(let value)): return matcher(value) case (.value(let value1), .value(let value2)): guard let comparator else { reportIssue(noComparatorMessage) fatalError(noComparatorMessage) } return comparator(value1, value2) default: return false } } } // MARK: - Default extension Parameter { /// Matches the current parameter with another parameter. /// /// - Parameter parameter: The parameter to match against. /// - Returns: `true` if the parameters match; otherwise, `false`. public func match(_ parameter: Parameter) -> Bool { match(parameter, using: Matcher.comparator(for: Value.self)) } } // MARK: - Equatable extension Parameter where Value: Equatable { /// Matches the current parameter with another parameter. /// /// - Parameter parameter: The parameter to match against. /// - Returns: `true` if the parameters match; otherwise, `false`. public func match(_ parameter: Parameter) -> Bool { match(parameter, using: Matcher.comparator(for: Value.self)) } } // MARK: - Sequence extension Parameter where Value: Sequence { /// Matches the current parameter with another parameter. /// /// - Parameter parameter: The parameter to match against. /// - Returns: `true` if the parameters match; otherwise, `false`. public func match(_ parameter: Parameter) -> Bool { match(parameter, using: Matcher.comparator(for: Value.self)) } } // MARK: - Equatable Sequence extension Parameter where Value: Equatable, Value: Sequence { /// Matches the current parameter with another parameter. /// /// - Parameter parameter: The parameter to match against. /// - Returns: `true` if the parameters match; otherwise, `false`. public func match(_ parameter: Parameter) -> Bool { match(parameter, using: Matcher.comparator(for: Value.self)) } } ================================================ FILE: Sources/Mockable/Models/Parameter.swift ================================================ // // Parameter.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 13.. // import IssueReporting /// An enumeration representing different types of parameters used in mocking. public enum Parameter: @unchecked Sendable { /// Matches any value. case any /// Matches a specific value. case value(Value) /// Matches a value using a custom matching closure. case matching((Value) -> Bool) } extension Parameter { /// Creates a type erased parameter from a value of type `T`. /// /// - Parameter value: The value to be encapsulated in a generic parameter. /// - Returns: A a type erased parameter containing the provided value. public static func generic(_ value: T) -> Parameter { Parameter.value(value).eraseToGenericValue() } /// Type erases a parameter of type `Parameter` to a `Parameter` /// /// - Returns: A type erased parameter with the same matching behavior. public func eraseToGenericValue() -> Parameter { switch self { case .any: return .any case .value(let value): let value = GenericValue(value: value) { left, right in guard let left = left as? Value, let right = right as? Value else { return false } guard let comparator = Matcher.comparator(for: Value.self) else { reportIssue(noComparatorMessage) fatalError(noComparatorMessage) } return comparator(left, right) } return .value(value) case .matching(let matcher): return .matching { value in guard let value = value.value as? Value else { return false } return matcher(value) } } } } extension Parameter { var noComparatorMessage: String { """ No comparator found for type "\(Value.self)". \ All non-equatable types must be registered using Matcher.register(_). """ } } ================================================ FILE: Sources/Mockable/Models/ReturnValue.swift ================================================ // // ReturnValue.swift // Mockable // // Created by Kolos Foltanyi on 2023. 11. 13.. // /// An enumeration representing different types of return values for mocked functions. /// /// - `return(Any)`: A concrete value to be returned. /// - `throw(Error)`: An error to be thrown. /// - `produce(Any)`: A value producer to be invoked. public enum ReturnValue { /// A case representing a concrete value to be returned. case `return`(Any) /// A case representing an error to be thrown. case `throw`(any Error) /// A case representing a value producer to be invoked. case produce(Any) } /// An error thrown when type erased producers cannot be casted to their original closure type. public enum ProducerCastError: Error { case typeMismatch } /// Casts the given producer to the specified type. /// /// This function is used to cast the producer to a specific type when using the `.produce` case /// in the `ReturnValue` enum. /// /// - Parameter producer: The producer to be cast. /// - Returns: The casted producer of type `P`. public func cast

(_ producer: Any) throws -> P { guard let producer = producer as? P else { throw ProducerCastError.typeMismatch } return producer } ================================================ FILE: Sources/Mockable/Models/TimeoutDuration.swift ================================================ // // TimeoutDuration.swift // Mockable // // // Created by Nayanda Haberty on 3/4/24. // import Foundation /// An enumeration representing a duration of time. public enum TimeoutDuration { /// A TimeoutDuration representing a given number of seconds. case seconds(Double) /// A TimeoutDuration representing a given number of miliseconds. case miliseconds(UInt) /// Converts the duration to TimeInterval. public var duration: TimeInterval { switch self { case .seconds(let value): value case .miliseconds(let value): TimeInterval(value) / 1000 } } } // MARK: - ExpressibleByFloatLiteral extension TimeoutDuration: ExpressibleByFloatLiteral { public init(floatLiteral value: Double) { self = .seconds(value) } } // MARK: - ExpressibleByIntegerLiteral extension TimeoutDuration: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int) { self = .seconds(Double(value)) } } ================================================ FILE: Sources/Mockable/Utils/Utils.swift ================================================ // // Utils.swift // Mockable // // Created by Kolos Foltanyi on 2024. 03. 17.. // /// Creates a proxy for building return values for members of the given service. /// /// Example usage of `given(_ service:)`: /// ```swift /// // Throw an error for the first call and then return 'product' for every other call /// given(productService) /// .fetch(for: .any).willThrow(error) /// .fetch(for: .any).willReturn(product) /// /// // Throw an error if the id parameter ends with a 0, return a product otherwise /// given(productService) /// .fetch(for: .any).willProduce { id in /// if id.uuidString.last == "0" { /// throw error /// } else { /// return product /// } /// } /// ``` /// /// - Parameter service: The mockable service for which return values are specified. /// - Returns: The service's return value builder. public func given(_ service: T) -> T.ReturnBuilder { service._given } /// Creates a proxy for building actions for members of the given service. /// /// Example usage of `when(_ service:)`: /// ```swift /// // log calls to fetch(for:) /// when(productService).fetch(for: .any).perform { /// print("fetch(for:) was called") /// } /// /// // log when url is accessed /// when(productService).url().performOnGet { /// print("url accessed") /// } /// /// // log when url is set to nil /// when(productService).url(newValue: .value(nil)).performOnSet { /// print("url set to nil") /// } /// ``` /// /// - Parameter service: The mockable service for which actions are specified. /// - Returns: The service's action builder. public func when(_ service: T) -> T.ActionBuilder { service._when } /// Creates a proxy for verifying invocations of members of the given service. /// /// Example usage of `verify(_ service:)`: /// ```swift /// verify(productService) /// // assert fetch(for:) was called between 1 and 5 times /// .fetch(for: .any).called(.from(1, to: 5)) /// // assert checkout(with:) was called between exactly 10 times /// .checkout(with: .any).called(10) /// // assert url property was accessed at least 2 times /// .url().getCalled(.moreOrEqual(to: 2)) /// // assert url property was never set to nil /// .url(newValue: .value(nil)).setCalled(.never) /// ``` /// - Parameter service: The mockable service for which invocations are verified. /// - Returns: The service's verification builder. public func verify(_ service: T) -> T.VerifyBuilder { service._verify } ================================================ FILE: Sources/MockableMacro/Extensions/AttributeListSyntax+Extensions.swift ================================================ // // File.swift // Mockable // // Created by Kolos Foltányi on 2025. 11. 16.. // import SwiftSyntax extension AttributeListSyntax { func filter(allowedNames: [String]) -> AttributeListSyntax { filter { element in switch element { case .attribute(let attributeSyntax): return attribute(attributeSyntax, matches: allowedNames) case .ifConfigDecl(let ifConfigDeclSyntax): return ifConfigDeclSyntax.clauses .compactMap { $0.elements } .allSatisfy { item in guard case .attributes(let list) = item else { return false } return list.allSatisfy { guard case .attribute(let attr) = $0 else { return false } return attribute(attr, matches: allowedNames) } } } } } private func attribute(_ attribute: AttributeSyntax, matches allowedNames: [String]) -> Bool { allowedNames.contains(attribute.attributeName.trimmedDescription) } } ================================================ FILE: Sources/MockableMacro/Extensions/AttributeSyntax+Extensions.swift ================================================ // // AttributeSyntax+Extensions.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 25.. // import SwiftSyntax extension AttributeSyntax { static var escaping: Self { AttributeSyntax( atSign: .atSignToken(), attributeName: IdentifierTypeSyntax( name: .keyword(.escaping) ) ) } } ================================================ FILE: Sources/MockableMacro/Extensions/DeclModifierListSyntax+Extensions.swift ================================================ // // DeclModifierListSyntax+Extensions.swift // Mockable // // Created by Kolos Foltanyi on 27/09/2024. // import SwiftSyntax extension DeclModifierListSyntax { func filtered(keywords: Set) -> DeclModifierListSyntax { filter { modifier in guard case .keyword(let keyword) = modifier.name.tokenKind else { return false } return keywords.contains(keyword) } } func appending(_ other: DeclModifierListSyntax) -> DeclModifierListSyntax { self + Array(other) } } ================================================ FILE: Sources/MockableMacro/Extensions/FunctionDeclSyntax+Extensions.swift ================================================ // // FunctionDeclSyntax+Extensions.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax extension FunctionDeclSyntax { var isVoid: Bool { signature.returnClause == nil } var isThrowing: Bool { #if canImport(SwiftSyntax600) signature.effectSpecifiers?.throwsClause?.throwsSpecifier.tokenKind == .keyword(.throws) #else signature.effectSpecifiers?.throwsSpecifier?.tokenKind == .keyword(.throws) #endif } var closureType: FunctionTypeSyntax { let params = signature.parameterClause.parameters .map { $0.resolvedType(for: .parameter) } #if canImport(SwiftSyntax600) let effectSpecifiers = TypeEffectSpecifiersSyntax( throwsClause: isThrowing ? .init(throwsSpecifier: .keyword(.throws)) : nil ) #else let effectSpecifiers = TypeEffectSpecifiersSyntax( throwsSpecifier: isThrowing ? .keyword(.throws) : nil ) #endif return FunctionTypeSyntax( parameters: TupleTypeElementListSyntax { for param in params { TupleTypeElementSyntax(type: param) } }, effectSpecifiers: effectSpecifiers, returnClause: signature.returnClause ?? .init(type: IdentifierTypeSyntax(name: NS.Void)) ) } func containsGenericType(in functionParameter: FunctionParameterSyntax) -> Bool { let genericParameters = genericParameterClause?.parameters ?? [] guard !genericParameters.isEmpty else { return false } let generics = genericParameters.map(\.name.trimmedDescription) return nil != TokenFinder.find(in: functionParameter.type) { guard case .identifier(let name) = $0.tokenKind else { return false } return generics.contains(name) } } } #if canImport(SwiftSyntax600) extension FunctionDeclSyntax { var errorType: TypeSyntax? { signature.effectSpecifiers?.throwsClause?.type } } #endif ================================================ FILE: Sources/MockableMacro/Extensions/FunctionParameterSyntax+Extensions.swift ================================================ // // FunctionParameterSyntax+Extensions.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 11. 20.. // import SwiftSyntax import SwiftSyntaxMacros enum FunctionParameterTypeResolveRole { /// Resolves a type that can be used for property type bindings, removes attributes. case binding /// Resolves a type that can be used as a function or closure parameter, keeps attributes. case parameter } extension FunctionParameterSyntax { func resolvedType(for role: FunctionParameterTypeResolveRole = .binding) -> TypeSyntax { guard ellipsis == nil else { return TypeSyntax(ArrayTypeSyntax(element: type)) } guard role != .parameter else { return type } if let attributeType = type.as(AttributedTypeSyntax.self) { return TypeSyntax(attributeType.baseType) } else { return TypeSyntax(type) } } var isInout: Bool { #if canImport(SwiftSyntax600) type.as(AttributedTypeSyntax.self)?.specifiers.contains { specifier in guard case .simpleTypeSpecifier(let simpleSpecifier) = specifier else { return false } return simpleSpecifier.specifier.tokenKind == .keyword(.inout) } ?? false #else type.as(AttributedTypeSyntax.self)?.specifier?.tokenKind == .keyword(.inout) #endif } } ================================================ FILE: Sources/MockableMacro/Extensions/GenericArgumentSyntax+Extensions.swift ================================================ // // GenericArgumentSyntax+Extensions.swift // Mockable // // Created by Scott Hoyt on 10/06/2025. // import SwiftSyntax #if canImport(SwiftSyntax601) // The purpose of this initializer is to silence deprecation warnings by the using the new API when // built against SwiftSyntax >= 601.0.0 extension GenericArgumentSyntax { init(argument: any TypeSyntaxProtocol) { self.init(argument: Argument(argument)) } } #endif ================================================ FILE: Sources/MockableMacro/Extensions/ProtocolDeclSyntax+Extensions.swift ================================================ // // ProtocolDeclSyntax+Extensions.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax extension ProtocolDeclSyntax { var mockName: String { NS.Mock.trimmedDescription + name.trimmedDescription } var mockType: IdentifierTypeSyntax { IdentifierTypeSyntax(name: .identifier(mockName)) } } ================================================ FILE: Sources/MockableMacro/Extensions/String+Extensions.swift ================================================ // // String+Extensions.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 11. 19.. // extension String { var capitalizedFirstLetter: String { let firstLetter = prefix(1).capitalized let remainingLetters = dropFirst() return firstLetter + remainingLetters } } ================================================ FILE: Sources/MockableMacro/Extensions/TokenSyntax+Extensions.swift ================================================ // // TokenSyntax+Extensions.swift // MockableMacro // // Created by Kolos Foltányi on 2025. 05. 06.. // import SwiftSyntax extension TokenSyntax { public var declNameOrVarCallName: Self { let text = trimmed.description if text.hasPrefix("`") && text.hasSuffix("`") { return self } if Keyword.all.contains(text) { return "`\(raw: text)`" } else { return self } } } extension Keyword { fileprivate static let all: [String] = [ "__consuming", "__owned", "__setter_access", "__shared", "_alignment", "_backDeploy", "_borrow", "_borrowing", "_BridgeObject", "_cdecl", "_Class", "_compilerInitialized", "_const", "_consuming", "_documentation", "_dynamicReplacement", "_effects", "_expose", "_forward", "_implements", "_linear", "_local", "_modify", "_move", "_mutating", "_NativeClass", "_NativeRefCountedObject", "_noMetadata", "_nonSendable", "_objcImplementation", "_objcRuntimeName", "_opaqueReturnTypeOf", "_optimize", "_originallyDefinedIn", "_PackageDescription", "_private", "_projectedValueProperty", "_read", "_RefCountedObject", "_semantics", "_specialize", "_spi", "_spi_available", "_swift_native_objc_runtime_base", "_Trivial", "_TrivialAtMost", "_TrivialStride", "_typeEraser", "_unavailableFromAsync", "_underlyingVersion", "_UnknownLayout", "_version", "accesses", "actor", "addressWithNativeOwner", "addressWithOwner", "any", "Any", "as", "assignment", "associatedtype", "associativity", "async", "attached", "autoclosure", "availability", "available", "await", "backDeployed", "before", "block", "borrowing", "break", "canImport", "case", "catch", "class", "compiler", "consume", "copy", "consuming", "continue", "convenience", "convention", "cType", "default", "defer", "deinit", "dependsOn", "deprecated", "derivative", "didSet", "differentiable", "distributed", "do", "dynamic", "each", "else", "enum", "escaping", "exclusivity", "exported", "extension", "fallthrough", "false", "file", "fileprivate", "final", "for", "discard", "forward", "func", "freestanding", "get", "guard", "higherThan", "if", "import", "in", "indirect", "infix", "init", "initializes", "inline", "inout", "internal", "introduced", "is", "isolated", "kind", "lazy", "left", "let", "line", "linear", "lowerThan", "macro", "message", "metadata", "module", "mutableAddressWithNativeOwner", "mutableAddressWithOwner", "mutating", "nil", "noasync", "noDerivative", "noescape", "none", "nonisolated", "nonmutating", "objc", "obsoleted", "of", "open", "operator", "optional", "override", "package", "postfix", "precedencegroup", "preconcurrency", "prefix", "private", "Protocol", "protocol", "public", "reasync", "renamed", "repeat", "required", "_resultDependsOn", "_resultDependsOnSelf", "rethrows", "retroactive", "return", "reverse", "right", "safe", "scoped", "self", "sending", "Self", "Sendable", "set", "some", "sourceFile", "spi", "spiModule", "static", "struct", "subscript", "super", "swift", "switch", "target", "then", "throw", "throws", "transpose", "true", "try", "Type", "typealias", "unavailable", "unchecked", "unowned", "unsafe", "unsafeAddress", "unsafeMutableAddress", "var", "visibility", "weak", "where", "while", "willSet", "witness_method", "wrt", "yield" ] } ================================================ FILE: Sources/MockableMacro/Extensions/VariableDeclSyntax+Extensions.swift ================================================ // // VariableDeclSyntax+Extensions.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 10.. // import SwiftSyntax extension VariableDeclSyntax { var name: TokenSyntax { get throws { .identifier(try binding.pattern.trimmedDescription) } } var isComputed: Bool { setAccessor == nil } var isThrowing: Bool { get throws { #if canImport(SwiftSyntax600) try getAccessor.effectSpecifiers?.throwsClause?.throwsSpecifier != nil #else try getAccessor.effectSpecifiers?.throwsSpecifier != nil #endif } } var resolvedType: TypeSyntax { get throws { let type = try type if let type = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { return type.wrappedType } return type } } var type: TypeSyntax { get throws { guard let typeAnnotation = try binding.typeAnnotation else { throw MockableMacroError.invalidVariableRequirement } return typeAnnotation.type.trimmed } } var getAccessor: AccessorDeclSyntax { get throws { let getAccessor = try accessors.first { $0.accessorSpecifier.tokenKind == .keyword(.get) } guard let getAccessor else { throw MockableMacroError.invalidVariableRequirement } return getAccessor } } var setAccessor: AccessorDeclSyntax? { try? accessors.first { $0.accessorSpecifier.tokenKind == .keyword(.set) } } var closureType: FunctionTypeSyntax { get throws { #if canImport(SwiftSyntax600) let effectSpecifiers = TypeEffectSpecifiersSyntax( throwsClause: try isThrowing ? .init(throwsSpecifier: .keyword(.throws)) : nil ) #else let effectSpecifiers = TypeEffectSpecifiersSyntax( throwsSpecifier: try isThrowing ? .keyword(.throws) : nil ) #endif return FunctionTypeSyntax( parameters: TupleTypeElementListSyntax(), effectSpecifiers: effectSpecifiers, returnClause: .init(type: try resolvedType) ) } } var binding: PatternBindingSyntax { get throws { guard let binding = bindings.first else { throw MockableMacroError.invalidVariableRequirement } return binding } } } // MARK: - Helpers extension VariableDeclSyntax { private var accessors: AccessorDeclListSyntax { get throws { guard let accessorBlock = try binding.accessorBlock, case .accessors(let accessorList) = accessorBlock.accessors else { throw MockableMacroError.invalidVariableRequirement } return accessorList } } } #if canImport(SwiftSyntax600) extension VariableDeclSyntax { var errorType: TypeSyntax? { get throws { try getAccessor.effectSpecifiers?.throwsClause?.type } } } #endif ================================================ FILE: Sources/MockableMacro/Factory/Buildable/Buildable.swift ================================================ // // Buildable.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax /// An enum representing the different builder structs to generate. enum BuilderKind: CaseIterable { case `return` case action case verify /// The builder struct declaration's name. var name: TokenSyntax { switch self { case .return: NS.ReturnBuilder case .action: NS.ActionBuilder case .verify: NS.VerifyBuilder } } /// The builder struct declaration's type. var type: IdentifierTypeSyntax { IdentifierTypeSyntax(name: name) } } /// A protocol to associate builder functions with individual requirements. /// /// Used to generate members of builder struct declarations in builder factory. protocol Buildable { /// Returns the specified builder implementation. /// /// - Parameter kind: The kind of builder function to generate. /// - Parameter modifiers: Declaration modifiers to add to the builder. /// - Parameter mockType: The enclosing mock service's type. /// - Returns: A function or variable declaration that mirrors a protocol requirement /// with its syntax. The parameters of the generated declaration are wrapped in the `Parameter` /// wrapper, and it returns the corresponding builder object. func builder( of kind: BuilderKind, with modifiers: DeclModifierListSyntax, using mockType: IdentifierTypeSyntax ) throws -> DeclSyntax } ================================================ FILE: Sources/MockableMacro/Factory/Buildable/Function+Buildable.swift ================================================ // // Function+Buildable.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax // MARK: - FunctionRequirement + Buildable extension FunctionRequirement: Buildable { func builder( of kind: BuilderKind, with modifiers: DeclModifierListSyntax, using mockType: IdentifierTypeSyntax ) throws -> DeclSyntax { let decl = FunctionDeclSyntax( attributes: syntax.attributes .filter(allowedNames: [NS.available.text]) .trimmed.with(\.trailingTrivia, .newline), modifiers: DeclModifierListSyntax { modifiers DeclModifierSyntax(name: .keyword(.nonisolated)) }, name: syntax.name.trimmed, genericParameterClause: genericParameterClause(for: kind), signature: signature(for: kind, using: mockType), genericWhereClause: genericWhereClause(for: kind), body: try body(for: kind) ) return DeclSyntax(decl) } } // MARK: - Helpers extension FunctionRequirement { private func genericParameterClause(for kind: BuilderKind) -> GenericParameterClauseSyntax? { switch kind { case .return: syntax.genericParameterClause case .action: filteredGenericParameterClause case .verify: filteredGenericParameterClause } } private func genericWhereClause(for kind: BuilderKind) -> GenericWhereClauseSyntax? { switch kind { case .return: syntax.genericWhereClause case .action: filteredGenericWhereClause case .verify: filteredGenericWhereClause } } private func signature( for kind: BuilderKind, using mockType: IdentifierTypeSyntax ) -> FunctionSignatureSyntax { FunctionSignatureSyntax( parameterClause: parameterClause, returnClause: returnClause(for: kind, using: mockType) ) } private var parameterClause: FunctionParameterClauseSyntax { let parameters = FunctionParameterListSyntax { for parameter in syntax.signature.parameterClause.parameters { FunctionParameterSyntax( firstName: parameter.firstName, secondName: parameter.secondName, type: IdentifierTypeSyntax(name: NS.Parameter(parameter.resolvedType().description)) ) } } return FunctionParameterClauseSyntax(parameters: parameters) } private func returnClause( for kind: BuilderKind, using mockType: IdentifierTypeSyntax ) -> ReturnClauseSyntax { let name = syntax.isThrowing ? NS.ThrowingFunction(kind) : NS.Function(kind) let arguments = GenericArgumentListSyntax { GenericArgumentSyntax(argument: mockType) GenericArgumentSyntax(argument: kind.type) if let returnType = functionReturnType(for: kind) { returnType } if let errorType = errorType(for: kind) { errorType } if let produceType = functionProduceType(for: kind) { produceType } } return ReturnClauseSyntax( type: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: name, genericArgumentClause: .init(arguments: arguments) ) ) } private func errorType(for kind: BuilderKind) -> GenericArgumentSyntax? { guard syntax.isThrowing && kind == .return else { return nil } #if canImport(SwiftSyntax600) guard let errorType = syntax.errorType else { return GenericArgumentSyntax(argument: defaultErrorType) } return GenericArgumentSyntax(argument: errorType.trimmed) #else return GenericArgumentSyntax(argument: defaultErrorType) #endif } private var defaultErrorType: some TypeSyntaxProtocol { SomeOrAnyTypeSyntax( someOrAnySpecifier: .keyword(.any), constraint: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Swift), name: NS.Error ) ) } private func functionReturnType(for kind: BuilderKind) -> GenericArgumentSyntax? { guard kind == .return else { return nil } guard let returnClause = syntax.signature.returnClause else { let voidType = IdentifierTypeSyntax(name: NS.Void) return GenericArgumentSyntax(argument: voidType) } return GenericArgumentSyntax(argument: returnClause.type) } private func functionProduceType(for kind: BuilderKind) -> GenericArgumentSyntax? { guard kind == .return else { return nil } return GenericArgumentSyntax(argument: syntax.closureType) } private func body(for kind: BuilderKind) throws -> CodeBlockSyntax { let arguments = try LabeledExprListSyntax { LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: NS.mocker) ) LabeledExprSyntax( label: NS.kind, colon: .colonToken(), expression: try caseSpecifier(wrapParams: false) ) } let statements = CodeBlockItemListSyntax { FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax(name: NS.initializer), leftParen: .leftParenToken(), arguments: arguments, rightParen: .rightParenToken() ) } return .init(statements: statements) } /// Returns the function's generic parameter clause with return only generics filtered out. /// If a generic parameter is only used in the return clause of a function, it will not /// be part of the returned generic parameter clause. private var filteredGenericParameterClause: GenericParameterClauseSyntax? { guard let generics = syntax.genericParameterClause else { return nil } var parameters = generics.parameters.filter { generic in hasParameter(containing: generic.name.trimmedDescription) } if let lastIndex = parameters.indices.last { parameters[lastIndex] = parameters[lastIndex].with(\.trailingComma, nil) } return parameters.isEmpty ? nil : .init(parameters: parameters) } /// Returns the function's generic where clause with return only generics filtered out. /// If a generic parameter is only used in the return clause of a function, it will not /// be part of the returned generic where clause. private var filteredGenericWhereClause: GenericWhereClauseSyntax? { guard let generics = syntax.genericWhereClause else { return nil } var requirements = generics.requirements.filter { requirement in switch requirement.requirement { case .conformanceRequirement(let conformance): return hasParameter(containing: conformance.leftType.trimmedDescription) case .sameTypeRequirement(let sameType): return hasParameter(containing: sameType.leftType.trimmedDescription) default: return false } } if let lastIndex = requirements.indices.last { var last = requirements[lastIndex] last.trailingComma = nil requirements[lastIndex] = last } let whereClause = GenericWhereClauseSyntax(requirements: requirements) .with(\.trailingTrivia, .space) return requirements.isEmpty ? nil : whereClause } private func hasParameter(containing identifier: String) -> Bool { nil != TokenFinder.find(in: syntax.signature.parameterClause) { $0.tokenKind == .identifier(identifier) } } } ================================================ FILE: Sources/MockableMacro/Factory/Buildable/Variable+Buildable.swift ================================================ // // Variable+Buildable.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax // MARK: - VariableRequirement + Buildable extension VariableRequirement: Buildable { func builder( of kind: BuilderKind, with modifiers: DeclModifierListSyntax, using mockType: IdentifierTypeSyntax ) throws -> DeclSyntax { if syntax.isComputed || kind == .return { return try variableDeclaration(of: kind, with: modifiers, using: mockType) } else { return try functionDeclaration(of: kind, with: modifiers, using: mockType) } } } // MARK: - Helpers extension VariableRequirement { func variableDeclaration( of kind: BuilderKind, with modifiers: DeclModifierListSyntax, using mockType: IdentifierTypeSyntax ) throws -> DeclSyntax { let variableDecl = VariableDeclSyntax( attributes: syntax.attributes .filter(allowedNames: [NS.available.text]) .trimmed.with(\.trailingTrivia, .newline), modifiers: DeclModifierListSyntax { modifiers DeclModifierSyntax(name: .keyword(.nonisolated)) }, bindingSpecifier: .keyword(.var), bindings: try PatternBindingListSyntax { PatternBindingSyntax( pattern: try syntax.binding.pattern, typeAnnotation: TypeAnnotationSyntax( type: try returnType(for: kind, using: mockType) ), accessorBlock: AccessorBlockSyntax( accessors: .getter(try body(for: kind)) ) ) } ) return DeclSyntax(variableDecl) } private func functionDeclaration( of kind: BuilderKind, with modifiers: DeclModifierListSyntax, using mockType: IdentifierTypeSyntax ) throws -> DeclSyntax { let functionDecl = FunctionDeclSyntax( attributes: syntax.attributes.trimmed.with(\.trailingTrivia, .newline), modifiers: modifiers, name: try syntax.name, signature: try signature(for: kind, using: mockType), body: .init(statements: try body(for: kind)) ) return DeclSyntax(functionDecl) } private func signature( for kind: BuilderKind, using mockType: IdentifierTypeSyntax ) throws -> FunctionSignatureSyntax { let parameters = try FunctionParameterListSyntax { FunctionParameterSyntax( firstName: NS.newValue, type: IdentifierTypeSyntax( name: NS.Parameter(try syntax.resolvedType.trimmedDescription) ), defaultValue: InitializerClauseSyntax( value: MemberAccessExprSyntax(name: NS.any) ) ) } return FunctionSignatureSyntax( parameterClause: FunctionParameterClauseSyntax(parameters: parameters), returnClause: ReturnClauseSyntax( type: try returnType(for: kind, using: mockType) ) ) } private func returnType( for kind: BuilderKind, using mockType: IdentifierTypeSyntax ) throws -> MemberTypeSyntax { let name = if syntax.isComputed { try syntax.isThrowing ? NS.ThrowingFunction(kind) : NS.Function(kind) } else { try syntax.isThrowing ? NS.ThrowingProperty(kind) : NS.Property(kind) } let arguments = try GenericArgumentListSyntax { GenericArgumentSyntax(argument: mockType) GenericArgumentSyntax(argument: kind.type) if let returnType = try variableReturnType(for: kind) { returnType } if let errorType = try errorType(for: kind) { errorType } if let produceType = try variableProduceType(for: kind) { produceType } } return MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: name, genericArgumentClause: .init(arguments: arguments) ) } private func errorType(for kind: BuilderKind) throws -> GenericArgumentSyntax? { guard try syntax.isThrowing && syntax.isComputed && kind == .return else { return nil } #if canImport(SwiftSyntax600) guard let errorType = try syntax.errorType else { return GenericArgumentSyntax(argument: defaultErrorType) } return GenericArgumentSyntax(argument: errorType.trimmed) #else return GenericArgumentSyntax(argument: defaultErrorType) #endif } private var defaultErrorType: some TypeSyntaxProtocol { SomeOrAnyTypeSyntax( someOrAnySpecifier: .keyword(.any), constraint: IdentifierTypeSyntax(name: NS.Error) ) } private func variableReturnType(for kind: BuilderKind) throws -> GenericArgumentSyntax? { guard kind == .return else { return nil } return GenericArgumentSyntax(argument: try syntax.resolvedType) } private func variableProduceType(for kind: BuilderKind) throws -> GenericArgumentSyntax? { guard kind == .return, syntax.isComputed else { return nil } return GenericArgumentSyntax(argument: try syntax.closureType) } private func body(for kind: BuilderKind) throws -> CodeBlockItemListSyntax { let arguments = try LabeledExprListSyntax { LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: NS.mocker) ) LabeledExprSyntax( label: NS.kind, colon: .colonToken(), expression: try caseSpecifier(wrapParams: false) ) if kind != .return, let setterSpecifier = try setterCaseSpecifier(wrapParams: false) { LabeledExprSyntax( label: NS.setKind, colon: .colonToken(), expression: setterSpecifier ) } } return CodeBlockItemListSyntax { FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax(name: NS._init), leftParen: .leftParenToken(), arguments: arguments, rightParen: .rightParenToken() ) } } } ================================================ FILE: Sources/MockableMacro/Factory/BuilderFactory.swift ================================================ // // BuilderFactory.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax /// Factory to generate builder struct declarations. /// /// Creates a member block item list that includes `ReturnBuilder`, /// `ActionBuilder` and `VerifyBuilder` struct declarations. enum BuilderFactory: Factory { static func build(from requirements: Requirements) throws -> MemberBlockItemListSyntax { try MemberBlockItemListSyntax { for builder in BuilderKind.allCases { try builderDeclaration(for: builder, requirements) } } } } // MARK: - Helpers extension BuilderFactory { private static func builderDeclaration( for kind: BuilderKind, _ requirements: Requirements ) throws -> some DeclSyntaxProtocol { StructDeclSyntax( modifiers: requirements.modifiers, name: kind.name, inheritanceClause: InheritanceClauseSyntax( inheritedTypes: [ InheritedTypeSyntax( type: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: NS.Builder ) ) ] ), memberBlock: MemberBlockSyntax(members: try members(kind, requirements)) ) } private static func members(_ kind: BuilderKind, _ requirements: Requirements) throws -> MemberBlockItemListSyntax { try MemberBlockItemListSyntax { mockerDeclaration(requirements) initializerDeclaration(kind, requirements) for variable in requirements.variables { MemberBlockItemSyntax( decl: try variable.builder( of: kind, with: requirements.modifiers, using: requirements.syntax.mockType ) ) } for function in requirements.functions { MemberBlockItemSyntax( decl: try function.builder( of: kind, with: requirements.modifiers, using: requirements.syntax.mockType ) ) } } } private static func mockerDeclaration(_ requirements: Requirements) -> VariableDeclSyntax { VariableDeclSyntax( modifiers: [DeclModifierSyntax(name: .keyword(.private))], bindingSpecifier: .keyword(.let), bindingsBuilder: { PatternBindingSyntax( pattern: IdentifierPatternSyntax(identifier: NS.mocker), typeAnnotation: TypeAnnotationSyntax(type: IdentifierTypeSyntax(name: NS.Mocker)) ) } ) } private static func initializerDeclaration( _ kind: BuilderKind, _ requirements: Requirements ) -> InitializerDeclSyntax { InitializerDeclSyntax( modifiers: DeclModifierListSyntax { requirements.modifiers DeclModifierSyntax(name: .keyword(.nonisolated)) }, signature: initializerSignature(kind, requirements) ) { InfixOperatorExprSyntax( leftOperand: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: .keyword(.self)), name: NS.mocker ), operator: AssignmentExprSyntax(), rightOperand: DeclReferenceExprSyntax(baseName: NS.mocker) ) } } private static func initializerSignature( _ kind: BuilderKind, _ requirements: Requirements ) -> FunctionSignatureSyntax { FunctionSignatureSyntax( parameterClause: FunctionParameterClauseSyntax { FunctionParameterSyntax( firstName: NS.mocker, type: IdentifierTypeSyntax(name: NS.Mocker) ) } ) } } ================================================ FILE: Sources/MockableMacro/Factory/Caseable/Caseable.swift ================================================ // // Caseable.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 12. 07.. // import SwiftSyntax /// Describes requirements that are representable by a `Member` enum case. /// /// Requirements conforming to `Caseable` provide their case declarations and a /// utility that helps creating member access expressions for a given case. protocol Caseable { /// Returns the member enum represenatation of a syntax. /// /// For example a member like: /// ``` /// func foo(param: String) -> Int /// ``` /// would return the following case declaration: /// ``` /// case foo(param: Parameter) /// ``` var caseDeclarations: [EnumCaseDeclSyntax] { get throws } /// Returns the initializer block that creates the enum case representation. /// /// For example a member like: /// ``` /// func foo(param: String) -> Int /// ``` /// would return the following initializer declaration if wrapParams is true: /// ``` /// .m1_foo(param: .value(param)) /// ``` /// and: /// ``` /// .m1_foo(param: param) /// ``` /// if wrapParams is false. func caseSpecifier(wrapParams: Bool) throws -> ExprSyntax /// Returns the initializer block that creates the enum case representation. /// /// For example a member like: /// ``` /// var prop: Int { get set } /// ``` /// would return the following initializer declaration if wrapParams is true: /// ``` /// .m1_set_name(newValue: .value(newValue)) /// ``` /// and: /// ``` /// .m1_set_name(newValue: newValue) /// ``` /// if wrapParams is false. func setterCaseSpecifier(wrapParams: Bool) throws -> ExprSyntax? } ================================================ FILE: Sources/MockableMacro/Factory/Caseable/Function+Caseable.swift ================================================ // // Function+Caseable.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 12. 07.. // import SwiftSyntax import Foundation // MARK: - FunctionRequirement + Caseable extension FunctionRequirement: Caseable { var caseDeclarations: [EnumCaseDeclSyntax] { get throws { let enumCase = EnumCaseElementSyntax( name: caseExpression.declName.baseName, parameterClause: enumParameterClause ) let elements: EnumCaseElementListSyntax = .init(arrayLiteral: enumCase) return [EnumCaseDeclSyntax(elements: elements)] } } func caseSpecifier(wrapParams: Bool) throws -> ExprSyntax { guard let parameters = parameters(wrap: wrapParams) else { return ExprSyntax(caseExpression) } let functionCallExpr = FunctionCallExprSyntax( calledExpression: caseExpression, leftParen: .leftParenToken(), arguments: parameters, rightParen: .rightParenToken() ) return ExprSyntax(functionCallExpr) } func setterCaseSpecifier(wrapParams: Bool) -> ExprSyntax? { nil } } // MARK: - Helpers extension FunctionRequirement { private var caseExpression: MemberAccessExprSyntax { let indexPrefix = String(index + 1) let specialEnclosingCharacters: CharacterSet = ["`"] let caseName = syntax.name.trimmedDescription.trimmingCharacters(in: specialEnclosingCharacters) return MemberAccessExprSyntax( name: .identifier("m\(indexPrefix)_\(caseName)") ) } private func parameters(wrap: Bool) -> LabeledExprListSyntax? { guard let parameterClause = enumParameterClause else { return nil } let functionParameters = syntax.signature.parameterClause.parameters let zippedParameters = zip(parameterClause.parameters, functionParameters) let enumeratedParameters = zippedParameters.enumerated() let lastIndex = enumeratedParameters.map(\.offset).last return LabeledExprListSyntax { for (index, element) in enumeratedParameters { let (enumParameter, functionParameter) = element let hasComma = index != lastIndex && lastIndex != 0 let hasColon = enumParameter.firstName != nil LabeledExprSyntax( label: enumParameter.firstName, colon: hasColon ? .colonToken() : nil, expression: parameterExpression( for: functionParameter, wrapParams: wrap ), trailingComma: hasComma ? .commaToken() : nil ) } } } private var enumParameterClause: EnumCaseParameterClauseSyntax? { guard !syntax.signature.parameterClause.parameters.isEmpty else { return nil } let enumParameters: EnumCaseParameterListSyntax = .init { for parameter in syntax.signature.parameterClause.parameters { let firstName = parameter.firstName.tokenKind == .wildcard ? nil : parameter.firstName.trimmed EnumCaseParameterSyntax( firstName: firstName, colon: firstName == nil ? nil : .colonToken(), type: wrappedType(for: parameter) ) } } return .init(parameters: enumParameters) } private func wrappedType(for parameter: FunctionParameterSyntax) -> IdentifierTypeSyntax { let type = parameter.resolvedType().description let isGeneric = syntax.containsGenericType(in: parameter) let identifier = isGeneric ? NS.GenericValue : type return IdentifierTypeSyntax(name: NS.Parameter(identifier)) } private func parameterExpression(for functionParameter: FunctionParameterSyntax, wrapParams: Bool) -> ExprSyntax { if wrapParams { wrappedParameterExpression(for: functionParameter) } else { parameterExpression(for: functionParameter) } } private func wrappedParameterExpression(for functionParameter: FunctionParameterSyntax) -> ExprSyntax { let isGeneric = syntax.containsGenericType(in: functionParameter) let functionParamName = (functionParameter.secondName ?? functionParameter.firstName).declNameOrVarCallName let functionCallExpr = FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax(name: isGeneric ? NS.generic : NS.value), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax(expression: DeclReferenceExprSyntax(baseName: functionParamName)) }, rightParen: .rightParenToken() ) return ExprSyntax(functionCallExpr) } private func parameterExpression(for functionParameter: FunctionParameterSyntax) -> ExprSyntax { let isGeneric = syntax.containsGenericType(in: functionParameter) let functionParamName = (functionParameter.secondName ?? functionParameter.firstName).declNameOrVarCallName if isGeneric { return ExprSyntax( FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: functionParamName), name: NS.eraseToGenericValue ), leftParen: .leftParenToken(), arguments: [], rightParen: .rightParenToken() ) ) } else { return ExprSyntax( DeclReferenceExprSyntax(baseName: functionParamName) ) } } } ================================================ FILE: Sources/MockableMacro/Factory/Caseable/Variable+Caseable.swift ================================================ // // Variable+Caseable.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 10.. // import SwiftSyntax // MARK: - VariableRequirement + Caseable extension VariableRequirement: Caseable { var caseDeclarations: [EnumCaseDeclSyntax] { get throws { [try getterEnumCaseDeclaration, try setterEnumCaseDeclaration].compactMap { $0 } } } func caseSpecifier(wrapParams: Bool) throws -> ExprSyntax { ExprSyntax(MemberAccessExprSyntax(name: try getterEnumName)) } func setterCaseSpecifier(wrapParams: Bool) throws -> ExprSyntax? { guard let setterName = try setterEnumName else { return nil } let functionCallExpr = FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax(name: setterName), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax( label: NS.newValue, colon: .colonToken(), expression: wrapParams ? wrappedSetterParameters : setterParameters ) }, rightParen: .rightParenToken() ) return ExprSyntax(functionCallExpr) } } // MARK: - Helpers extension VariableRequirement { private func enumName(prefix: String = "") throws -> TokenSyntax { .identifier("m\(String(index + 1))_\(prefix)\(try syntax.name)") } private var getterEnumName: TokenSyntax { get throws { syntax.isComputed ? try enumName() : try enumName(prefix: NS.get_) } } private var setterEnumName: TokenSyntax? { get throws { syntax.isComputed ? nil : try enumName(prefix: NS.set_) } } private var setterParameters: ExprSyntax { ExprSyntax(DeclReferenceExprSyntax(baseName: NS.newValue)) } private var wrappedSetterParameters: ExprSyntax { let functionCallExpr = FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax(name: NS.value), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax(expression: DeclReferenceExprSyntax(baseName: NS.newValue)) }, rightParen: .rightParenToken() ) return ExprSyntax(functionCallExpr) } private var getterEnumCaseDeclaration: EnumCaseDeclSyntax { get throws { let getterCase = EnumCaseElementSyntax(name: try getterEnumName) let elements: EnumCaseElementListSyntax = .init(arrayLiteral: getterCase) return EnumCaseDeclSyntax(elements: elements) } } private var setterEnumCaseDeclaration: EnumCaseDeclSyntax? { get throws { guard let setterName = try setterEnumName else { return nil } let setterCase = EnumCaseElementSyntax( name: setterName, parameterClause: try setterEnumParameterClause ) let elements: EnumCaseElementListSyntax = .init(arrayLiteral: setterCase) return EnumCaseDeclSyntax(elements: elements) } } private var wrappedType: IdentifierTypeSyntax { get throws { let baseType = try syntax.resolvedType.trimmedDescription return IdentifierTypeSyntax(name: NS.Parameter(baseType)) } } private var setterEnumParameterClause: EnumCaseParameterClauseSyntax? { get throws { guard !syntax.isComputed else { return nil } let parameter = EnumCaseParameterSyntax( firstName: NS.newValue, colon: .colonToken(), type: try wrappedType ) return EnumCaseParameterClauseSyntax( parameters: EnumCaseParameterListSyntax([parameter]) ) } } } ================================================ FILE: Sources/MockableMacro/Factory/ConformanceFactory.swift ================================================ // // ConformanceFactory.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax /// Factory to generate mock conformances of requirements. /// /// Returns a member block item list that includes a mock implementation for every requirement. enum ConformanceFactory: Factory { static func build(from requirements: Requirements) throws -> MemberBlockItemListSyntax { try MemberBlockItemListSyntax { try inits(requirements) try functions(requirements) try variables(requirements) } } } // MARK: - Helpers extension ConformanceFactory { private static func variables(_ requirements: Requirements) throws -> MemberBlockItemListSyntax { try MemberBlockItemListSyntax { for variable in requirements.variables { MemberBlockItemSyntax( decl: try variable.implement(with: requirements.modifiers) ) } } } private static func functions(_ requirements: Requirements) throws -> MemberBlockItemListSyntax { try MemberBlockItemListSyntax { for function in requirements.functions { MemberBlockItemSyntax( decl: try function.implement(with: requirements.modifiers) ) } } } private static func inits(_ requirements: Requirements) throws -> MemberBlockItemListSyntax { try MemberBlockItemListSyntax { for initializer in requirements.initializers { MemberBlockItemSyntax( decl: try initializer.implement(with: requirements.modifiers) ) } } } } ================================================ FILE: Sources/MockableMacro/Factory/EnumFactory.swift ================================================ // // EnumFactory.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 12.. // import SwiftSyntax /// Factory to generate the `Member` enum. /// /// Generates an enum that represents the given requirements as an enum case. /// The enum declaration also contains the implementation of the `match(_:)` function. enum EnumFactory: Factory { static func build(from requirements: Requirements) throws -> IfConfigDeclSyntax { SwiftVersionHelper.condition( minimumVersion: .swift_6_1, gte: .decls( try MemberBlockItemListSyntax { try enumDeclaration(requirements, nonisolated: true) } ), else: .decls( try MemberBlockItemListSyntax { try enumDeclaration(requirements, nonisolated: false) } ) ) } } // MARK: - Helpers extension EnumFactory { private static func enumDeclaration( _ requirements: Requirements, nonisolated: Bool ) throws -> EnumDeclSyntax { EnumDeclSyntax( modifiers: DeclModifierListSyntax { requirements.modifiers if nonisolated { DeclModifierSyntax(name: .keyword(.nonisolated)) } }, name: NS.Member, inheritanceClause: inheritanceClause, memberBlock: MemberBlockSyntax(members: try members(requirements)) ) } private static var inheritanceClause: InheritanceClauseSyntax { InheritanceClauseSyntax { InheritedTypeSyntax( type: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: NS.Matchable ) ) InheritedTypeSyntax( type: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: NS.CaseIdentifiable ) ) InheritedTypeSyntax( type: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Swift), name: NS.Sendable ) ) } } private static func members(_ requirements: Requirements) throws -> MemberBlockItemListSyntax { try MemberBlockItemListSyntax { for enumCase in try enumCaseDeclarations(requirements) { enumCase } try matcherFunction(requirements) } } private static func enumCaseDeclarations(_ requirements: Requirements) throws -> [EnumCaseDeclSyntax] { var cases = [EnumCaseDeclSyntax]() for variable in requirements.variables { cases += try variable.caseDeclarations } for function in requirements.functions { cases += try function.caseDeclarations } return cases } private static func matcherFunction(_ requirements: Requirements) throws -> MemberBlockItemSyntax { let param = FunctionParameterSyntax( firstName: .wildcardToken(), secondName: NS.other, type: IdentifierTypeSyntax(name: NS.Member) ) let returnType = IdentifierTypeSyntax(name: NS.Bool) let signature = FunctionSignatureSyntax( parameterClause: .init(parameters: [param]), returnClause: .init(type: returnType) ) let statement = try CodeBlockItemListSyntax { try matcherSwitch(requirements) } let decl = FunctionDeclSyntax( modifiers: DeclModifierListSyntax { requirements.modifiers DeclModifierSyntax(name: .keyword(.nonisolated)) }, name: NS.match, signature: signature, body: .init(statements: statement) ) return .init(decl: decl) } private static func matcherSwitch(_ requirements: Requirements) throws -> ExprSyntax { let subject = TupleExprSyntax { LabeledExprListSyntax { LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: .keyword(.self)) ) LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: NS.other) ) } } let cases = try enumCaseDeclarations(requirements) let switchSyntax = try SwitchExprSyntax(subject: subject) { try SwitchCaseListSyntax { for caseDeclaration in cases { try matcherCase(for: caseDeclaration) } if cases.count > 1 { defaultCase } } } return ExprSyntax(switchSyntax) } private static var defaultCase: SwitchCaseSyntax { let label = SwitchDefaultLabelSyntax() let returnStmt = ReturnStmtSyntax( expression: BooleanLiteralExprSyntax(false) ) return SwitchCaseSyntax( label: .default(label), statements: CodeBlockItemListSyntax { returnStmt } ) } private static func matcherCase(for enumDeclaration: EnumCaseDeclSyntax) throws -> SwitchCaseSyntax { guard let enumCase = enumDeclaration.elements.first else { throw MockableMacroError.invalidDerivedEnumCase } let switchCaseLabel = try SwitchCaseLabelSyntax { SwitchCaseItemSyntax( pattern: ExpressionPatternSyntax( expression: try matcherCaseExpr(for: enumCase) ) ) } let statements = CodeBlockItemListSyntax { matcherCaseBody(for: enumCase) } return SwitchCaseSyntax( label: .case(switchCaseLabel), statements: statements ) } private static func matcherCaseExpr(for enumCase: EnumCaseElementSyntax) throws -> TupleExprSyntax { let parameters = enumCase.parameterClause?.parameters ?? [] let leftCase = matcherCaseSide(enumCase.name, parameters, prefix: NS.left) let rightCase = matcherCaseSide(enumCase.name, parameters, prefix: NS.right) return TupleExprSyntax { LabeledExprListSyntax { LabeledExprSyntax(expression: leftCase) LabeledExprSyntax(expression: rightCase) } } } private static func matcherCaseSide( _ name: TokenSyntax, _ parameters: EnumCaseParameterListSyntax, prefix: TokenSyntax ) -> ExprSyntax { let memberAccess = MemberAccessExprSyntax( declName: DeclReferenceExprSyntax(baseName: name) ) guard !parameters.isEmpty else { return ExprSyntax(memberAccess) } let functionCallExpr = FunctionCallExprSyntax( calledExpression: memberAccess, leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { for (index, parameter) in parameters.enumerated() { LabeledExprSyntax( label: parameter.firstName, colon: parameter.firstName != nil ? .colonToken() : nil, expression: parameterExpression(for: parameter, at: index, prefix: prefix) ) } }, rightParen: .rightParenToken() ) return ExprSyntax(functionCallExpr) } private static func parameterExpression( for param: EnumCaseParameterSyntax, at index: Int, prefix: TokenSyntax ) -> PatternExprSyntax { return PatternExprSyntax( pattern: ValueBindingPatternSyntax( bindingSpecifier: .keyword(.let), pattern: IdentifierPatternSyntax( identifier: parameterName(param, index: index, prefix: prefix) ) ) ) } private static func parameterName( _ param: EnumCaseParameterSyntax, index: Int, prefix: TokenSyntax ) -> TokenSyntax { let patternId = param.firstName ?? NS.Param(suffix: String(index + 1)) let name = prefix.description + patternId.trimmed.description.capitalizedFirstLetter return .identifier(name) } private static func matcherCaseBody(for enumCase: EnumCaseElementSyntax) -> StmtSyntax { let parameters = enumCase.parameterClause?.parameters ?? [] guard !parameters.isEmpty else { let returnStmt = ReturnStmtSyntax(expression: BooleanLiteralExprSyntax(true)) return StmtSyntax(returnStmt) } let leftNames = parameters.enumerated().map { index, element in parameterName(element, index: index, prefix: NS.left) } let rightNames = parameters.enumerated().map { index, element in parameterName(element, index: index, prefix: NS.right) } var expression: ExprSyntax! for (leftName, rightName) in zip(leftNames, rightNames) { guard expression != nil else { expression = matchCall(leftName, rightName) continue } let infixExpression = InfixOperatorExprSyntax( leftOperand: expression, operator: BinaryOperatorExprSyntax(operator: .binaryOperator(NS._andSign)), rightOperand: matchCall(leftName, rightName) ) expression = ExprSyntax(infixExpression) } let returnStmt = ReturnStmtSyntax(expression: expression) return StmtSyntax(returnStmt) } private static func matchCall(_ leftName: TokenSyntax, _ rightName: TokenSyntax) -> ExprSyntax { let functionCallExpr = FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: leftName), declName: DeclReferenceExprSyntax(baseName: NS.match) ), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: rightName) ) }, rightParen: .rightParenToken() ) return ExprSyntax(functionCallExpr) } } ================================================ FILE: Sources/MockableMacro/Factory/Factory.swift ================================================ // // Factory.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 28.. // import SwiftSyntax /// Defines a generic factory protocol for generating syntax of the mock implementation. /// /// This protocol serves as a blueprint for factory types that transform the protocol requirements /// into a mock implementation. protocol Factory { /// The type of syntax produced by the factory. associatedtype Result: SyntaxProtocol /// Generates a syntax using the given requirements. /// /// - Parameter requirements: Groupped protocol requirements to generate syntax for. /// - Throws: Throws an error if the construction fails, which could be due to invalid /// requirements or other issues that prevent the successful generation of the result. /// - Returns: An instance of the associated `Result` type, representing the generated /// syntax structure. static func build(from requirements: Requirements) throws -> Result } ================================================ FILE: Sources/MockableMacro/Factory/MemberFactory.swift ================================================ // // MemberFactory.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax /// Factory to generate custom members. /// /// Generates custom members (ex.: default init, reset function, etc...) /// for the mock implementation. enum MemberFactory: Factory { static func build(from requirements: Requirements) throws -> MemberBlockItemListSyntax { MemberBlockItemListSyntax { mockerAlias(requirements) mocker(requirements) clause(requirements, name: NS._given, type: NS.ReturnBuilder, message: Messages.givenMessage) clause(requirements, name: NS._when, type: NS.ActionBuilder, message: Messages.whenMessage) clause(requirements, name: NS._verify, type: NS.VerifyBuilder, message: Messages.verifyMessage) reset(requirements) defaultInit(requirements) } } } // MARK: - Helpers extension MemberFactory { private static func defaultInit(_ requirements: Requirements) -> InitializerDeclSyntax { InitializerDeclSyntax( modifiers: requirements.isActor ? requirements.modifiers : memberModifiers(requirements), signature: .init(parameterClause: defaultInitParameters), body: .init { CodeBlockItemSyntax(item: .expr(mockerAssignmentWithPolicy)) } ) } private static func mockerAlias(_ requirements: Requirements) -> TypeAliasDeclSyntax { TypeAliasDeclSyntax( modifiers: requirements.modifiers, name: NS.Mocker, initializer: TypeInitializerClauseSyntax( value: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: NS.Mocker, genericArgumentClause: GenericArgumentClauseSyntax( arguments: GenericArgumentListSyntax { GenericArgumentSyntax(argument: requirements.syntax.mockType) } ) ) ) ) } private static func mocker(_ requirements: Requirements) -> VariableDeclSyntax { VariableDeclSyntax( modifiers: [DeclModifierSyntax(name: .keyword(.private))], bindingSpecifier: .keyword(.let) ) { PatternBindingSyntax( pattern: IdentifierPatternSyntax(identifier: NS.mocker), initializer: InitializerClauseSyntax( value: FunctionCallExprSyntax( calledExpression: DeclReferenceExprSyntax(baseName: NS.Mocker), leftParen: .leftParenToken(), arguments: [], rightParen: .rightParenToken() ) ) ) } } private static func clause( _ requirements: Requirements, name: TokenSyntax, type: TokenSyntax, message: String ) -> VariableDeclSyntax { VariableDeclSyntax( attributes: unavailableAttribute(message: message), modifiers: memberModifiers(requirements), bindingSpecifier: .keyword(.var), bindings: PatternBindingListSyntax { PatternBindingSyntax( pattern: IdentifierPatternSyntax(identifier: name), typeAnnotation: TypeAnnotationSyntax(type: IdentifierTypeSyntax(name: type)), accessorBlock: AccessorBlockSyntax( accessors: .getter(builderInit) ) ) } ) } private static var builderInit: CodeBlockItemListSyntax { CodeBlockItemListSyntax { FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax(name: NS._init), leftParen: .leftParenToken(), arguments: [ LabeledExprSyntax( label: NS.mocker, colon: .colonToken(), expression: DeclReferenceExprSyntax(baseName: NS.mocker) ) ], rightParen: .rightParenToken() ) } } private static func reset(_ requirements: Requirements) -> FunctionDeclSyntax { FunctionDeclSyntax( modifiers: memberModifiers(requirements), name: NS.reset, signature: .init( parameterClause: FunctionParameterClauseSyntax( parameters: [scopesParameter] ) ), body: resetCall ) } private static func memberModifiers(_ requirements: Requirements) -> DeclModifierListSyntax { DeclModifierListSyntax { requirements.modifiers DeclModifierSyntax(name: .keyword(.nonisolated)) } } private static var scopesParameter: FunctionParameterSyntax { FunctionParameterSyntax( firstName: .wildcardToken(), secondName: NS.scopes, type: IdentifierTypeSyntax( name: NS.Set, genericArgumentClause: GenericArgumentClauseSyntax( arguments: GenericArgumentListSyntax { GenericArgumentSyntax( argument: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: NS.MockerScope ) ) } ) ), defaultValue: InitializerClauseSyntax( value: MemberAccessExprSyntax(name: NS.all) ) ) } private static var resetCall: CodeBlockSyntax { CodeBlockSyntax( statements: CodeBlockItemListSyntax { FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: NS.mocker), name: NS.reset ), leftParen: .leftParenToken(), arguments: [ LabeledExprSyntax( label: NS.scopes, colon: .colonToken(), expression: DeclReferenceExprSyntax(baseName: NS.scopes) ) ], rightParen: .rightParenToken() ) } ) } private static func unavailableAttribute(message: String) -> AttributeListSyntax { let arguments = AvailabilityArgumentListSyntax { AvailabilityArgumentSyntax(argument: .token(NS._star)) AvailabilityArgumentSyntax(argument: .token(NS.deprecated)) AvailabilityArgumentSyntax(argument: .availabilityLabeledArgument( AvailabilityLabeledArgumentSyntax( label: NS.message, value: .string(SimpleStringLiteralExprSyntax( openingQuote: .stringQuoteToken(), segments: SimpleStringLiteralSegmentListSyntax { StringSegmentSyntax(content: .identifier(message)) }, closingQuote: .stringQuoteToken() )) ) )) } let attribute = AttributeSyntax( attributeName: IdentifierTypeSyntax(name: NS.available), leftParen: .leftParenToken(), arguments: .availability(arguments), rightParen: .rightParenToken(), trailingTrivia: .newline ) return AttributeListSyntax { attribute } } private static var defaultInitParameters: FunctionParameterClauseSyntax { FunctionParameterClauseSyntax( parameters: FunctionParameterListSyntax { FunctionParameterSyntax( firstName: NS.policy, type: OptionalTypeSyntax( wrappedType: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: NS.MockerPolicy ) ), defaultValue: InitializerClauseSyntax( value: NilLiteralExprSyntax() ) ) } ) } private static var mockerAssignmentWithPolicy: ExprSyntax { let expression = IfExprSyntax( conditions: ConditionElementListSyntax { OptionalBindingConditionSyntax( bindingSpecifier: .keyword(.let), pattern: IdentifierPatternSyntax( identifier: NS.policy ) ) }, body: CodeBlockSyntax( statements: CodeBlockItemListSyntax { InfixOperatorExprSyntax( leftOperand: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: NS.mocker), declName: DeclReferenceExprSyntax(baseName: NS.policy) ), operator: AssignmentExprSyntax(), rightOperand: DeclReferenceExprSyntax(baseName: NS.policy) ) } ) ) return ExprSyntax(expression) } } ================================================ FILE: Sources/MockableMacro/Factory/MockFactory.swift ================================================ // // MockFactory.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 28.. // #if canImport(SwiftSyntax600) || swift(<6) import SwiftSyntax #else @preconcurrency import SwiftSyntax #endif /// Factory to generate the mock service declaration. /// /// Generates a class declaration that defines the mock implementation of the protocol. enum MockFactory: Factory { static func build(from requirements: Requirements) throws -> DeclSyntax { let classDecl = ClassDeclSyntax( leadingTrivia: leadingTrivia(requirements), attributes: try attributes(requirements), modifiers: modifiers(requirements), classKeyword: classKeyword(requirements), name: .identifier(requirements.syntax.mockName), genericParameterClause: genericParameterClause(requirements), inheritanceClause: inheritanceClause(requirements), genericWhereClause: genericWhereClause(requirements), memberBlock: try MemberBlockSyntax { try MemberFactory.build(from: requirements) try ConformanceFactory.build(from: requirements) try EnumFactory.build(from: requirements) try BuilderFactory.build(from: requirements) } ) return DeclSyntax(classDecl) } } // MARK: - Helpers extension MockFactory { private static func leadingTrivia(_ requirements: Requirements) -> Trivia { requirements.syntax.leadingTrivia } private static let inheritedTypeMappings: [String: TokenSyntax] = [ NS.NSObjectProtocol: NS.NSObject ] private static func attributes(_ requirements: Requirements) throws -> AttributeListSyntax { guard requirements.containsGenericExistentials else { return [] } return try AttributeListSyntax { // Runtime support for parametrized protocol types is only available from: try Availability.from(iOS: "16.0", macOS: "13.0", tvOS: "16.0", watchOS: "9.0") } .with(\.trailingTrivia, .newline) } private static func modifiers(_ requirements: Requirements) -> DeclModifierListSyntax { var modifiers = requirements.syntax.modifiers.trimmed if !requirements.isActor { modifiers.append(DeclModifierSyntax(name: .keyword(.final))) } return modifiers } private static func classKeyword(_ requirements: Requirements) -> TokenSyntax { requirements.isActor ? .keyword(.actor) : .keyword(.class) } private static func inheritanceClause(_ requirements: Requirements) -> InheritanceClauseSyntax { InheritanceClauseSyntax { inheritedTypeMappings(requirements) InheritedTypeSyntax(type: IdentifierTypeSyntax( name: requirements.syntax.name.trimmed )) InheritedTypeSyntax( type: MemberTypeSyntax( baseType: IdentifierTypeSyntax(name: NS.Mockable), name: NS.MockableService ) ) } } private static func inheritedTypeMappings(_ requirements: Requirements) -> InheritedTypeListSyntax { guard let inheritanceClause = requirements.syntax.inheritanceClause else { return [] } return InheritedTypeListSyntax { for inheritedType in inheritanceClause.inheritedTypes { if let type = inheritedType.type.as(IdentifierTypeSyntax.self), let mapping = inheritedTypeMappings[type.name.trimmedDescription] { InheritedTypeSyntax(type: IdentifierTypeSyntax(name: mapping)) } } } } private static func getAssociatedTypes(_ requirements: Requirements) -> [AssociatedTypeDeclSyntax] { requirements.syntax.memberBlock.members.compactMap { $0.decl.as(AssociatedTypeDeclSyntax.self) } } private static func genericParameterClause(_ requirements: Requirements) -> GenericParameterClauseSyntax? { let associatedTypes = getAssociatedTypes(requirements) guard !associatedTypes.isEmpty else { return nil } return .init { GenericParameterListSyntax { for name in associatedTypes.map(\.name.trimmed) { GenericParameterSyntax(name: name) } } } } private static func genericWhereClause(_ requirements: Requirements) -> GenericWhereClauseSyntax? { let associatedTypes = getAssociatedTypes(requirements) guard !associatedTypes.isEmpty else { return nil } let inheritances = associatedTypes.filter { $0.inheritanceClause != nil } let whereClauses = associatedTypes.filter { $0.genericWhereClause != nil } guard !inheritances.isEmpty || !whereClauses.isEmpty else { return nil } let requirementList = GenericRequirementListSyntax { if let genericWhereClause = requirements.syntax.genericWhereClause { genericWhereClause.requirements } for type in whereClauses { if let genericWhereClause = type.genericWhereClause { genericWhereClause.requirements } } for type in inheritances { if let inheritanceClause = type.inheritanceClause { for inheritedType in inheritanceClause.inheritedTypes { let requirement = ConformanceRequirementSyntax( leftType: IdentifierTypeSyntax(name: type.name), rightType: inheritedType.type ) GenericRequirementSyntax( requirement: .conformanceRequirement(requirement) ) } } } } return .init(requirements: requirementList) } } ================================================ FILE: Sources/MockableMacro/Factory/Mockable/Function+Mockable.swift ================================================ // // Function+Mockable.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax // MARK: - FunctionRequirement + Mockable extension FunctionRequirement: Mockable { func implement(with modifiers: DeclModifierListSyntax) throws -> DeclSyntax { let decl = FunctionDeclSyntax( attributes: syntax.attributes.trimmed.with(\.trailingTrivia, .newline), modifiers: declarationModifiers(extending: modifiers), funcKeyword: syntax.funcKeyword.trimmed, name: syntax.name.trimmed, genericParameterClause: syntax.genericParameterClause?.trimmed, signature: syntax.signature.trimmed, genericWhereClause: syntax.genericWhereClause?.trimmed, body: .init(statements: try body) ) for parameter in decl.signature.parameterClause.parameters { guard parameter.type.as(FunctionTypeSyntax.self) == nil else { throw MockableMacroError.nonEscapingFunctionParameter } } return DeclSyntax(decl) } } // MARK: - Helpers extension FunctionRequirement { private func declarationModifiers(extending modifiers: DeclModifierListSyntax) -> DeclModifierListSyntax { let filtered = syntax.modifiers.filtered(keywords: [.nonisolated]) return modifiers.trimmed.appending(filtered.trimmed) } private var body: CodeBlockItemListSyntax { get throws { try CodeBlockItemListSyntax { try memberDeclaration if syntax.isVoid { mockerCall } else { returnStatement } } } } private var memberDeclaration: DeclSyntax { get throws { let variableDecl = try VariableDeclSyntax(bindingSpecifier: .keyword(.let)) { try PatternBindingListSyntax { PatternBindingSyntax( pattern: IdentifierPatternSyntax(identifier: NS.member), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax(name: NS.Member) ), initializer: InitializerClauseSyntax( value: try caseSpecifier(wrapParams: true) ) ) } } return DeclSyntax(variableDecl) } } private var returnStatement: StmtSyntax { StmtSyntax(ReturnStmtSyntax(expression: mockerCall)) } private var mockerCall: ExprSyntax { let call = FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: NS.mocker), declName: DeclReferenceExprSyntax( baseName: syntax.isThrowing ? NS.mockThrowing : NS.mock ) ), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: NS.member) ) if let errorTypeExpression { errorTypeExpression } }, rightParen: .rightParenToken(), trailingClosure: mockerClosure ) return if syntax.isThrowing { ExprSyntax(TryExprSyntax(expression: call)) } else { ExprSyntax(call) } } private var errorTypeExpression: LabeledExprSyntax? { #if canImport(SwiftSyntax600) guard let type = syntax.errorType else { return nil } return LabeledExprSyntax( label: NS.error, colon: .colonToken(), expression: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: .identifier(type.trimmedDescription)), declName: DeclReferenceExprSyntax(baseName: .keyword(.`self`)) ) ) #else return nil #endif } private var mockerClosure: ClosureExprSyntax { ClosureExprSyntax( signature: mockerClosureSignature, statements: CodeBlockItemListSyntax { producerDeclaration producerCall } ) } private var mockerClosureSignature: ClosureSignatureSyntax { let paramList = ClosureShorthandParameterListSyntax { ClosureShorthandParameterSyntax(name: NS.producer) } return ClosureSignatureSyntax(parameterClause: .simpleInput(paramList)) } private var producerDeclaration: VariableDeclSyntax { VariableDeclSyntax( bindingSpecifier: .keyword(.let), bindings: PatternBindingListSyntax { PatternBindingSyntax( pattern: IdentifierPatternSyntax(identifier: NS.producer), initializer: InitializerClauseSyntax(value: producerCast) ) } ) } private var producerCast: TryExprSyntax { TryExprSyntax( expression: AsExprSyntax( expression: FunctionCallExprSyntax( calledExpression: DeclReferenceExprSyntax(baseName: NS.cast), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: NS.producer) ) }, rightParen: .rightParenToken() ), type: syntax.closureType ) ) } private var producerCall: ReturnStmtSyntax { let producerCallExpr = FunctionCallExprSyntax( calledExpression: DeclReferenceExprSyntax(baseName: NS.producer), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { for parameter in syntax.signature.parameterClause.parameters { let parameterReference = DeclReferenceExprSyntax( baseName: (parameter.secondName?.trimmed ?? parameter.firstName.trimmed).declNameOrVarCallName ) if parameter.isInout { LabeledExprSyntax(expression: InOutExprSyntax(expression: parameterReference)) } else { LabeledExprSyntax(expression: parameterReference) } } }, rightParen: .rightParenToken() ) let producerCall = ExprSyntax(producerCallExpr) let tryProducerCall = ExprSyntax(TryExprSyntax(expression: producerCall)) return ReturnStmtSyntax(expression: syntax.isThrowing ? tryProducerCall : producerCall) } } ================================================ FILE: Sources/MockableMacro/Factory/Mockable/Initializer+Mockable.swift ================================================ // // Initializer+Mockable.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax // MARK: - InitializerRequirement + Mockable extension InitializerRequirement: Mockable { func implement(with modifiers: DeclModifierListSyntax) throws -> DeclSyntax { let initDecl = InitializerDeclSyntax( attributes: syntax.attributes.trimmed.with(\.trailingTrivia, .newline), modifiers: declarationModifiers(extending: modifiers), initKeyword: syntax.initKeyword.trimmed, optionalMark: syntax.optionalMark?.trimmed, genericParameterClause: syntax.genericParameterClause?.trimmed, signature: syntax.signature.trimmed, genericWhereClause: syntax.genericWhereClause?.trimmed, body: .init(statements: []) ) return DeclSyntax(initDecl) } } // MARK: - Helpers extension InitializerRequirement { private func declarationModifiers(extending modifiers: DeclModifierListSyntax) -> DeclModifierListSyntax { let filtered = syntax.modifiers.filtered(keywords: [.nonisolated]) return modifiers.trimmed.appending(filtered.trimmed) } } ================================================ FILE: Sources/MockableMacro/Factory/Mockable/Mockable.swift ================================================ // // Mockable.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax /// Defines a protocol for creating mock implementations. /// /// Requirements conforming to `Mockable` can generate mock versions of themselves. protocol Mockable { /// Creates a mock declaration. /// /// - Parameter modifiers: Modifiers to apply to the mock declaration. /// - Throws: If the mock cannot be generated. /// - Returns: The mock declaration of the requirements. func implement(with modifiers: DeclModifierListSyntax) throws -> DeclSyntax } ================================================ FILE: Sources/MockableMacro/Factory/Mockable/Variable+Mockable.swift ================================================ // // Variable+Mockable.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 23.. // import SwiftSyntax // MARK: - VariableRequirement + Mockable extension VariableRequirement: Mockable { func implement(with modifiers: DeclModifierListSyntax) throws -> DeclSyntax { let variableDecl = VariableDeclSyntax( attributes: syntax.attributes.trimmed.with(\.trailingTrivia, .newline), modifiers: declarationModifiers(extending: modifiers), bindingSpecifier: .keyword(.var), bindings: try PatternBindingListSyntax { try syntax.binding.with(\.accessorBlock, accessorBlock) } ) return DeclSyntax(variableDecl) } } // MARK: - Helpers extension VariableRequirement { private func declarationModifiers(extending modifiers: DeclModifierListSyntax) -> DeclModifierListSyntax { let filtered = syntax.modifiers.filtered(keywords: [.nonisolated]) return modifiers.trimmed.appending(filtered.trimmed) } private var accessorBlock: AccessorBlockSyntax { get throws { AccessorBlockSyntax(accessors: .accessors(try accessorDeclList)) } } private var accessorDeclList: AccessorDeclListSyntax { get throws { try AccessorDeclListSyntax { try getterDecl if let setterDecl = try setterDecl { setterDecl } } } } private var getterDecl: AccessorDeclSyntax { get throws { let caseSpecifier = try caseSpecifier(wrapParams: true) let mockerCall = try mockerCall let body = CodeBlockSyntax( statements: CodeBlockItemListSyntax { memberDeclaration(caseSpecifier) ReturnStmtSyntax(expression: mockerCall) } ) return try syntax.getAccessor.with(\.body, body) } } private var setterDecl: AccessorDeclSyntax? { get throws { guard let caseSpecifier = try setterCaseSpecifier(wrapParams: true), let setAccessor = syntax.setAccessor else { return nil } let body = CodeBlockSyntax( statements: CodeBlockItemListSyntax { memberDeclaration(caseSpecifier) mockerCall(memberName: NS.addInvocation) mockerCall(memberName: NS.performActions) } ) return setAccessor.with(\.body, body) } } private var mockerCall: ExprSyntax { get throws { let call = FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: NS.mocker), declName: DeclReferenceExprSyntax( baseName: try syntax.isThrowing ? NS.mockThrowing : NS.mock ) ), leftParen: .leftParenToken(), arguments: try LabeledExprListSyntax { LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: NS.member) ) if let errorTypeExpression = try errorTypeExpression { errorTypeExpression } }, rightParen: .rightParenToken(), trailingClosure: try mockerClosure ) return if try syntax.isThrowing { ExprSyntax(TryExprSyntax(expression: call)) } else { ExprSyntax(call) } } } private var errorTypeExpression: LabeledExprSyntax? { get throws { #if canImport(SwiftSyntax600) guard let type = try syntax.errorType else { return nil } return LabeledExprSyntax( label: NS.error, colon: .colonToken(), expression: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: .identifier(type.trimmedDescription)), declName: DeclReferenceExprSyntax(baseName: .keyword(.`self`)) ) ) #else return nil #endif } } private func memberDeclaration(_ caseSpecifier: ExprSyntax) -> VariableDeclSyntax { VariableDeclSyntax(bindingSpecifier: .keyword(.let)) { PatternBindingListSyntax { PatternBindingSyntax( pattern: IdentifierPatternSyntax(identifier: NS.member), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax(name: NS.Member) ), initializer: InitializerClauseSyntax(value: caseSpecifier) ) } } } private var mockerClosure: ClosureExprSyntax { get throws { ClosureExprSyntax( signature: mockerClosureSignature, statements: try CodeBlockItemListSyntax { try producerDeclaration try producerCall } ) } } private var mockerClosureSignature: ClosureSignatureSyntax { let paramList = ClosureShorthandParameterListSyntax { ClosureShorthandParameterSyntax(name: NS.producer) } return ClosureSignatureSyntax(parameterClause: .simpleInput(paramList)) } private var producerDeclaration: VariableDeclSyntax { get throws { VariableDeclSyntax( bindingSpecifier: .keyword(.let), bindings: try PatternBindingListSyntax { PatternBindingSyntax( pattern: IdentifierPatternSyntax(identifier: NS.producer), initializer: InitializerClauseSyntax(value: try producerCast) ) } ) } } private var producerCast: TryExprSyntax { get throws { TryExprSyntax( expression: AsExprSyntax( expression: FunctionCallExprSyntax( calledExpression: DeclReferenceExprSyntax(baseName: NS.cast), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax( expression: DeclReferenceExprSyntax(baseName: NS.producer) ) }, rightParen: .rightParenToken() ), type: try syntax.closureType ) ) } } private var producerCall: ReturnStmtSyntax { get throws { let producerCallExpr = FunctionCallExprSyntax( calledExpression: DeclReferenceExprSyntax(baseName: NS.producer), leftParen: .leftParenToken(), arguments: [], rightParen: .rightParenToken() ) let producerCall = ExprSyntax(producerCallExpr) let tryProducerCall = ExprSyntax(TryExprSyntax(expression: producerCall)) return ReturnStmtSyntax(expression: try syntax.isThrowing ? tryProducerCall : producerCall) } } private func mockerCall(memberName: TokenSyntax) -> FunctionCallExprSyntax { FunctionCallExprSyntax( calledExpression: MemberAccessExprSyntax( base: DeclReferenceExprSyntax(baseName: NS.mocker), name: memberName ), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax( label: NS._for, colon: .colonToken(), expression: DeclReferenceExprSyntax(baseName: NS.member) ) }, rightParen: .rightParenToken() ) } } ================================================ FILE: Sources/MockableMacro/MockableMacro.swift ================================================ // // MockableMacro.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 11. 14.. // import SwiftSyntax import SwiftSyntaxMacros import SwiftDiagnostics public enum MockableMacro: PeerMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else { throw MockableMacroError.notAProtocol } #if swift(>=6) && !canImport(SwiftSyntax600) context.diagnose(Diagnostic(node: node, message: MockableMacroWarning.versionMismatch)) #endif let requirements = try Requirements(protocolDecl) let declaration = try MockFactory.build(from: requirements) let codeblock = CodeBlockItemListSyntax { CodeBlockItemSyntax(item: .decl(declaration)) } let ifClause = IfConfigClauseListSyntax { IfConfigClauseSyntax( poundKeyword: .poundIfToken(), condition: DeclReferenceExprSyntax(baseName: NS.MOCKING), elements: .statements(codeblock) ) } return [DeclSyntax(IfConfigDeclSyntax(clauses: ifClause))] } } ================================================ FILE: Sources/MockableMacro/MockableMacroError.swift ================================================ // // MockableMacroError.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 11. 24.. // public enum MockableMacroError: Error, CustomStringConvertible { case notAProtocol case invalidVariableRequirement case invalidDerivedEnumCase case nonEscapingFunctionParameter case subscriptsNotSupported case operatorsNotSupported case staticMembersNotSupported public var description: String { switch self { case .notAProtocol: return "@Mockable can only be applied to protocols." case .invalidVariableRequirement: return "Invalid variable requirement. Missing type annotation or accessor block." case .invalidDerivedEnumCase: return "Unexpected error during generating an enum representation of this protocol." case .nonEscapingFunctionParameter: return """ Non-escaping function parameters are not supported by @Mockable. \ Add @escaping to all function parameters to resolve this issue. """ case .subscriptsNotSupported: return "Subscript requirements are not supported by @Mockable." case .operatorsNotSupported: return "Operator requirements are not supported by @Mockable." case .staticMembersNotSupported: return "Static member requirements are not supported by @Mockable." } } } ================================================ FILE: Sources/MockableMacro/MockableMacroWarning.swift ================================================ // // MockableMacroWarning.swift // Mockable // // Created by Kolos Foltanyi on 2024. 12. 01.. // import SwiftSyntax import SwiftDiagnostics public enum MockableMacroWarning { case versionMismatch } // MARK: - DiagnosticMessage extension MockableMacroWarning: DiagnosticMessage { public var message: String { switch self { case .versionMismatch: """ Your SwiftSyntax version is pinned to \(swiftSyntaxVersion).x.x by some of your dependencies. \ Using a lower SwiftSyntax version than your Swift version may lead to issues when using Mockable. """ } } public var diagnosticID: MessageID { switch self { case .versionMismatch: MessageID(domain: "Mockable", id: "MockableMacroWarning.versionMismatch") } } public var severity: DiagnosticSeverity { .warning } } // MARK: - Helpers extension MockableMacroWarning { private var swiftSyntaxVersion: String { #if canImport(SwiftSyntax600) ">600" #elseif canImport(SwiftSyntax510) "510" #elseif canImport(SwiftSyntax509) "509" #else "" #endif } } ================================================ FILE: Sources/MockableMacro/Plugin.swift ================================================ // // MockableMacroPlugin.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 11. 14.. // import SwiftCompilerPlugin import SwiftSyntaxMacros @main struct Plugin: CompilerPlugin { let providingMacros: [any Macro.Type] = [ MockableMacro.self ] } ================================================ FILE: Sources/MockableMacro/Requirements/FunctionRequirement.swift ================================================ // // FunctionRequirement.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 12. 07.. // import SwiftSyntax struct FunctionRequirement { let index: Int let syntax: FunctionDeclSyntax } ================================================ FILE: Sources/MockableMacro/Requirements/InitializerRequirement.swift ================================================ // // InitializerRequirement.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 14.. // import SwiftSyntax struct InitializerRequirement { let index: Int let syntax: InitializerDeclSyntax } ================================================ FILE: Sources/MockableMacro/Requirements/Requirements.swift ================================================ // // Requirements.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 12.. // import SwiftSyntax struct Requirements { // MARK: Properties let syntax: ProtocolDeclSyntax let modifiers: DeclModifierListSyntax let isActor: Bool let containsGenericExistentials: Bool var functions = [FunctionRequirement]() var variables = [VariableRequirement]() var initializers = [InitializerRequirement]() // MARK: Init init(_ syntax: ProtocolDeclSyntax) throws { self.syntax = syntax let members = syntax.memberBlock.members guard members.compactMap({ $0.decl.as(SubscriptDeclSyntax.self) }).isEmpty else { throw MockableMacroError.subscriptsNotSupported } self.modifiers = Self.initModifiers(syntax) self.isActor = Self.initIsActor(syntax) self.initializers = Self.initInitializers(members) self.variables = try Self.initVariables(members) self.functions = try Self.initFunctions(members, startIndex: variables.count) self.containsGenericExistentials = try Self.initContainsGenericExistentials(variables, functions) } } // MARK: - Helpers extension Requirements { private static func isStatic(_ modifier: DeclModifierSyntax) -> Bool { modifier.name.tokenKind == .keyword(.static) } private static func initModifiers(_ syntax: ProtocolDeclSyntax) -> DeclModifierListSyntax { syntax.modifiers.trimmed.filter { modifier in guard case .keyword(let keyword) = modifier.name.tokenKind else { return true } return keyword != .private && keyword != .nonisolated } } private static func initIsActor(_ syntax: ProtocolDeclSyntax) -> Bool { guard let inheritanceClause = syntax.inheritanceClause, !inheritanceClause.inheritedTypes.isEmpty else { return false } for inheritedType in inheritanceClause.inheritedTypes { if let type = inheritedType.type.as(IdentifierTypeSyntax.self), type.name.trimmed.tokenKind == NS.Actor.tokenKind { return true } } return false } private static func initVariables(_ members: MemberBlockItemListSyntax) throws -> [VariableRequirement] { try members .compactMap { $0.decl.as(VariableDeclSyntax.self) } .filter { guard !$0.modifiers.contains(where: isStatic) else { throw MockableMacroError.staticMembersNotSupported } return true } .enumerated() .map(VariableRequirement.init) } private static func initFunctions(_ members: MemberBlockItemListSyntax, startIndex: Int) throws -> [FunctionRequirement] { try members .compactMap { $0.decl.as(FunctionDeclSyntax.self) } .filter { guard !$0.modifiers.contains(where: isStatic) else { throw MockableMacroError.staticMembersNotSupported } guard case .identifier = $0.name.tokenKind else { throw MockableMacroError.operatorsNotSupported } return true } .enumerated() .map { index, element in FunctionRequirement(index: startIndex + index, syntax: element) } } private static func initInitializers(_ members: MemberBlockItemListSyntax) -> [InitializerRequirement] { members .compactMap { $0.decl.as(InitializerDeclSyntax.self) } .enumerated() .map { InitializerRequirement(index: $0, syntax: $1) } } private static func initContainsGenericExistentials( _ variables: [VariableRequirement], _ functions: [FunctionRequirement] ) throws -> Bool { let variables = try variables.filter { let type = try $0.syntax.type return hasParametrizedProtocolRequirement(type) } let functions = functions.filter { guard let returnClause = $0.syntax.signature.returnClause else { return false } let type = returnClause.type return hasParametrizedProtocolRequirement(type) } return !variables.isEmpty || !functions.isEmpty } private static func hasParametrizedProtocolRequirement(_ type: TypeSyntax) -> Bool { if let type = type.as(SomeOrAnyTypeSyntax.self), type.someOrAnySpecifier.tokenKind == .keyword(.any), let type = type.constraint.as(IdentifierTypeSyntax.self), let argumentClause = type.genericArgumentClause, !argumentClause.arguments.isEmpty { return true } else if let type = type.as(IdentifierTypeSyntax.self), let argumentClause = type.genericArgumentClause { return argumentClause.arguments.contains { #if canImport(SwiftSyntax601) switch $0.argument { case .type(let type): return hasParametrizedProtocolRequirement(type) default: return false } #else return hasParametrizedProtocolRequirement($0.argument) #endif } } else { return false } } } ================================================ FILE: Sources/MockableMacro/Requirements/VariableRequirement.swift ================================================ // // VariableRequirement.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 10.. // import SwiftSyntax struct VariableRequirement { let index: Int let syntax: VariableDeclSyntax } ================================================ FILE: Sources/MockableMacro/Utils/Availability.swift ================================================ // // Availability.swift // MockableMacro // // Created by Kolos Foltanyi on 30/07/2024. // import SwiftSyntax enum AvailabilityVersionParseError: Error { case invalidVersionString } enum Availability { static func from(iOS: String, macOS: String, tvOS: String, watchOS: String) throws -> AttributeSyntax { let arguments = try AvailabilityArgumentListSyntax { try availability(platform: NS.iOS, version: iOS) try availability(platform: NS.macOS, version: macOS) try availability(platform: NS.tvOS, version: tvOS) try availability(platform: NS.watchOS, version: watchOS) AvailabilityArgumentSyntax(argument: .token(.binaryOperator("*"))) } return AttributeSyntax( attributeName: IdentifierTypeSyntax(name: NS.available), leftParen: .leftParenToken(), arguments: .availability(arguments), rightParen: .rightParenToken() ) } private static func availability(platform: TokenSyntax, version: String) throws -> AvailabilityArgumentSyntax { let version = version.split(separator: ".").map(String.init) guard let major = version.first else { throw AvailabilityVersionParseError.invalidVersionString } let components = version.dropFirst().compactMap { return VersionComponentSyntax(number: .integerLiteral($0)) } let versionSyntax = PlatformVersionSyntax( platform: platform, version: .init( major: .integerLiteral(major), components: .init(components) ) ) return AvailabilityArgumentSyntax(argument: .availabilityVersionRestriction(versionSyntax)) } } ================================================ FILE: Sources/MockableMacro/Utils/Messages.swift ================================================ // // Messages.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 04. 01.. // enum Messages { static let givenMessage = "Use given(_ service:) instead." static let whenMessage = "Use when(_ service:) instead." static let verifyMessage = "Use verify(_ service:) instead." } ================================================ FILE: Sources/MockableMacro/Utils/Namespace.swift ================================================ // // Namespace.swift // MockableMacro // // Created by Kolos Foltanyi on 2024. 03. 28.. // #if canImport(SwiftSyntax600) || swift(<6) import SwiftSyntax #else @preconcurrency import SwiftSyntax #endif // swiftlint:disable identifier_name enum NS { static let MOCKING: TokenSyntax = "MOCKING" static let generic: TokenSyntax = "generic" static let value: TokenSyntax = "value" static let eraseToGenericValue: TokenSyntax = "eraseToGenericValue" static let newValue: TokenSyntax = "newValue" static let other: TokenSyntax = "other" static let match: TokenSyntax = "match" static let left: TokenSyntax = "left" static let right: TokenSyntax = "right" static let with: TokenSyntax = "with" static let reset: TokenSyntax = "reset" static let scopes: TokenSyntax = "scopes" static let all: TokenSyntax = "all" static let available: TokenSyntax = "available" static let deprecated: TokenSyntax = "deprecated" static let message: TokenSyntax = "message" static let kind: TokenSyntax = "kind" static let setKind: TokenSyntax = "setKind" static let any: TokenSyntax = "any" static let initializer: TokenSyntax = "init" static let member: TokenSyntax = "member" static let mock: TokenSyntax = "mock" static let mockThrowing: TokenSyntax = "mockThrowing" static let producer: TokenSyntax = "producer" static let cast: TokenSyntax = "cast" static let addInvocation: TokenSyntax = "addInvocation" static let performActions: TokenSyntax = "performActions" static let policy: TokenSyntax = "policy" static let error: TokenSyntax = "error" static let swift: TokenSyntax = "swift" static let iOS: TokenSyntax = "iOS" static let macOS: TokenSyntax = "macOS" static let tvOS: TokenSyntax = "tvOS" static let watchOS: TokenSyntax = "watchOS" static let _given: TokenSyntax = "_given" static let _when: TokenSyntax = "_when" static let _verify: TokenSyntax = "_verify" static let _andSign: String = "&&" static let _init: TokenSyntax = "init" static let _star: TokenSyntax = "*" static let _for: TokenSyntax = "for" static let _gte: String = ">=" static let get_: String = "get_" static let set_: String = "set_" static let mocker: TokenSyntax = "mocker" static let Mockable: TokenSyntax = "Mockable" static let Mock: TokenSyntax = "Mock" static let MockableService: TokenSyntax = "MockableService" static let Bool: TokenSyntax = "Bool" static let GenericValue: String = "GenericValue" static let Mocker: TokenSyntax = "Mocker" static let Builder: TokenSyntax = "Builder" static let Member: TokenSyntax = "Member" static let Matchable: TokenSyntax = "Matchable" static let CaseIdentifiable: TokenSyntax = "CaseIdentifiable" static let ReturnBuilder: TokenSyntax = "ReturnBuilder" static let ActionBuilder: TokenSyntax = "ActionBuilder" static let VerifyBuilder: TokenSyntax = "VerifyBuilder" static let MockerScope: TokenSyntax = "MockerScope" static let MockerPolicy: TokenSyntax = "MockerPolicy" static let Set: TokenSyntax = "Set" static let Void: TokenSyntax = "Void" static let Actor: TokenSyntax = "Actor" static let Error: TokenSyntax = "Error" static let NSObjectProtocol: String = "NSObjectProtocol" static let NSObject: TokenSyntax = "NSObject" static let Swift: TokenSyntax = "Swift" static let Sendable: TokenSyntax = "Sendable" static func Parameter(_ type: String) -> TokenSyntax { "Parameter<\(raw: type)>" } static func Param(suffix: String) -> TokenSyntax { "Param\(raw: suffix)" } static func Function(_ kind: BuilderKind) -> TokenSyntax { "Function\(raw: kind.name)" } static func ThrowingFunction(_ kind: BuilderKind) -> TokenSyntax { "ThrowingFunction\(raw: kind.name)" } static func Property(_ kind: BuilderKind) -> TokenSyntax { "Property\(raw: kind.name)" } static func ThrowingProperty(_ kind: BuilderKind) -> TokenSyntax { "ThrowingProperty\(raw: kind.name)" } } // swiftlint:enable identifier_name ================================================ FILE: Sources/MockableMacro/Utils/SwiftVersionHelper.swift ================================================ // // File.swift // Mockable // // Created by Kolos Foltányi on 2025. 11. 13.. // import SwiftSyntax enum SwiftVersionRequirement: TokenSyntax { case swift_6_1 = "6.1" } enum SwiftVersionHelper { static func condition( minimumVersion: SwiftVersionRequirement, gte latest: IfConfigClauseSyntax.Elements, else fallback: IfConfigClauseSyntax.Elements ) -> IfConfigDeclSyntax { IfConfigDeclSyntax( clauses: IfConfigClauseListSyntax { IfConfigClauseSyntax( poundKeyword: .poundIfToken(), condition: FunctionCallExprSyntax( calledExpression: DeclReferenceExprSyntax(baseName: NS.swift), leftParen: .leftParenToken(), arguments: LabeledExprListSyntax { LabeledExprSyntax( expression: PrefixOperatorExprSyntax( operator: .prefixOperator(NS._gte), expression: FloatLiteralExprSyntax(literal: minimumVersion.rawValue) ) ) }, rightParen: .rightParenToken(), additionalTrailingClosures: MultipleTrailingClosureElementListSyntax() ), elements: latest ) IfConfigClauseSyntax( poundKeyword: .poundElseToken(), elements: fallback ) }, poundEndif: .poundEndifToken() ) } } ================================================ FILE: Sources/MockableMacro/Utils/TokenFinder.swift ================================================ // // TokenFinder.swift // MockableMacro // // Created by Kolos Foltanyi on 2023. 11. 20.. // import SwiftSyntax class TokenVisitor: SyntaxVisitor { var match: (TokenSyntax) -> Bool init(match: @escaping (TokenSyntax) -> Bool) { self.match = match super.init(viewMode: .sourceAccurate) } override func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind { match(token) ? .skipChildren : .visitChildren } } enum TokenFinder { static func find( in syntax: some SyntaxProtocol, matching matcher: @escaping (TokenSyntax) -> Bool ) -> TokenSyntax? { var result: TokenSyntax? let visitor = TokenVisitor { let match = matcher($0) if match, result == nil { result = $0 } return result != nil } visitor.walk(syntax) return result } } ================================================ FILE: Tests/MockableMacroTests/AccessModifierTests.swift ================================================ // // AccessModifierTests.swift // // // Created by Nayanda Haberty on 29/5/24. // import MacroTesting import XCTest final class AccessModifierTests: MockableMacroTestCase { func test_public_modifier() { assertMacro { """ @Mockable public protocol Test { init(id: String) var foo: Int { get } func bar(number: Int) -> Int } """ } expansion: { """ public protocol Test { init(id: String) var foo: Int { get } func bar(number: Int) -> Int } #if MOCKING public final class MockTest: Test, Mockable.MockableService { public typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") public nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") public nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") public nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } public nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } public nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } public init(id: String) { } public func bar(number: Int) -> Int { let member: Member = .m2_bar(number: .value(number)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Int) -> Int return producer(number) } } public var foo: Int { get { let member: Member = .m1_foo return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } #if swift(>=6.1) public nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo case m2_bar(number: Parameter) public nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true case (.m2_bar(number: let leftNumber), .m2_bar(number: let rightNumber)): return leftNumber.match(rightNumber) default: return false } } } #else public enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo case m2_bar(number: Parameter) public nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true case (.m2_bar(number: let leftNumber), .m2_bar(number: let rightNumber)): return leftNumber.match(rightNumber) default: return false } } } #endif public struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker public nonisolated init(mocker: Mocker) { self.mocker = mocker } public nonisolated var foo: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m1_foo) } public nonisolated func bar(number: Parameter) -> Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m2_bar(number: number)) } } public struct ActionBuilder: Mockable.Builder { private let mocker: Mocker public nonisolated init(mocker: Mocker) { self.mocker = mocker } public nonisolated var foo: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } public nonisolated func bar(number: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_bar(number: number)) } } public struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker public nonisolated init(mocker: Mocker) { self.mocker = mocker } public nonisolated var foo: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } public nonisolated func bar(number: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_bar(number: number)) } } } #endif """ } } func test_private_access_modifier() { assertMacro { """ @Mockable private protocol Test { var foo: Int { get } func bar(number: Int) -> Int } """ } expansion: { """ private protocol Test { var foo: Int { get } func bar(number: Int) -> Int } #if MOCKING private final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func bar(number: Int) -> Int { let member: Member = .m2_bar(number: .value(number)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Int) -> Int return producer(number) } } var foo: Int { get { let member: Member = .m1_foo return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo case m2_bar(number: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true case (.m2_bar(number: let leftNumber), .m2_bar(number: let rightNumber)): return leftNumber.match(rightNumber) default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo case m2_bar(number: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true case (.m2_bar(number: let leftNumber), .m2_bar(number: let rightNumber)): return leftNumber.match(rightNumber) default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m1_foo) } nonisolated func bar(number: Parameter) -> Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m2_bar(number: number)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } nonisolated func bar(number: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_bar(number: number)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } nonisolated func bar(number: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_bar(number: number)) } } } #endif """ } } func test_mutating_modifier_filtered() { assertMacro { """ @Mockable public protocol Test { mutating nonisolated func foo() } """ } expansion: { """ public protocol Test { mutating nonisolated func foo() } #if MOCKING public final class MockTest: Test, Mockable.MockableService { public typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") public nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") public nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") public nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } public nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } public nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } public nonisolated func foo() { let member: Member = .m1_foo mocker.mock(member) { producer in let producer = try cast(producer) as () -> Void return producer() } } #if swift(>=6.1) public nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo public nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #else public enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo public nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #endif public struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker public nonisolated init(mocker: Mocker) { self.mocker = mocker } public nonisolated func foo() -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m1_foo) } } public struct ActionBuilder: Mockable.Builder { private let mocker: Mocker public nonisolated init(mocker: Mocker) { self.mocker = mocker } public nonisolated func foo() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } } public struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker public nonisolated init(mocker: Mocker) { self.mocker = mocker } public nonisolated func foo() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/ActorConformanceTests.swift ================================================ // // ActorConformanceTests.swift // // // Created by Kolos Foltanyi on 06/07/2024. // import MacroTesting import XCTest final class ActorConformanceTests: MockableMacroTestCase { func test_global_actor_conformance() { assertMacro { """ @MainActor @Mockable protocol Test { var foo: Int { get } nonisolated var quz: Int { get } func bar(number: Int) -> Int nonisolated func baz(number: Int) -> Int } """ } expansion: { """ @MainActor protocol Test { var foo: Int { get } nonisolated var quz: Int { get } func bar(number: Int) -> Int nonisolated func baz(number: Int) -> Int } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func bar(number: Int) -> Int { let member: Member = .m3_bar(number: .value(number)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Int) -> Int return producer(number) } } nonisolated func baz(number: Int) -> Int { let member: Member = .m4_baz(number: .value(number)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Int) -> Int return producer(number) } } var foo: Int { get { let member: Member = .m1_foo return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } nonisolated var quz: Int { get { let member: Member = .m2_quz return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo case m2_quz case m3_bar(number: Parameter) case m4_baz(number: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true case (.m2_quz, .m2_quz): return true case (.m3_bar(number: let leftNumber), .m3_bar(number: let rightNumber)): return leftNumber.match(rightNumber) case (.m4_baz(number: let leftNumber), .m4_baz(number: let rightNumber)): return leftNumber.match(rightNumber) default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo case m2_quz case m3_bar(number: Parameter) case m4_baz(number: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true case (.m2_quz, .m2_quz): return true case (.m3_bar(number: let leftNumber), .m3_bar(number: let rightNumber)): return leftNumber.match(rightNumber) case (.m4_baz(number: let leftNumber), .m4_baz(number: let rightNumber)): return leftNumber.match(rightNumber) default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m1_foo) } nonisolated var quz: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m2_quz) } nonisolated func bar(number: Parameter) -> Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m3_bar(number: number)) } nonisolated func baz(number: Parameter) -> Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m4_baz(number: number)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } nonisolated var quz: Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_quz) } nonisolated func bar(number: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m3_bar(number: number)) } nonisolated func baz(number: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m4_baz(number: number)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } nonisolated var quz: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_quz) } nonisolated func bar(number: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m3_bar(number: number)) } nonisolated func baz(number: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m4_baz(number: number)) } } } #endif """ } } func test_actor_requirement() { assertMacro { """ @Mockable protocol Test: Actor { var foo: Int { get } nonisolated var quz: Int { get } func bar(number: Int) -> Int nonisolated func baz(number: Int) -> Int } """ } expansion: { """ protocol Test: Actor { var foo: Int { get } nonisolated var quz: Int { get } func bar(number: Int) -> Int nonisolated func baz(number: Int) -> Int } #if MOCKING actor MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func bar(number: Int) -> Int { let member: Member = .m3_bar(number: .value(number)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Int) -> Int return producer(number) } } nonisolated func baz(number: Int) -> Int { let member: Member = .m4_baz(number: .value(number)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Int) -> Int return producer(number) } } var foo: Int { get { let member: Member = .m1_foo return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } nonisolated var quz: Int { get { let member: Member = .m2_quz return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo case m2_quz case m3_bar(number: Parameter) case m4_baz(number: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true case (.m2_quz, .m2_quz): return true case (.m3_bar(number: let leftNumber), .m3_bar(number: let rightNumber)): return leftNumber.match(rightNumber) case (.m4_baz(number: let leftNumber), .m4_baz(number: let rightNumber)): return leftNumber.match(rightNumber) default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo case m2_quz case m3_bar(number: Parameter) case m4_baz(number: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true case (.m2_quz, .m2_quz): return true case (.m3_bar(number: let leftNumber), .m3_bar(number: let rightNumber)): return leftNumber.match(rightNumber) case (.m4_baz(number: let leftNumber), .m4_baz(number: let rightNumber)): return leftNumber.match(rightNumber) default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m1_foo) } nonisolated var quz: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m2_quz) } nonisolated func bar(number: Parameter) -> Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m3_bar(number: number)) } nonisolated func baz(number: Parameter) -> Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m4_baz(number: number)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } nonisolated var quz: Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_quz) } nonisolated func bar(number: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m3_bar(number: number)) } nonisolated func baz(number: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m4_baz(number: number)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } nonisolated var quz: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_quz) } nonisolated func bar(number: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m3_bar(number: number)) } nonisolated func baz(number: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m4_baz(number: number)) } } } #endif """ } } func test_nonisolated_protocol() { assertMacro { """ @Mockable nonisolated protocol Test { func foo() } """ } expansion: { """ nonisolated protocol Test { func foo() } #if MOCKING nonisolated final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo() { let member: Member = .m1_foo mocker.mock(member) { producer in let producer = try cast(producer) as () -> Void return producer() } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m1_foo) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } } } #endif """ } } func test_mainactor_requirement() { assertMacro { """ @Mockable protocol Test { @MainActor func foo() } """ } expansion: { """ protocol Test { @MainActor func foo() } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } @MainActor func foo() { let member: Member = .m1_foo mocker.mock(member) { producer in let producer = try cast(producer) as () -> Void return producer() } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m1_foo) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/AssociatedTypeTests.swift ================================================ // // AssociatedTypeTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 21.. // import MacroTesting import XCTest final class AssociatedTypeTests: MockableMacroTestCase { func test_associated_type() { assertMacro { """ @Mockable protocol Test { associatedtype Item func foo(item: Item) -> Item } """ } expansion: { """ protocol Test { associatedtype Item func foo(item: Item) -> Item } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo(item: Item) -> Item { let member: Member = .m1_foo(item: .value(item)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Item) -> Item return producer(item) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(item: let leftItem), .m1_foo(item: let rightItem)): return leftItem.match(rightItem) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(item: let leftItem), .m1_foo(item: let rightItem)): return leftItem.match(rightItem) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionReturnBuilder Item> { .init(mocker, kind: .m1_foo(item: item)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo(item: item)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo(item: item)) } } } #endif """ } } func test_associated_type_with_inheritance() { assertMacro { """ @Mockable protocol Test { associatedtype Item: Equatable, Hashable func foo(item: Item) -> Item } """ } expansion: { """ protocol Test { associatedtype Item: Equatable, Hashable func foo(item: Item) -> Item } #if MOCKING final class MockTest: Test, Mockable.MockableService where Item: Equatable, Item: Hashable { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo(item: Item) -> Item { let member: Member = .m1_foo(item: .value(item)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Item) -> Item return producer(item) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(item: let leftItem), .m1_foo(item: let rightItem)): return leftItem.match(rightItem) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(item: let leftItem), .m1_foo(item: let rightItem)): return leftItem.match(rightItem) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionReturnBuilder Item> { .init(mocker, kind: .m1_foo(item: item)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo(item: item)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo(item: item)) } } } #endif """ } } func test_associated_type_with_where_clause() { assertMacro { """ @Mockable protocol Test { associatedtype Item where Item: Equatable, Item: Hashable func foo(item: Item) -> Item } """ } expansion: { """ protocol Test { associatedtype Item where Item: Equatable, Item: Hashable func foo(item: Item) -> Item } #if MOCKING final class MockTest: Test, Mockable.MockableService where Item: Equatable, Item: Hashable { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo(item: Item) -> Item { let member: Member = .m1_foo(item: .value(item)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Item) -> Item return producer(item) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(item: let leftItem), .m1_foo(item: let rightItem)): return leftItem.match(rightItem) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(item: let leftItem), .m1_foo(item: let rightItem)): return leftItem.match(rightItem) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionReturnBuilder Item> { .init(mocker, kind: .m1_foo(item: item)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo(item: item)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo(item: item)) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/AttributesTests.swift ================================================ // // AttributesTest.swift // // // Created by Kolos Foltanyi on 2024. 03. 17.. // import MacroTesting import XCTest final class AttributesTests: MockableMacroTestCase { func test_attributed_requirements() { assertMacro { """ @Mockable protocol AttributeTest { @available(iOS 15, *) init(name: String) @available(iOS 15, *) var prop: Int { get } @available(iOS 15, *) func test() @available(iOS 15, *) init(name2: String) @available(iOS 15, *) var prop2: Int { get } @available(iOS 15, *) func test2() } """ } expansion: { """ protocol AttributeTest { @available(iOS 15, *) init(name: String) @available(iOS 15, *) var prop: Int { get } @available(iOS 15, *) func test() @available(iOS 15, *) init(name2: String) @available(iOS 15, *) var prop2: Int { get } @available(iOS 15, *) func test2() } #if MOCKING final class MockAttributeTest: AttributeTest, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } @available(iOS 15, *) init(name: String) { } @available(iOS 15, *) init(name2: String) { } @available(iOS 15, *) func test() { let member: Member = .m3_test mocker.mock(member) { producer in let producer = try cast(producer) as () -> Void return producer() } } @available(iOS 15, *) func test2() { let member: Member = .m4_test2 mocker.mock(member) { producer in let producer = try cast(producer) as () -> Void return producer() } } @available(iOS 15, *) var prop: Int { get { let member: Member = .m1_prop return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } @available(iOS 15, *) var prop2: Int { get { let member: Member = .m2_prop2 return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_prop case m2_prop2 case m3_test case m4_test2 nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_prop, .m1_prop): return true case (.m2_prop2, .m2_prop2): return true case (.m3_test, .m3_test): return true case (.m4_test2, .m4_test2): return true default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_prop case m2_prop2 case m3_test case m4_test2 nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_prop, .m1_prop): return true case (.m2_prop2, .m2_prop2): return true case (.m3_test, .m3_test): return true case (.m4_test2, .m4_test2): return true default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } @available(iOS 15, *) nonisolated var prop: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m1_prop) } @available(iOS 15, *) nonisolated var prop2: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m2_prop2) } @available(iOS 15, *) nonisolated func test() -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m3_test) } @available(iOS 15, *) nonisolated func test2() -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m4_test2) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } @available(iOS 15, *) nonisolated var prop: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_prop) } @available(iOS 15, *) nonisolated var prop2: Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_prop2) } @available(iOS 15, *) nonisolated func test() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m3_test) } @available(iOS 15, *) nonisolated func test2() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m4_test2) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } @available(iOS 15, *) nonisolated var prop: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_prop) } @available(iOS 15, *) nonisolated var prop2: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_prop2) } @available(iOS 15, *) nonisolated func test() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m3_test) } @available(iOS 15, *) nonisolated func test2() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m4_test2) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/DocCommentsTests.swift ================================================ // // DocCommentsTests.swift // // // Created by Sagar Dagdu on 20/12/24. // import XCTest import MacroTesting final class DocCommentsTests: MockableMacroTestCase { func test_documentation_comments() { assertMacro { """ /// Protocol documentation /// Multiple lines @Mockable protocol Test { var foo: Int { get } } """ } expansion: { """ /// Protocol documentation /// Multiple lines protocol Test { var foo: Int { get } } #if MOCKING /// Protocol documentation /// Multiple lines final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } var foo: Int { get { let member: Member = .m1_foo return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m1_foo) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var foo: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/ExoticParameterTests.swift ================================================ // // ExoticParameterTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 21.. // import MacroTesting import XCTest final class ExoticParameterTests: MockableMacroTestCase { func test_inout_parameter() { assertMacro { """ @Mockable protocol Test { func modifyValue(_ value: inout Int) } """ } expansion: { """ protocol Test { func modifyValue(_ value: inout Int) } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func modifyValue(_ value: inout Int) { let member: Member = .m1_modifyValue(.value(value)) mocker.mock(member) { producer in let producer = try cast(producer) as (inout Int) -> Void return producer(&value) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_modifyValue(Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_modifyValue(let leftParam1), .m1_modifyValue(let rightParam1)): return leftParam1.match(rightParam1) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_modifyValue(Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_modifyValue(let leftParam1), .m1_modifyValue(let rightParam1)): return leftParam1.match(rightParam1) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func modifyValue(_ value: Parameter) -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m1_modifyValue(value)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func modifyValue(_ value: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_modifyValue(value)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func modifyValue(_ value: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_modifyValue(value)) } } } #endif """ } } func test_variadic_parameter() { assertMacro { """ @Mockable protocol Test { func printValues(_ values: Int...) } """ } expansion: { """ protocol Test { func printValues(_ values: Int...) } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func printValues(_ values: Int...) { let member: Member = .m1_printValues(.value(values)) mocker.mock(member) { producer in let producer = try cast(producer) as ([Int]) -> Void return producer(values) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_printValues(Parameter<[Int]>) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_printValues(let leftParam1), .m1_printValues(let rightParam1)): return leftParam1.match(rightParam1) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_printValues(Parameter<[Int]>) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_printValues(let leftParam1), .m1_printValues(let rightParam1)): return leftParam1.match(rightParam1) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func printValues(_ values: Parameter<[Int]>) -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m1_printValues(values)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func printValues(_ values: Parameter<[Int]>) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_printValues(values)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func printValues(_ values: Parameter<[Int]>) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_printValues(values)) } } } #endif """ } } func test_closure_parameter() { assertMacro { """ @Mockable protocol Test { func execute(operation: @escaping () throws -> Void) } """ } expansion: { """ protocol Test { func execute(operation: @escaping () throws -> Void) } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func execute(operation: @escaping () throws -> Void) { let member: Member = .m1_execute(operation: .value(operation)) mocker.mock(member) { producer in let producer = try cast(producer) as (@escaping () throws -> Void) -> Void return producer(operation) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_execute(operation: Parameter<() throws -> Void>) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_execute(operation: let leftOperation), .m1_execute(operation: let rightOperation)): return leftOperation.match(rightOperation) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_execute(operation: Parameter<() throws -> Void>) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_execute(operation: let leftOperation), .m1_execute(operation: let rightOperation)): return leftOperation.match(rightOperation) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func execute(operation: Parameter<() throws -> Void>) -> Mockable.FunctionReturnBuilder Void) -> Void> { .init(mocker, kind: .m1_execute(operation: operation)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func execute(operation: Parameter<() throws -> Void>) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_execute(operation: operation)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func execute(operation: Parameter<() throws -> Void>) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_execute(operation: operation)) } } } #endif """ } } func test_reserved_keyword_parameter() { assertMacro { """ @Mockable protocol Test { func foo(for: String) } """ } expansion: { """ protocol Test { func foo(for: String) } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo(for: String) { let member: Member = .m1_foo(for: .value(`for`)) mocker.mock(member) { producer in let producer = try cast(producer) as (String) -> Void return producer(`for`) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(for: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(for: let leftFor), .m1_foo(for: let rightFor)): return leftFor.match(rightFor) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(for: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(for: let leftFor), .m1_foo(for: let rightFor)): return leftFor.match(rightFor) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(for: Parameter) -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m1_foo(for: `for`)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(for: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo(for: `for`)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(for: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo(for: `for`)) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/FunctionEffectTests.swift ================================================ // // FunctionEffectTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 21.. // import MacroTesting import XCTest final class FunctionEffectTests: MockableMacroTestCase { func test_throwing_function() { assertMacro { """ @Mockable protocol Test { func returnsAndThrows() throws -> String func canThrowError() throws } """ } expansion: { """ protocol Test { func returnsAndThrows() throws -> String func canThrowError() throws } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func returnsAndThrows() throws -> String { let member: Member = .m1_returnsAndThrows return try mocker.mockThrowing(member) { producer in let producer = try cast(producer) as () throws -> String return try producer() } } func canThrowError() throws { let member: Member = .m2_canThrowError try mocker.mockThrowing(member) { producer in let producer = try cast(producer) as () throws -> Void return try producer() } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_returnsAndThrows case m2_canThrowError nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_returnsAndThrows, .m1_returnsAndThrows): return true case (.m2_canThrowError, .m2_canThrowError): return true default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_returnsAndThrows case m2_canThrowError nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_returnsAndThrows, .m1_returnsAndThrows): return true case (.m2_canThrowError, .m2_canThrowError): return true default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func returnsAndThrows() -> Mockable.ThrowingFunctionReturnBuilder String> { .init(mocker, kind: .m1_returnsAndThrows) } nonisolated func canThrowError() -> Mockable.ThrowingFunctionReturnBuilder Void> { .init(mocker, kind: .m2_canThrowError) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func returnsAndThrows() -> Mockable.ThrowingFunctionActionBuilder { .init(mocker, kind: .m1_returnsAndThrows) } nonisolated func canThrowError() -> Mockable.ThrowingFunctionActionBuilder { .init(mocker, kind: .m2_canThrowError) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func returnsAndThrows() -> Mockable.ThrowingFunctionVerifyBuilder { .init(mocker, kind: .m1_returnsAndThrows) } nonisolated func canThrowError() -> Mockable.ThrowingFunctionVerifyBuilder { .init(mocker, kind: .m2_canThrowError) } } } #endif """ } } func test_rethrowing_function() { assertMacro { """ @Mockable protocol Test { func execute(operation: @escaping () throws -> Void) rethrows } """ } expansion: { """ protocol Test { func execute(operation: @escaping () throws -> Void) rethrows } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func execute(operation: @escaping () throws -> Void) rethrows { let member: Member = .m1_execute(operation: .value(operation)) mocker.mock(member) { producer in let producer = try cast(producer) as (@escaping () throws -> Void) -> Void return producer(operation) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_execute(operation: Parameter<() throws -> Void>) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_execute(operation: let leftOperation), .m1_execute(operation: let rightOperation)): return leftOperation.match(rightOperation) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_execute(operation: Parameter<() throws -> Void>) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_execute(operation: let leftOperation), .m1_execute(operation: let rightOperation)): return leftOperation.match(rightOperation) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func execute(operation: Parameter<() throws -> Void>) -> Mockable.FunctionReturnBuilder Void) -> Void> { .init(mocker, kind: .m1_execute(operation: operation)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func execute(operation: Parameter<() throws -> Void>) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_execute(operation: operation)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func execute(operation: Parameter<() throws -> Void>) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_execute(operation: operation)) } } } #endif """ } } func test_async_function() { assertMacro { """ @Mockable protocol Test { func asyncFunction() async func asyncThrowingFunction() async throws func asyncParamFunction(param: @escaping () async throws -> Void) async throws } """ } expansion: { """ protocol Test { func asyncFunction() async func asyncThrowingFunction() async throws func asyncParamFunction(param: @escaping () async throws -> Void) async throws } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func asyncFunction() async { let member: Member = .m1_asyncFunction mocker.mock(member) { producer in let producer = try cast(producer) as () -> Void return producer() } } func asyncThrowingFunction() async throws { let member: Member = .m2_asyncThrowingFunction try mocker.mockThrowing(member) { producer in let producer = try cast(producer) as () throws -> Void return try producer() } } func asyncParamFunction(param: @escaping () async throws -> Void) async throws { let member: Member = .m3_asyncParamFunction(param: .value(param)) try mocker.mockThrowing(member) { producer in let producer = try cast(producer) as (@escaping () async throws -> Void) throws -> Void return try producer(param) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_asyncFunction case m2_asyncThrowingFunction case m3_asyncParamFunction(param: Parameter<() async throws -> Void>) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_asyncFunction, .m1_asyncFunction): return true case (.m2_asyncThrowingFunction, .m2_asyncThrowingFunction): return true case (.m3_asyncParamFunction(param: let leftParam), .m3_asyncParamFunction(param: let rightParam)): return leftParam.match(rightParam) default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_asyncFunction case m2_asyncThrowingFunction case m3_asyncParamFunction(param: Parameter<() async throws -> Void>) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_asyncFunction, .m1_asyncFunction): return true case (.m2_asyncThrowingFunction, .m2_asyncThrowingFunction): return true case (.m3_asyncParamFunction(param: let leftParam), .m3_asyncParamFunction(param: let rightParam)): return leftParam.match(rightParam) default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func asyncFunction() -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m1_asyncFunction) } nonisolated func asyncThrowingFunction() -> Mockable.ThrowingFunctionReturnBuilder Void> { .init(mocker, kind: .m2_asyncThrowingFunction) } nonisolated func asyncParamFunction(param: Parameter<() async throws -> Void>) -> Mockable.ThrowingFunctionReturnBuilder Void) throws -> Void> { .init(mocker, kind: .m3_asyncParamFunction(param: param)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func asyncFunction() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_asyncFunction) } nonisolated func asyncThrowingFunction() -> Mockable.ThrowingFunctionActionBuilder { .init(mocker, kind: .m2_asyncThrowingFunction) } nonisolated func asyncParamFunction(param: Parameter<() async throws -> Void>) -> Mockable.ThrowingFunctionActionBuilder { .init(mocker, kind: .m3_asyncParamFunction(param: param)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func asyncFunction() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_asyncFunction) } nonisolated func asyncThrowingFunction() -> Mockable.ThrowingFunctionVerifyBuilder { .init(mocker, kind: .m2_asyncThrowingFunction) } nonisolated func asyncParamFunction(param: Parameter<() async throws -> Void>) -> Mockable.ThrowingFunctionVerifyBuilder { .init(mocker, kind: .m3_asyncParamFunction(param: param)) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/GenericFunctionTests.swift ================================================ // // GenericFunctionTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 21.. // import MacroTesting import XCTest final class GenericFunctionTests: MockableMacroTestCase { func test_deeply_nested_generic_parameter() { assertMacro { """ @Mockable protocol Test { func foo(item: (Array<[(Set, String)]>, Int)) } """ } expansion: { """ protocol Test { func foo(item: (Array<[(Set, String)]>, Int)) } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo(item: (Array<[(Set, String)]>, Int)) { let member: Member = .m1_foo(item: .generic(item)) mocker.mock(member) { producer in let producer = try cast(producer) as ((Array<[(Set, String)]>, Int)) -> Void return producer(item) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(item: let leftItem), .m1_foo(item: let rightItem)): return leftItem.match(rightItem) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo(item: let leftItem), .m1_foo(item: let rightItem)): return leftItem.match(rightItem) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter<(Array<[(Set, String)]>, Int)>) -> Mockable.FunctionReturnBuilder, String)]>, Int)) -> Void> { .init(mocker, kind: .m1_foo(item: item.eraseToGenericValue())) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter<(Array<[(Set, String)]>, Int)>) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo(item: item.eraseToGenericValue())) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo(item: Parameter<(Array<[(Set, String)]>, Int)>) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo(item: item.eraseToGenericValue())) } } } #endif """ } } func test_generic_param_and_return() { assertMacro { """ @Mockable protocol Test { func genericFunc(item: T) -> V } """ } expansion: { """ protocol Test { func genericFunc(item: T) -> V } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func genericFunc(item: T) -> V { let member: Member = .m1_genericFunc(item: .generic(item)) return mocker.mock(member) { producer in let producer = try cast(producer) as (T) -> V return producer(item) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_genericFunc(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_genericFunc(item: let leftItem), .m1_genericFunc(item: let rightItem)): return leftItem.match(rightItem) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_genericFunc(item: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_genericFunc(item: let leftItem), .m1_genericFunc(item: let rightItem)): return leftItem.match(rightItem) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func genericFunc(item: Parameter) -> Mockable.FunctionReturnBuilder V> { .init(mocker, kind: .m1_genericFunc(item: item.eraseToGenericValue())) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func genericFunc(item: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_genericFunc(item: item.eraseToGenericValue())) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func genericFunc(item: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_genericFunc(item: item.eraseToGenericValue())) } } } #endif """ } } func test_constrained_generic_params() { assertMacro { """ @Mockable protocol Test { func method1( p1: T, p2: E, p3: C, p4: I ) where E: Equatable, E: Hashable, C: Codable } """ } expansion: { """ protocol Test { func method1( p1: T, p2: E, p3: C, p4: I ) where E: Equatable, E: Hashable, C: Codable } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func method1( p1: T, p2: E, p3: C, p4: I ) where E: Equatable, E: Hashable, C: Codable { let member: Member = .m1_method1(p1: .generic( p1), p2: .generic(p2), p3: .generic(p3), p4: .generic(p4)) mocker.mock(member) { producer in let producer = try cast(producer) as (T, E, C, I) -> Void return producer(p1, p2, p3, p4) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_method1(p1: Parameter, p2: Parameter, p3: Parameter, p4: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_method1(p1: let leftP1, p2: let leftP2, p3: let leftP3, p4: let leftP4), .m1_method1(p1: let rightP1, p2: let rightP2, p3: let rightP3, p4: let rightP4)): return leftP1.match(rightP1) && leftP2.match(rightP2) && leftP3.match(rightP3) && leftP4.match(rightP4) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_method1(p1: Parameter, p2: Parameter, p3: Parameter, p4: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_method1(p1: let leftP1, p2: let leftP2, p3: let leftP3, p4: let leftP4), .m1_method1(p1: let rightP1, p2: let rightP2, p3: let rightP3, p4: let rightP4)): return leftP1.match(rightP1) && leftP2.match(rightP2) && leftP3.match(rightP3) && leftP4.match(rightP4) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func method1( p1: Parameter, p2: Parameter, p3: Parameter, p4: Parameter) -> Mockable.FunctionReturnBuilder Void> where E: Equatable, E: Hashable, C: Codable { .init(mocker, kind: .m1_method1(p1: p1.eraseToGenericValue(), p2: p2.eraseToGenericValue(), p3: p3.eraseToGenericValue(), p4: p4.eraseToGenericValue())) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func method1( p1: Parameter, p2: Parameter, p3: Parameter, p4: Parameter) -> Mockable.FunctionActionBuilder where E: Equatable, E: Hashable, C: Codable { .init(mocker, kind: .m1_method1(p1: p1.eraseToGenericValue(), p2: p2.eraseToGenericValue(), p3: p3.eraseToGenericValue(), p4: p4.eraseToGenericValue())) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func method1( p1: Parameter, p2: Parameter, p3: Parameter, p4: Parameter) -> Mockable.FunctionVerifyBuilder where E: Equatable, E: Hashable, C: Codable { .init(mocker, kind: .m1_method1(p1: p1.eraseToGenericValue(), p2: p2.eraseToGenericValue(), p3: p3.eraseToGenericValue(), p4: p4.eraseToGenericValue())) } } } #endif """ } } func test_parametrized_protocol_requirements_in_variables() { assertMacro { """ @Mockable protocol Test { var prop: any SomeProtocol { get } } """ } expansion: { """ protocol Test { var prop: any SomeProtocol { get } } #if MOCKING @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } var prop: any SomeProtocol { get { let member: Member = .m1_prop return mocker.mock(member) { producer in let producer = try cast(producer) as () -> any SomeProtocol return producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_prop nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_prop, .m1_prop): return true } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_prop nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_prop, .m1_prop): return true } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var prop: Mockable.FunctionReturnBuilder, () -> any SomeProtocol> { .init(mocker, kind: .m1_prop) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var prop: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_prop) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var prop: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_prop) } } } #endif """ } } func test_nested_parametrized_protocol_requirements_in_variables() { assertMacro { """ @Mockable protocol Test { var prop: Parent>> { get } } """ } expansion: { """ protocol Test { var prop: Parent>> { get } } #if MOCKING @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } var prop: Parent>> { get { let member: Member = .m1_prop return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Parent>> return producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_prop nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_prop, .m1_prop): return true } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_prop nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_prop, .m1_prop): return true } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var prop: Mockable.FunctionReturnBuilder>>, () -> Parent>>> { .init(mocker, kind: .m1_prop) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var prop: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_prop) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var prop: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_prop) } } } #endif """ } } func test_parametrized_protocol_requirements_in_functions() { assertMacro { """ @Mockable protocol Test { func foo() -> any SomeProtocol } """ } expansion: { """ protocol Test { func foo() -> any SomeProtocol } #if MOCKING @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo() -> any SomeProtocol { let member: Member = .m1_foo return mocker.mock(member) { producer in let producer = try cast(producer) as () -> any SomeProtocol return producer() } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionReturnBuilder, () -> any SomeProtocol> { .init(mocker, kind: .m1_foo) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } } } #endif """ } } func test_nested_parametrized_protocol_requirements_in_functions() { assertMacro { """ @Mockable protocol Test { func foo() -> Parent>> } """ } expansion: { """ protocol Test { func foo() -> Parent>> } #if MOCKING @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo() -> Parent>> { let member: Member = .m1_foo return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Parent>> return producer() } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionReturnBuilder>>, () -> Parent>>> { .init(mocker, kind: .m1_foo) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/InheritedTypeMappingTests.swift ================================================ // // InheritedTypeMappingTests.swift // Mockable // // Created by Kolos Foltanyi on 27/09/2024. // import MacroTesting import XCTest final class InheritedTypeMappingTests: MockableMacroTestCase { func test_nsobject_conformance() { assertMacro { """ @Mockable protocol TestObject: NSObjectProtocol { func foo() } """ } expansion: { """ protocol TestObject: NSObjectProtocol { func foo() } #if MOCKING final class MockTestObject: NSObject, TestObject, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo() { let member: Member = .m1_foo mocker.mock(member) { producer in let producer = try cast(producer) as () -> Void return producer() } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_foo, .m1_foo): return true } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionReturnBuilder Void> { .init(mocker, kind: .m1_foo) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_foo) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func foo() -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_foo) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/InitRequirementTests.swift ================================================ // // InitRequirementTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 23.. // import MacroTesting import XCTest final class InitRequirementTests: MockableMacroTestCase { func test_init_requirement() { assertMacro { """ @Mockable protocol Test { init() init(name: String) } """ } expansion: { """ protocol Test { init() init(name: String) } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } init() { } init(name: String) { } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { nonisolated func match(_ other: Member) -> Bool { switch (self, other) { } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { nonisolated func match(_ other: Member) -> Bool { switch (self, other) { } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } } } #endif """ } } func test_multiple_init_requirement() { assertMacro { """ @Mockable protocol Test { init?() async throws init(name: String) init(name value: String, _ index: Int) } """ } expansion: { """ protocol Test { init?() async throws init(name: String) init(name value: String, _ index: Int) } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } init?() async throws { } init(name: String) { } init(name value: String, _ index: Int) { } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { nonisolated func match(_ other: Member) -> Bool { switch (self, other) { } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { nonisolated func match(_ other: Member) -> Bool { switch (self, other) { } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/NameCollisionTests.swift ================================================ // // NameCollisionTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 21.. // import MacroTesting import XCTest final class NameCollisionTests: MockableMacroTestCase { func test_same_name_different_type_params() { assertMacro { """ @Mockable protocol Test { func fetchData(for name: Int) -> String func fetchData(for name: String) -> String } """ } expansion: { """ protocol Test { func fetchData(for name: Int) -> String func fetchData(for name: String) -> String } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func fetchData(for name: Int) -> String { let member: Member = .m1_fetchData(for: .value(name)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Int) -> String return producer(name) } } func fetchData(for name: String) -> String { let member: Member = .m2_fetchData(for: .value(name)) return mocker.mock(member) { producer in let producer = try cast(producer) as (String) -> String return producer(name) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_fetchData(for: Parameter) case m2_fetchData(for: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_fetchData(for: let leftFor), .m1_fetchData(for: let rightFor)): return leftFor.match(rightFor) case (.m2_fetchData(for: let leftFor), .m2_fetchData(for: let rightFor)): return leftFor.match(rightFor) default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_fetchData(for: Parameter) case m2_fetchData(for: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_fetchData(for: let leftFor), .m1_fetchData(for: let rightFor)): return leftFor.match(rightFor) case (.m2_fetchData(for: let leftFor), .m2_fetchData(for: let rightFor)): return leftFor.match(rightFor) default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func fetchData(for name: Parameter) -> Mockable.FunctionReturnBuilder String> { .init(mocker, kind: .m1_fetchData(for: name)) } nonisolated func fetchData(for name: Parameter) -> Mockable.FunctionReturnBuilder String> { .init(mocker, kind: .m2_fetchData(for: name)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func fetchData(for name: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_fetchData(for: name)) } nonisolated func fetchData(for name: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_fetchData(for: name)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func fetchData(for name: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_fetchData(for: name)) } nonisolated func fetchData(for name: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_fetchData(for: name)) } } } #endif """ } } func test_same_name_different_name_params() { assertMacro { """ @Mockable protocol Test { func fetchData(forA name: String) -> String func fetchData(forB name: String) -> String } """ } expansion: { """ protocol Test { func fetchData(forA name: String) -> String func fetchData(forB name: String) -> String } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func fetchData(forA name: String) -> String { let member: Member = .m1_fetchData(forA: .value(name)) return mocker.mock(member) { producer in let producer = try cast(producer) as (String) -> String return producer(name) } } func fetchData(forB name: String) -> String { let member: Member = .m2_fetchData(forB: .value(name)) return mocker.mock(member) { producer in let producer = try cast(producer) as (String) -> String return producer(name) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_fetchData(forA: Parameter) case m2_fetchData(forB: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_fetchData(forA: let leftForA), .m1_fetchData(forA: let rightForA)): return leftForA.match(rightForA) case (.m2_fetchData(forB: let leftForB), .m2_fetchData(forB: let rightForB)): return leftForB.match(rightForB) default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_fetchData(forA: Parameter) case m2_fetchData(forB: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_fetchData(forA: let leftForA), .m1_fetchData(forA: let rightForA)): return leftForA.match(rightForA) case (.m2_fetchData(forB: let leftForB), .m2_fetchData(forB: let rightForB)): return leftForB.match(rightForB) default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func fetchData(forA name: Parameter) -> Mockable.FunctionReturnBuilder String> { .init(mocker, kind: .m1_fetchData(forA: name)) } nonisolated func fetchData(forB name: Parameter) -> Mockable.FunctionReturnBuilder String> { .init(mocker, kind: .m2_fetchData(forB: name)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func fetchData(forA name: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_fetchData(forA: name)) } nonisolated func fetchData(forB name: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_fetchData(forB: name)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func fetchData(forA name: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_fetchData(forA: name)) } nonisolated func fetchData(forB name: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_fetchData(forB: name)) } } } #endif """ } } func test_reserved_keyword() { assertMacro { """ @Mockable protocol Test { func `repeat`(param: Bool) -> String } """ } expansion: { """ protocol Test { func `repeat`(param: Bool) -> String } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func `repeat`(param: Bool) -> String { let member: Member = .m1_repeat(param: .value(param)) return mocker.mock(member) { producer in let producer = try cast(producer) as (Bool) -> String return producer(param) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_repeat(param: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_repeat(param: let leftParam), .m1_repeat(param: let rightParam)): return leftParam.match(rightParam) } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_repeat(param: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_repeat(param: let leftParam), .m1_repeat(param: let rightParam)): return leftParam.match(rightParam) } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func `repeat`(param: Parameter) -> Mockable.FunctionReturnBuilder String> { .init(mocker, kind: .m1_repeat(param: param)) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func `repeat`(param: Parameter) -> Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_repeat(param: param)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated func `repeat`(param: Parameter) -> Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_repeat(param: param)) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/PropertyRequirementTests.swift ================================================ // // PropertyRequirementTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 21.. // import MacroTesting import XCTest final class PropertyRequirementTests: MockableMacroTestCase { func test_computed_property() { assertMacro { """ @Mockable protocol Test { var computedInt: Int { get } var computedString: String! { get } } """ } expansion: { """ protocol Test { var computedInt: Int { get } var computedString: String! { get } } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } var computedInt: Int { get { let member: Member = .m1_computedInt return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } } var computedString: String! { get { let member: Member = .m2_computedString return mocker.mock(member) { producer in let producer = try cast(producer) as () -> String return producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_computedInt case m2_computedString nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_computedInt, .m1_computedInt): return true case (.m2_computedString, .m2_computedString): return true default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_computedInt case m2_computedString nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_computedInt, .m1_computedInt): return true case (.m2_computedString, .m2_computedString): return true default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var computedInt: Mockable.FunctionReturnBuilder Int> { .init(mocker, kind: .m1_computedInt) } nonisolated var computedString: Mockable.FunctionReturnBuilder String> { .init(mocker, kind: .m2_computedString) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var computedInt: Mockable.FunctionActionBuilder { .init(mocker, kind: .m1_computedInt) } nonisolated var computedString: Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_computedString) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var computedInt: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m1_computedInt) } nonisolated var computedString: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_computedString) } } } #endif """ } } func test_mutable_property() { assertMacro { """ @Mockable protocol Test { var mutableInt: Int { get set } var mutableString: String { get set } } """ } expansion: { """ protocol Test { var mutableInt: Int { get set } var mutableString: String { get set } } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } var mutableInt: Int { get { let member: Member = .m1_get_mutableInt return mocker.mock(member) { producer in let producer = try cast(producer) as () -> Int return producer() } } set { let member: Member = .m1_set_mutableInt(newValue: .value(newValue)) mocker.addInvocation(for: member) mocker.performActions(for: member) } } var mutableString: String { get { let member: Member = .m2_get_mutableString return mocker.mock(member) { producer in let producer = try cast(producer) as () -> String return producer() } } set { let member: Member = .m2_set_mutableString(newValue: .value(newValue)) mocker.addInvocation(for: member) mocker.performActions(for: member) } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_get_mutableInt case m1_set_mutableInt(newValue: Parameter) case m2_get_mutableString case m2_set_mutableString(newValue: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_get_mutableInt, .m1_get_mutableInt): return true case (.m1_set_mutableInt(newValue: let leftNewValue), .m1_set_mutableInt(newValue: let rightNewValue)): return leftNewValue.match(rightNewValue) case (.m2_get_mutableString, .m2_get_mutableString): return true case (.m2_set_mutableString(newValue: let leftNewValue), .m2_set_mutableString(newValue: let rightNewValue)): return leftNewValue.match(rightNewValue) default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_get_mutableInt case m1_set_mutableInt(newValue: Parameter) case m2_get_mutableString case m2_set_mutableString(newValue: Parameter) nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_get_mutableInt, .m1_get_mutableInt): return true case (.m1_set_mutableInt(newValue: let leftNewValue), .m1_set_mutableInt(newValue: let rightNewValue)): return leftNewValue.match(rightNewValue) case (.m2_get_mutableString, .m2_get_mutableString): return true case (.m2_set_mutableString(newValue: let leftNewValue), .m2_set_mutableString(newValue: let rightNewValue)): return leftNewValue.match(rightNewValue) default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var mutableInt: Mockable.PropertyReturnBuilder { .init(mocker, kind: .m1_get_mutableInt) } nonisolated var mutableString: Mockable.PropertyReturnBuilder { .init(mocker, kind: .m2_get_mutableString) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } func mutableInt(newValue: Parameter = .any) -> Mockable.PropertyActionBuilder { .init(mocker, kind: .m1_get_mutableInt, setKind: .m1_set_mutableInt(newValue: newValue)) } func mutableString(newValue: Parameter = .any) -> Mockable.PropertyActionBuilder { .init(mocker, kind: .m2_get_mutableString, setKind: .m2_set_mutableString(newValue: newValue)) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } func mutableInt(newValue: Parameter = .any) -> Mockable.PropertyVerifyBuilder { .init(mocker, kind: .m1_get_mutableInt, setKind: .m1_set_mutableInt(newValue: newValue)) } func mutableString(newValue: Parameter = .any) -> Mockable.PropertyVerifyBuilder { .init(mocker, kind: .m2_get_mutableString, setKind: .m2_set_mutableString(newValue: newValue)) } } } #endif """ } } func test_async_throwing_property() { assertMacro { """ @Mockable protocol Test { var throwingProperty: Int { get throws } var asyncProperty: String { get async } var asyncThrowingProperty: String { get async throws } } """ } expansion: { """ protocol Test { var throwingProperty: Int { get throws } var asyncProperty: String { get async } var asyncThrowingProperty: String { get async throws } } #if MOCKING final class MockTest: Test, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } var throwingProperty: Int { get throws { let member: Member = .m1_throwingProperty return try mocker.mockThrowing(member) { producer in let producer = try cast(producer) as () throws -> Int return try producer() } } } var asyncProperty: String { get async { let member: Member = .m2_asyncProperty return mocker.mock(member) { producer in let producer = try cast(producer) as () -> String return producer() } } } var asyncThrowingProperty: String { get async throws { let member: Member = .m3_asyncThrowingProperty return try mocker.mockThrowing(member) { producer in let producer = try cast(producer) as () throws -> String return try producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_throwingProperty case m2_asyncProperty case m3_asyncThrowingProperty nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_throwingProperty, .m1_throwingProperty): return true case (.m2_asyncProperty, .m2_asyncProperty): return true case (.m3_asyncThrowingProperty, .m3_asyncThrowingProperty): return true default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_throwingProperty case m2_asyncProperty case m3_asyncThrowingProperty nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_throwingProperty, .m1_throwingProperty): return true case (.m2_asyncProperty, .m2_asyncProperty): return true case (.m3_asyncThrowingProperty, .m3_asyncThrowingProperty): return true default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var throwingProperty: Mockable.ThrowingFunctionReturnBuilder Int> { .init(mocker, kind: .m1_throwingProperty) } nonisolated var asyncProperty: Mockable.FunctionReturnBuilder String> { .init(mocker, kind: .m2_asyncProperty) } nonisolated var asyncThrowingProperty: Mockable.ThrowingFunctionReturnBuilder String> { .init(mocker, kind: .m3_asyncThrowingProperty) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var throwingProperty: Mockable.ThrowingFunctionActionBuilder { .init(mocker, kind: .m1_throwingProperty) } nonisolated var asyncProperty: Mockable.FunctionActionBuilder { .init(mocker, kind: .m2_asyncProperty) } nonisolated var asyncThrowingProperty: Mockable.ThrowingFunctionActionBuilder { .init(mocker, kind: .m3_asyncThrowingProperty) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var throwingProperty: Mockable.ThrowingFunctionVerifyBuilder { .init(mocker, kind: .m1_throwingProperty) } nonisolated var asyncProperty: Mockable.FunctionVerifyBuilder { .init(mocker, kind: .m2_asyncProperty) } nonisolated var asyncThrowingProperty: Mockable.ThrowingFunctionVerifyBuilder { .init(mocker, kind: .m3_asyncThrowingProperty) } } } #endif """ } } } ================================================ FILE: Tests/MockableMacroTests/TypedThrowsTests_Swift6.swift ================================================ // // TypedThrowsTests.swift // // // Created by Kolos Foltanyi on 08/07/2024. // import MacroTesting import XCTest import SwiftSyntax #if canImport(SwiftSyntax600) final class TypedThrowsTests_Swift6: MockableMacroTestCase { func test_typed_throws_requirement() { assertMacro { """ @Mockable protocol TypedErrorProtocol { func foo() throws(ExampleError) var baz: String { get throws(ExampleError) } } """ } expansion: { """ protocol TypedErrorProtocol { func foo() throws(ExampleError) var baz: String { get throws(ExampleError) } } #if MOCKING final class MockTypedErrorProtocol: TypedErrorProtocol, Mockable.MockableService { typealias Mocker = Mockable.Mocker private let mocker = Mocker() @available(*, deprecated, message: "Use given(_ service:) instead. ") nonisolated var _given: ReturnBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use when(_ service:) instead. ") nonisolated var _when: ActionBuilder { .init(mocker: mocker) } @available(*, deprecated, message: "Use verify(_ service:) instead. ") nonisolated var _verify: VerifyBuilder { .init(mocker: mocker) } nonisolated func reset(_ scopes: Set = .all) { mocker.reset(scopes: scopes) } nonisolated init(policy: Mockable.MockerPolicy? = nil) { if let policy { mocker.policy = policy } } func foo() throws(ExampleError) { let member: Member = .m2_foo try mocker.mockThrowing(member, error: ExampleError.self) { producer in let producer = try cast(producer) as () throws -> Void return try producer() } } var baz: String { get throws(ExampleError) { let member: Member = .m1_baz return try mocker.mockThrowing(member, error: ExampleError.self) { producer in let producer = try cast(producer) as () throws -> String return try producer() } } } #if swift(>=6.1) nonisolated enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_baz case m2_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_baz, .m1_baz): return true case (.m2_foo, .m2_foo): return true default: return false } } } #else enum Member: Mockable.Matchable, Mockable.CaseIdentifiable, Swift.Sendable { case m1_baz case m2_foo nonisolated func match(_ other: Member) -> Bool { switch (self, other) { case (.m1_baz, .m1_baz): return true case (.m2_foo, .m2_foo): return true default: return false } } } #endif struct ReturnBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var baz: Mockable.ThrowingFunctionReturnBuilder String> { .init(mocker, kind: .m1_baz) } nonisolated func foo() -> Mockable.ThrowingFunctionReturnBuilder Void> { .init(mocker, kind: .m2_foo) } } struct ActionBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var baz: Mockable.ThrowingFunctionActionBuilder { .init(mocker, kind: .m1_baz) } nonisolated func foo() -> Mockable.ThrowingFunctionActionBuilder { .init(mocker, kind: .m2_foo) } } struct VerifyBuilder: Mockable.Builder { private let mocker: Mocker nonisolated init(mocker: Mocker) { self.mocker = mocker } nonisolated var baz: Mockable.ThrowingFunctionVerifyBuilder { .init(mocker, kind: .m1_baz) } nonisolated func foo() -> Mockable.ThrowingFunctionVerifyBuilder { .init(mocker, kind: .m2_foo) } } } #endif """ } } } #endif ================================================ FILE: Tests/MockableMacroTests/Utils/MockableMacroTestCase.swift ================================================ // // MockableMacroTestCase.swift // // // Created by Kolos Foltanyi on 2023. 11. 21.. // import Foundation import SwiftSyntaxMacros import SwiftSyntaxMacrosTestSupport import MacroTesting import XCTest #if canImport(MockableMacro) import MockableMacro #endif class MockableMacroTestCase: XCTestCase { override func invokeTest() { #if canImport(MockableMacro) withMacroTesting(record: false, macros: ["Mockable": MockableMacro.self]) { super.invokeTest() } #else fatalError("Macro tests can only be run on the host platform!") #endif } } ================================================ FILE: Tests/MockableTests/ActionTests.swift ================================================ // // ActionTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 22.. // import XCTest import Mockable final class ActionTests: XCTestCase { // MARK: Properties private let mock = MockTestService() // MARK: Overrides override func tearDown() { mock.reset() Matcher.reset() } // MARK: Tests func test_givenActionRegistered_whenMockCalled_actionIsCalled() throws { var called = false given(mock).getUser(for: .any).willReturn(.test1) when(mock).getUser(for: .any).perform { called = true } _ = try mock.getUser(for: UUID()) XCTAssertTrue(called) } func test_givenActionRegisteredForMutableProperty_whenMockCalled_relatedActionsAreCalled() { var getCalled = false var setCalled = false given(mock).name.willReturn("") when(mock) .name().performOnGet { getCalled = true } .name().performOnSet { setCalled = true } _ = mock.name mock.name = "" XCTAssertTrue(getCalled) XCTAssertTrue(setCalled) } func test_givenParameterConditionedActions_whenMockCalled_onlyCallsActionWithMatchingParameter() throws { var firstCalled = false var secondCalled = false let id1 = UUID() let id2 = UUID() given(mock).getUser(for: .any).willReturn(.test1) when(mock) .getUser(for: .value(id1)).perform { firstCalled = true } .getUser(for: .value(id2)).perform { secondCalled = true } _ = try mock.getUser(for: id2) XCTAssertFalse(firstCalled) XCTAssertTrue(secondCalled) } func test_givenMultipleActions_whenMockCalled_allActionsAreCalled() throws { var firstCalled = false var secondCalled = false var thirdCalled = false given(mock).getUser(for: .any).willReturn(.test1) when(mock) .getUser(for: .any).perform { firstCalled = true } .getUser(for: .any).perform { secondCalled = true } .getUser(for: .any).perform { thirdCalled = true } _ = try mock.getUser(for: UUID()) XCTAssertTrue(firstCalled) XCTAssertTrue(secondCalled) XCTAssertTrue(thirdCalled) } func test_givenGenericActionsRegistered_whenMockCalled_actionWithMatchingTypeCalled() { var firstCalled = false var secondCalled = false var thirdCalled = false let id = UUID() given(mock) .delete(for: .value("id")).willReturn(1) .delete(for: .value(id)).willReturn(2) when(mock) .delete(for: .value("id")).perform { firstCalled = true } .delete(for: .value(id)).perform { secondCalled = true } .delete(for: .value("id")).perform { thirdCalled = true } _ = mock.delete(for: id) XCTAssertFalse(firstCalled) XCTAssertTrue(secondCalled) XCTAssertFalse(thirdCalled) } func test_givenGenericParamAndReturnFunc_whenActionUsed_onlyParamIsInfered() { var called = false given(mock) .retrieveItem(item: Parameter.any) .willReturn(0) when(mock) .retrieveItem(item: Parameter.any) .perform { called = true } let _: Int = mock.retrieveItem(item: 0) XCTAssertTrue(called) } } ================================================ FILE: Tests/MockableTests/BuildTests.swift ================================================ // // TestProtocol.swift // // // Created by Kolos Foltanyi on 2023. 11. 21.. // import Mockable @Mockable protocol TestAssociatedTypes where Item2: Identifiable { associatedtype Item1 associatedtype Item2: Equatable, Hashable associatedtype Item3 where Item3: Equatable, Item3: Hashable func foo(item1: Item1) -> Item1 func foo(item2: Item2) -> Item2 func foo(item3: Item3) -> Item3 } @Mockable protocol TestExoticParameters { func modifyValue(_ value: inout Int) func printValues(_ values: Int...) func execute(operation: @escaping () throws -> Void) } @Mockable protocol TestFunctionEffects { func canThrowError() throws func returnsAndThrows() throws -> String func call(operation: @escaping () throws -> Void) rethrows func asyncFunction() async func asyncThrowingFunction() async throws func asyncParamFunction(param: @escaping () async throws -> Void) async nonisolated func nonisolatedFunction() async } @Mockable protocol TestGenericFunctions { func foo(item: (Array<[(Set, String)]>, Int)) func genericFunc(item: T) -> V func getInts() -> any Collection func method( prop1: T, prop2: E, prop3: C, prop4: I ) where E: Equatable, E: Hashable, C: Codable } @Mockable protocol TestNameCollisions { func fetchData(for name: Int) -> String func fetchData(for name: String) -> String func fetchData(forA name: String) -> String func fetchData(forB name: String) -> String func `repeat`(param: Bool) -> Int } @Mockable protocol TestPropertyRequirements { var computedInt: Int { get } var computedString: String { get } var mutableInt: Int { get set } var mutableUnwrappedString: String! { get set } var throwingProperty: Int { get throws } var asyncProperty: String { get async } var asyncThrowingProperty: String { get async throws } nonisolated var nonisolatedProperty: String { get set } } @Mockable protocol TestInitRequirements { init?() async throws init(index: Int) init(name value: String, index: Int) } @Mockable protocol TestAttributes { @available(iOS 16, *) init(attributed: String) @available(iOS 16, *) var attributedProp: Int { get } @available(iOS 16, *) func attributedTest() } #if canImport(Foundation) import Foundation @Mockable protocol TestNSObject: NSObjectProtocol { func foo(param: Int) -> String } #endif @Mockable protocol TestActorConformance: Actor { func foo(param: Int) -> String } @Mockable @MainActor protocol TestGlobalActor { func foo(param: Int) -> String } @Mockable protocol TestSendable: Sendable { func foo(param: Int) -> String } @Mockable public protocol TestReservedKeyword { func example(for: String) async throws -> String } #if swift(>=6.1) @Mockable nonisolated protocol NonisolatedProtocol { func foo() } #endif @Mockable protocol AttributedRequirementProtocol { #if swift(>=0) @available(*, deprecated) #endif func foo() } ================================================ FILE: Tests/MockableTests/ErrorShadowingCompileTests.swift ================================================ import XCTest import Mockable private typealias Error = Int @Mockable private protocol ShadowedErrorTypealiasService { func throwingMethod() throws } final class ErrorShadowingCompileTests: XCTestCase { func test_shadowed_error_typealias_allows_macro_expansion() { _ = MockShadowedErrorTypealiasService() } } ================================================ FILE: Tests/MockableTests/GivenTests.swift ================================================ // // GivenTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 22.. // import XCTest import Mockable final class GivenTests: XCTestCase { // MARK: Properties private var mock = MockTestService() // MARK: Overrides override func tearDown() { mock.reset() Matcher.reset() } // MARK: Tests func test_givenReturnValueRegistered_whenMockCalled_valueReturned() throws { given(mock) .getUser(for: .any).willReturn(.test1) .getUser(for: .any).willProduce { _ in .test2 } let user1 = try mock.getUser(for: UUID()) let user2 = try mock.getUser(for: UUID()) XCTAssertEqual(user1, .test1) XCTAssertEqual(user2, .test2) } func test_givenReturnValueRegisteredForMutableProperty_whenMockCalled_valueReturned() { let test1 = "name" let test2 = "name" given(mock) .name.willReturn(test1) .name.willProduce { test2 } let name1 = mock.name let name2 = mock.name XCTAssertEqual(name1, test1) XCTAssertEqual(name2, test2) } func test_givenGenericReturnValueRegistered_whenMockCalled_valueReturned() { let red = "red" given(mock).getFavoriteColor().willReturn(red) let color = mock.getFavoriteColor() XCTAssertEqual(color, red) } func test_givenParameterConditionedReturn_whenMockCalled_onlyReturnsForMatchingParameters() throws { let id1 = UUID() let id2 = UUID() given(mock) .getUser(for: .value(id1)).willReturn(User.test1) .getUser(for: .value(id2)).willReturn(User.test2) .getUsers(for: .value([id1])).willReturn([User.test1]) .getUsers(for: .value([id2])).willReturn([User.test2]) let testUser1 = try mock.getUser(for: id1) let testUser2 = try mock.getUser(for: id2) let testUsers1 = mock.getUsers(for: id1) let testUsers2 = mock.getUsers(for: id2) XCTAssertEqual(testUser1, User.test1) XCTAssertEqual(testUser2, User.test2) XCTAssertEqual(testUsers1, [User.test1]) XCTAssertEqual(testUsers2, [User.test2]) } func test_givenReturnWithParamComparator_whenMockCalled_valueReturnedToMatchingParameters() { let validIds = [UUID(), UUID()] let invalidIds = [UUID(), UUID()] let user: User = .test1 let error: UserError = .notFound given(mock) .getUser(for: .matching { validIds.contains($0) }).willReturn(user) .getUser(for: .matching { invalidIds.contains($0) }).willThrow(error) for id in invalidIds { XCTAssertThrowsError(try mock.getUser(for: id)) } for id in validIds { XCTAssertEqual(try mock.getUser(for: id), .test1) } } func test_givenMultipleReturn_whenMockCalled_valuesAreReturnedInOrder() throws { given(mock) .getUser(for: .any).willReturn(.test1) .getUser(for: .any).willReturn(.test2) .getUser(for: .any).willReturn(.test3) let testUser1 = try mock.getUser(for: UUID()) let testUser2 = try mock.getUser(for: UUID()) let testUser3 = try mock.getUser(for: UUID()) let last = try mock.getUser(for: UUID()) XCTAssertEqual(testUser1, .test1) XCTAssertEqual(testUser2, .test2) XCTAssertEqual(testUser3, .test3) XCTAssertEqual(last, .test3) } func test_givenGenericReturnsRegistered_whenMockCalled_valueWithMatchingTypeReturned() { let stringValue = "value1" let intValue = 1234 let doubleValue: Double = 2.5 given(mock) .getUserAndValue(for: .any, value: .any).willReturn((.test1, stringValue)) .getUserAndValue(for: .any, value: .any).willReturn((.test2, intValue)) .getUserAndValue(for: .any, value: .any).willReturn((.test3, doubleValue)) let (test1, value1): (User, String) = mock.getUserAndValue(for: UUID(), value: "") let (test2, value2): (User, Int) = mock.getUserAndValue(for: UUID(), value: 0) let (test3, value3): (User, Double) = mock.getUserAndValue(for: UUID(), value: 0.5) XCTAssertEqual(test1, .test1) XCTAssertEqual(value1, stringValue) XCTAssertEqual(test2, .test2) XCTAssertEqual(value2, intValue) XCTAssertEqual(test3, .test3) XCTAssertEqual(value3, doubleValue) } func test_givenVoidProducerRegistered_whenMockCalled_producerUsedToProduceResult() { given(mock).print().willProduce { throw UserError.notFound } XCTAssertThrowsError(try mock.print()) } func test_givenGenericProducers_whenMockCalled_matchingProducerIsUsedToReturnValue() { let stringValue = "value1" let intValue = 1234 given(mock) .retrieve().willProduce { stringValue } .retrieve().willProduce { intValue } let test: Int = mock.retrieve() XCTAssertEqual(test, intValue) } func test_givenError_whenMockCalled_errorIsThrown() async { given(mock).setUser(user: .any).willThrow(UserError.notFound) do { _ = try await mock.setUser(user: .test1) } catch let error as UserError { XCTAssertEqual(error, UserError.notFound) } catch { XCTFail("Unexpected error: \(error.localizedDescription)") } } func test_givenThrowingProducer_whenMockCalled_errorIsThrown() async { given(mock) .setUser(user: .any).willProduce { _ in throw UserError.notFound } do { _ = try await mock.setUser(user: .test1) } catch let error as UserError { XCTAssertEqual(error, UserError.notFound) } catch { XCTFail("Unexpected error: \(error.localizedDescription)") } } func test_givenMultipleGivenClauses_whenMockCalled_givensAreUsedInOrder() { given(mock) .getUser(for: .any).willReturn(.test1) .getUser(for: .any).willThrow(UserError.notFound) .getUser(for: .any).willProduce { _ in .test2 } .getUser(for: .any).willProduce { _ in throw UserError.notFound } XCTAssertEqual(try mock.getUser(for: UUID()), .test1) XCTAssertThrowsError(try mock.getUser(for: UUID())) XCTAssertEqual(try mock.getUser(for: UUID()), .test2) XCTAssertThrowsError(try mock.getUser(for: UUID())) } func test_givenEquatableParameterWithCondition_whenMockCalled_canMatchWithDefaultMatcher() { let user1: User = .test1 let user2: User = .test2 let user3: User = .test3 given(mock) .modify(user: .value(user1)).willReturn(1) .modify(user: .matching { (user: User) in user == user2 }).willReturn(2) .modify(user: Parameter.any).willReturn(3) XCTAssertEqual(mock.modify(user: user1), 1) XCTAssertEqual(mock.modify(user: user2), 2) XCTAssertEqual(mock.modify(user: user3), 3) } func test_givenGenericParameterWithCondition_whenMockCalled_canMatchWithRegistered() { let user1: User = .test1 let user2: User = .test2 let user3: User = .test3 Matcher.register(User.self) given(mock) .delete(for: .value(user1)).willReturn(1) .delete(for: .matching { (user: User) in user == user2 }).willReturn(2) .delete(for: Parameter.any).willReturn(3) XCTAssertEqual(mock.delete(for: user1), 1) XCTAssertEqual(mock.delete(for: user2), 2) XCTAssertEqual(mock.delete(for: user3), 3) } func test_givenNonEquatableParameterWithCondition_whenCalled_canMatchWithRegisteredMatcher() { Matcher.register(Product.self, match: { $0.name == $1.name }) given(mock) .update(products: .value([.test])).willReturn(1) .update(products: .matching { $0.allSatisfy { $0.name == Product.test.name } }).willReturn(2) .update(products: .any).willReturn(3) XCTAssertEqual(mock.update(products: [.test]), 1) XCTAssertEqual(mock.update(products: [.test]), 2) XCTAssertEqual(mock.update(products: [.test]), 3) } func test_givenGenericParamAndReturnFunc_whenValueGiven_genericsAreInferred() { given(mock) .retrieveItem(item: Parameter.any) .willReturn(1234) let result: Int = mock.retrieveItem(item: 0) XCTAssertEqual(result, 1234) } func test_givenEscapingClosureParam_whenWillProduceCalled_closureCanBeSaved() throws { var storedCompletion: ((Product) -> Void)? given(mock) .download(completion: .any) .willProduce { completion in storedCompletion = completion } mock.download { _ in } XCTAssertNotNil(storedCompletion) } func test_givenInoutParam_whenWillProduceUsed_mutationWorks() { given(mock) .change(user: .any) .willProduce { param in param.age = 100 } var user: User = .init(name: "test", age: 0) mock.change(user: &user) XCTAssertEqual(user.age, 100) } func test_givenResult_willEmitSuccess() throws { let expected = User.test1 let result: Result = .success(expected) given(mock) .getUser(for: .any) .willHandle(result) let actual = try mock.getUser(for: .init()) XCTAssertEqual(actual, expected) } func test_givenResult_willThrow() throws { let expected = UserError.notFound let result: Result = .failure(expected) given(mock) .getUser(for: .any) .willHandle(result) XCTAssertThrowsError(try mock.getUser(for: .init())) } @MainActor func test_givenConcurrentGivens_whenCalled_synchronizedCorrectly() async throws { // Register return values concurrently await withTaskGroup(of: Void.self) { @MainActor group in for _ in (0..<50) { group.addTask { @MainActor in given(self.mock).getUser(for: .any).willReturn(.test1) } group.addTask { @MainActor in given(self.mock).getUser(for: .any).willReturn(.test2) } } await group.waitForAll() } // Concurrent calls await withTaskGroup(of: Void.self) { @MainActor group in let id = UUID() for _ in (0..<50) { group.addTask { @MainActor in _ = try? self.mock.getUser(for: id) } group.addTask { @MainActor in _ = try? self.mock.getUser(for: id) } } await group.waitForAll() } // Concurrent verifications await withTaskGroup(of: Void.self) { @MainActor group in let verify = verify(self.mock) for index in (0..<100) { group.addTask { await verify.getUser(for: .any).calledEventually(.moreOrEqual(to: index)) } group.addTask { verify.getUser(for: .any).called(100) } } await group.waitForAll() } } } ================================================ FILE: Tests/MockableTests/GivenTests_Swift6.swift ================================================ // // GivenTests_Swift6.swift // Mockable // // Created by Kolos Foltanyi on 28/09/2024. // import XCTest import Mockable #if swift(>=6) final class GivenTests_Swift6: XCTestCase { // MARK: Properties private var mock = MockTestService_Swift6() // MARK: Overrides override func tearDown() { mock.reset() Matcher.reset() } // MARK: Tests func test_givenTypedThrows_whenErrorSet_correctTypeThrown() { given(mock) .fetch().willThrow(.notFound) .fetched.willThrow(.notFound) do { try mock.fetch() } catch { XCTAssertEqual(error, UserError.notFound) } do { _ = try mock.fetched } catch { XCTAssertEqual(error, UserError.notFound) } } } #endif ================================================ FILE: Tests/MockableTests/Helpers/Task+Sleep.swift ================================================ // // Task+Sleep.swift // // // Created by Kolos Foltanyi on 2024. 04. 07.. // import Foundation extension Task where Success == Never, Failure == Never { static func sleep(seconds: TimeInterval) async throws { try await sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) } } ================================================ FILE: Tests/MockableTests/PolicyTests.swift ================================================ // // PolicyTests.swift // // // Created by Kolos Foltanyi on 2024. 04. 02.. // import XCTest import Mockable public struct Car: Equatable { var name: String var seats: Int } extension Car: Mocked { public static var mock: Car { Car(name: "Mock", seats: 4) } } @Mockable private protocol PolicyService { func throwingVoidFunc() throws var throwingVoidProp: Void { get throws } func nonThrowingVoidFunc() var nonThrowingVoidProp: Void { get } func optionalFunc() -> String? var optionalProp: String? { get } func carFunc() -> Car func optionalCarFunc() -> Car? var carProp: Car { get } func carsFunc() -> [Car] var carsProp: [Car] { get } } final class PolicyTests: XCTestCase { // MARK: Overrides override func tearDown() { Matcher.reset() MockerPolicy.default = .strict } // MARK: Tests func test_whenDefaultPolicyChanged_allCallsAreRelaxed() throws { let mock = MockPolicyService() MockerPolicy.default = .relaxed try testRelaxed(on: mock) } func test_whenCustomRelaxedPolicySet_allCallsAreRelaxed() throws { let mock = MockPolicyService(policy: .relaxed) try testRelaxed(on: mock) } func test_whenCustomVoidPolicySet_mockReturnsDefault() throws { let mock = MockPolicyService(policy: .relaxedVoid) try mock.throwingVoidFunc() try mock.throwingVoidProp mock.nonThrowingVoidFunc() mock.nonThrowingVoidProp } func test_whenOnlyOptionalPolicySet_mockReturnsNilNotMockableValue() throws { let mock = MockPolicyService(policy: .relaxedOptional) XCTAssertNil(mock.optionalCarFunc()) } func test_whenCustomMockedPolicySet_mockReturnsDefault() throws { let mock = MockPolicyService(policy: .relaxedMocked) XCTAssertEqual(Car.mock, mock.carFunc()) XCTAssertEqual(Car.mock, mock.optionalCarFunc()) XCTAssertEqual(Car.mock, mock.carProp) XCTAssertEqual(Car.mocks, mock.carsFunc()) XCTAssertEqual(Car.mocks, mock.carsProp) } private func testRelaxed(on service: MockPolicyService) throws { try service.throwingVoidFunc() try service.throwingVoidProp service.nonThrowingVoidFunc() service.nonThrowingVoidProp XCTAssertEqual(nil, service.optionalFunc()) XCTAssertEqual(nil, service.optionalProp) XCTAssertEqual(Car.mock, service.carFunc()) XCTAssertEqual(Car.mock, service.optionalCarFunc()) XCTAssertEqual(Car.mock, service.carProp) XCTAssertEqual(Car.mocks, service.carsFunc()) XCTAssertEqual(Car.mocks, service.carsProp) } } ================================================ FILE: Tests/MockableTests/Protocols/Models/Product.swift ================================================ // // Product.swift // // // Created by Kolos Foltanyi on 2023. 11. 26.. // import Foundation struct Product { var id = UUID() var name: String static let test = Product(name: "product1") } ================================================ FILE: Tests/MockableTests/Protocols/Models/User.swift ================================================ // // User.swift // // // Created by Kolos Foltanyi on 2023. 11. 26.. // import Foundation struct User: Equatable, Hashable { let id = UUID() let name: String var age: Int static let test1: User = .init(name: "test1", age: 1) static let test2: User = .init(name: "test2", age: 2) static let test3: User = .init(name: "test3", age: 3) static let list: [User] = [ .init(name: "test1", age: 1), .init(name: "test2", age: 2), .init(name: "test3", age: 3), .init(name: "test4", age: 4) ] } ================================================ FILE: Tests/MockableTests/Protocols/Models/UserError.swift ================================================ // // UserError.swift // // // Created by Kolos Foltanyi on 2023. 11. 26.. // enum UserError: Error { case notFound } ================================================ FILE: Tests/MockableTests/Protocols/TestService.swift ================================================ // // TestService.swift // // // Created by Kolos Foltanyi on 2023. 11. 24.. // import Mockable import Foundation @Mockable protocol TestService { // MARK: Associated Type associatedtype Color func getFavoriteColor() -> Color // MARK: Properties var name: String { get set } var computed: String { get } // MARK: Functions func getUser(for id: UUID) throws -> User func getUsers(for ids: UUID...) -> [User] func setUser(user: User) async throws -> Bool func modify(user: User) -> Int func update(products: [Product]) -> Int func download(completion: @escaping (Product) -> Void) func print() throws func change(user: inout User) // MARK: Generics func getUserAndValue(for id: UUID, value: Value) -> (User, Value) func delete(for value: T) -> Int func retrieve() -> V func retrieveItem(item: T) -> V } ================================================ FILE: Tests/MockableTests/Protocols/TestService_Swift6.swift ================================================ // // TestService6.swift // Mockable // // Created by Kolos Foltanyi on 28/09/2024. // import Mockable #if swift(>=6) @Mockable protocol TestService_Swift6 { // MARK: Typed Throws func fetch() throws(UserError) var fetched: User { get throws(UserError) } } #endif ================================================ FILE: Tests/MockableTests/VerifyTests.swift ================================================ // // VerifyTests.swift // // // Created by Kolos Foltanyi on 2023. 11. 22.. // import XCTest import Foundation @testable import Mockable final class VerifyTests: XCTestCase { // MARK: Properties private let mock = MockTestService() // MARK: Overrides override func tearDown() { mock.reset() Matcher.reset() } // MARK: Tests func test_givenMockFunctionIsCalled_whenCountVerified_assertsMatchingCounts() throws { given(mock).getUser(for: .any).willReturn(.test1) _ = try mock.getUser(for: UUID()) verify(mock) .getUser(for: .any).called(.once) .getUser(for: .any).called(.atLeastOnce) .getUser(for: .any).called(.less(than: 2)) .getUser(for: .any).called(.more(than: 0)) .getUser(for: .any).called(.moreOrEqual(to: 1)) .getUser(for: .any).called(.lessOrEqual(to: 1)) .getUser(for: .any).called(.from(0, to: 2)) .getUser(for: .any).called(.exactly(1)) } @MainActor func test_givenMockFunctionIsCalledAsyncrhonously_whenCountVerified_assertsMatchingCounts() async { given(mock).getUser(for: .any).willReturn(.test1) Task { try await Task.sleep(seconds: 0.5) _ = try mock.getUser(for: UUID()) } verify(mock).getUser(for: .any).called(.never) await verify(mock) .getUser(for: .any).calledEventually(.once) .getUser(for: .any).calledEventually(.atLeastOnce) .getUser(for: .any).calledEventually(.less(than: 2)) .getUser(for: .any).calledEventually(.more(than: 0)) .getUser(for: .any).calledEventually(.moreOrEqual(to: 1)) .getUser(for: .any).calledEventually(.lessOrEqual(to: 1)) .getUser(for: .any).calledEventually(.from(0, to: 2)) .getUser(for: .any).calledEventually(.exactly(1)) } func test_givenMockPropertyAccessed_whenCountVerified_assertsGetterAndSetter() throws { let testName = "Name" given(mock).name.willReturn(testName) _ = mock.name _ = mock.name mock.name = testName verify(mock) .name().getCalled(2) .name().setCalled(.once) } @MainActor func test_givenMockPropertyAccessedAsynchronously_whenCountVerified_assertsGetterAndSetter() async { let testName = "Name" given(mock).name.willReturn(testName) Task { try await Task.sleep(seconds: 0.5) _ = mock.name _ = mock.name mock.name = testName } verify(mock) .name().getCalled(.never) .name().setCalled(.never) await verify(mock) .name().getCalledEventually(.exactly(2)) .name().setCalledEventually(.once) } func test_givenGenericParamAndReturnFunc_whenVerifyUsed_OnlyParamIsInferred() { given(mock) .retrieveItem(item: Parameter.any) .willReturn(0) let _: Int = mock.retrieveItem(item: 0) verify(mock) .retrieveItem(item: Parameter.any) .called(.atLeastOnce) } @MainActor func test_givenAsyncVerification_whenSatisfied_verifiesEarlyBeforeTimeout() async throws { given(mock).getUser(for: .any).willReturn(.test1) Task { try await Task.sleep(seconds: 1) _ = try self.mock.getUser(for: UUID()) } let verify = verify(mock) try await withTimeout(after: 3) { await verify.getUser(for: .any) .calledEventually(.atLeastOnce, before: .seconds(5)) } } }