├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── docc.yml ├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── Demo ├── .swiftlint.yml ├── Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── danielsaidi.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── Shared │ ├── Demo │ │ ├── ContentView.swift │ │ ├── DemoApp.swift │ │ ├── DemoList.swift │ │ ├── DemoListButton.swift │ │ ├── DemoListItem.swift │ │ ├── DemoListLink.swift │ │ └── DemoListText.swift │ ├── Mocks │ │ ├── TestMock.swift │ │ └── TestMockable.swift │ ├── Protocols │ │ └── TestProtocol.swift │ ├── Resources │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-iOS-1024.png │ │ │ │ ├── Icon-macOS-1024.png │ │ │ │ ├── Icon-macOS-128.png │ │ │ │ ├── Icon-macOS-16.png │ │ │ │ ├── Icon-macOS-256.png │ │ │ │ ├── Icon-macOS-32.png │ │ │ │ ├── Icon-macOS-512.png │ │ │ │ └── Icon-macOS-64.png │ │ │ └── Contents.json │ │ ├── Color+Demo.swift │ │ ├── Fonts │ │ │ └── Knewave-Regular.ttf │ │ └── Image+Demo.swift │ └── Screens │ │ ├── MockScreen.swift │ │ └── MockableScreen.swift ├── iOS │ └── Info.plist └── macOS │ ├── Info.plist │ └── macOS.entitlements ├── LICENSE ├── Package.swift ├── README.md ├── RELEASE_NOTES.md ├── Resources └── Icon.png ├── Sources └── MockingKit │ ├── AsyncMockReference.swift │ ├── Internals │ └── Escaping.swift │ ├── Mock.swift │ ├── MockCall.swift │ ├── MockReference.swift │ ├── Mockable+Call.swift │ ├── Mockable+Inspect.swift │ ├── Mockable+Register.swift │ ├── Mockable+Reset.swift │ ├── Mockable.swift │ ├── MockingKit.docc │ ├── Getting-Started.md │ ├── MockingKit.md │ └── Resources │ │ ├── Logo.png │ │ └── Page.png │ └── Mocks │ ├── MockNotificationCenter.swift │ ├── MockPasteboard.swift │ └── MockUserDefaults.swift ├── Tests └── MockingKitTests │ ├── Foundation │ ├── MockNotificationCenterTests.swift │ └── MockUserDefaultsTests.swift │ ├── GenericTests.swift │ ├── MockableAsyncTests.swift │ ├── MockableTests.swift │ └── TestTypes.swift ├── package_version.sh └── scripts ├── build.sh ├── chmod.sh ├── docc.sh ├── framework.sh ├── git_default_branch.sh ├── package_docc.sh ├── package_framework.sh ├── package_name.sh ├── package_version.sh ├── sync_from.sh ├── test.sh ├── version.sh ├── version_bump.sh ├── version_number.sh ├── version_validate_git.sh └── version_validate_target.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [danielsaidi] 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow builds and tests the project. 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift 3 | 4 | name: Build Runner 5 | 6 | on: 7 | push: 8 | branches: ["master"] 9 | pull_request: 10 | branches: ["master"] 11 | 12 | jobs: 13 | build: 14 | runs-on: macos-15 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: maxim-lobanov/setup-xcode@v1 18 | with: 19 | xcode-version: latest-stable 20 | - name: Build all platforms 21 | run: bash scripts/build.sh ${{ github.event.repository.name }} 22 | - name: Test iOS 23 | run: bash scripts/test.sh ${{ github.event.repository.name }} 24 | -------------------------------------------------------------------------------- /.github/workflows/docc.yml: -------------------------------------------------------------------------------- 1 | # This workflow builds publish DocC docs to GitHub Pages. 2 | # Source: https://maxxfrazer.medium.com/deploying-docc-with-github-actions-218c5ca6cad5 3 | # Sample: https://github.com/AgoraIO-Community/VideoUIKit-iOS/blob/main/.github/workflows/deploy_docs.yml 4 | 5 | name: DocC Runner 6 | 7 | on: 8 | push: 9 | branches: ["master"] 10 | 11 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | # Allow one concurrent deployment 18 | concurrency: 19 | group: "pages" 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | deploy: 24 | environment: 25 | name: github-pages 26 | url: ${{ steps.deployment.outputs.page_url }} 27 | runs-on: macos-15 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | - id: pages 32 | name: Setup Pages 33 | uses: actions/configure-pages@v4 34 | - name: Select Xcode version 35 | uses: maxim-lobanov/setup-xcode@v1 36 | with: 37 | xcode-version: latest-stable 38 | - name: Build DocC 39 | run: bash scripts/docc.sh ${{ github.event.repository.name }} 40 | - name: Upload artifact 41 | uses: actions/upload-pages-artifact@v3 42 | with: 43 | path: '.build/docs-iOS' 44 | - id: deployment 45 | name: Deploy to GitHub Pages 46 | uses: actions/deploy-pages@v4 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | .swiftpm/ 5 | xcuserdata/ 6 | DerivedData/ -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - function_body_length 3 | - function_parameter_count 4 | - identifier_name 5 | - large_tuple 6 | - line_length 7 | - todo 8 | - trailing_whitespace 9 | - type_name 10 | - vertical_whitespace 11 | 12 | included: 13 | - Sources 14 | - Tests 15 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - function_body_length 3 | - function_parameter_count 4 | - identifier_name 5 | - large_tuple 6 | - line_length 7 | - todo 8 | - trailing_whitespace 9 | - type_name 10 | - vertical_whitespace 11 | 12 | included: 13 | - ../Sources 14 | - ../Tests 15 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A9827A3D257ED48A0006D64F /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A2A257ED4880006D64F /* DemoApp.swift */; }; 11 | A9827A3E257ED48A0006D64F /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A2A257ED4880006D64F /* DemoApp.swift */; }; 12 | A9827A3F257ED48A0006D64F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A2B257ED4880006D64F /* ContentView.swift */; }; 13 | A9827A40257ED48A0006D64F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A2B257ED4880006D64F /* ContentView.swift */; }; 14 | A9827A56257ED4E60006D64F /* Color+Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A54257ED4E60006D64F /* Color+Demo.swift */; }; 15 | A9827A57257ED4E60006D64F /* Color+Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A54257ED4E60006D64F /* Color+Demo.swift */; }; 16 | A9827A58257ED4E60006D64F /* Image+Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A55257ED4E60006D64F /* Image+Demo.swift */; }; 17 | A9827A59257ED4E60006D64F /* Image+Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A55257ED4E60006D64F /* Image+Demo.swift */; }; 18 | A9827A6F257ED9BB0006D64F /* DemoListButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6A257ED9BB0006D64F /* DemoListButton.swift */; }; 19 | A9827A70257ED9BB0006D64F /* DemoListButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6A257ED9BB0006D64F /* DemoListButton.swift */; }; 20 | A9827A71257ED9BB0006D64F /* DemoListLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6B257ED9BB0006D64F /* DemoListLink.swift */; }; 21 | A9827A72257ED9BB0006D64F /* DemoListLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6B257ED9BB0006D64F /* DemoListLink.swift */; }; 22 | A9827A73257ED9BB0006D64F /* DemoListText.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6C257ED9BB0006D64F /* DemoListText.swift */; }; 23 | A9827A74257ED9BB0006D64F /* DemoListText.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6C257ED9BB0006D64F /* DemoListText.swift */; }; 24 | A9827A75257ED9BB0006D64F /* DemoList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6D257ED9BB0006D64F /* DemoList.swift */; }; 25 | A9827A76257ED9BB0006D64F /* DemoList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6D257ED9BB0006D64F /* DemoList.swift */; }; 26 | A9827A77257ED9BB0006D64F /* DemoListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6E257ED9BB0006D64F /* DemoListItem.swift */; }; 27 | A9827A78257ED9BB0006D64F /* DemoListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A6E257ED9BB0006D64F /* DemoListItem.swift */; }; 28 | A9827A81257EDA990006D64F /* Knewave-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A9827A80257EDA990006D64F /* Knewave-Regular.ttf */; }; 29 | A9827A82257EDA990006D64F /* Knewave-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A9827A80257EDA990006D64F /* Knewave-Regular.ttf */; }; 30 | A9827A88257EDAA10006D64F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9827A87257EDAA10006D64F /* Assets.xcassets */; }; 31 | A9827A89257EDAA10006D64F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9827A87257EDAA10006D64F /* Assets.xcassets */; }; 32 | A9827A90257EDBD10006D64F /* MockScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A8F257EDBD10006D64F /* MockScreen.swift */; }; 33 | A9827A91257EDBD10006D64F /* MockScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A8F257EDBD10006D64F /* MockScreen.swift */; }; 34 | A9827A95257EDBDB0006D64F /* MockableScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A94257EDBDB0006D64F /* MockableScreen.swift */; }; 35 | A9827A96257EDBDB0006D64F /* MockableScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A94257EDBDB0006D64F /* MockableScreen.swift */; }; 36 | A9827A9B257EDC1A0006D64F /* TestProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A9A257EDC1A0006D64F /* TestProtocol.swift */; }; 37 | A9827A9C257EDC1A0006D64F /* TestProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827A9A257EDC1A0006D64F /* TestProtocol.swift */; }; 38 | A9827AA7257EDC760006D64F /* TestMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827AA6257EDC760006D64F /* TestMock.swift */; }; 39 | A9827AA8257EDC760006D64F /* TestMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827AA6257EDC760006D64F /* TestMock.swift */; }; 40 | A9827AC8257EDCFD0006D64F /* TestMockable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827AC7257EDCFD0006D64F /* TestMockable.swift */; }; 41 | A9827AC9257EDCFD0006D64F /* TestMockable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9827AC7257EDCFD0006D64F /* TestMockable.swift */; }; 42 | A9CD2C93281C33F90044E20E /* MockingKit in Frameworks */ = {isa = PBXBuildFile; productRef = A9CD2C92281C33F90044E20E /* MockingKit */; }; 43 | A9CD2C95281C33FD0044E20E /* MockingKit in Frameworks */ = {isa = PBXBuildFile; productRef = A9CD2C94281C33FD0044E20E /* MockingKit */; }; 44 | /* End PBXBuildFile section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | A9827A2A257ED4880006D64F /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = ""; }; 48 | A9827A2B257ED4880006D64F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 49 | A9827A31257ED48A0006D64F /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | A9827A34257ED48A0006D64F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | A9827A39257ED48A0006D64F /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | A9827A3B257ED48A0006D64F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | A9827A3C257ED48A0006D64F /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; 54 | A9827A54257ED4E60006D64F /* Color+Demo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Color+Demo.swift"; sourceTree = ""; }; 55 | A9827A55257ED4E60006D64F /* Image+Demo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Image+Demo.swift"; sourceTree = ""; }; 56 | A9827A5F257ED7B20006D64F /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; name = .swiftlint.yml; path = ../.swiftlint.yml; sourceTree = ""; }; 57 | A9827A60257ED7B20006D64F /* RELEASE_NOTES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = RELEASE_NOTES.md; path = ../RELEASE_NOTES.md; sourceTree = ""; }; 58 | A9827A61257ED7B20006D64F /* Package.resolved */ = {isa = PBXFileReference; lastKnownFileType = text; name = Package.resolved; path = ../Package.resolved; sourceTree = ""; }; 59 | A9827A63257ED7B20006D64F /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 60 | A9827A64257ED7B20006D64F /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Package.swift; sourceTree = ""; }; 61 | A9827A65257ED7B20006D64F /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; name = .gitignore; path = ../.gitignore; sourceTree = ""; }; 62 | A9827A66257ED7B20006D64F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 63 | A9827A67257ED7BA0006D64F /* Fastfile */ = {isa = PBXFileReference; lastKnownFileType = text; name = Fastfile; path = ../fastlane/Fastfile; sourceTree = ""; }; 64 | A9827A6A257ED9BB0006D64F /* DemoListButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoListButton.swift; sourceTree = ""; }; 65 | A9827A6B257ED9BB0006D64F /* DemoListLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoListLink.swift; sourceTree = ""; }; 66 | A9827A6C257ED9BB0006D64F /* DemoListText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoListText.swift; sourceTree = ""; }; 67 | A9827A6D257ED9BB0006D64F /* DemoList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoList.swift; sourceTree = ""; }; 68 | A9827A6E257ED9BB0006D64F /* DemoListItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoListItem.swift; sourceTree = ""; }; 69 | A9827A80257EDA990006D64F /* Knewave-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Knewave-Regular.ttf"; sourceTree = ""; }; 70 | A9827A87257EDAA10006D64F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 71 | A9827A8F257EDBD10006D64F /* MockScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockScreen.swift; sourceTree = ""; }; 72 | A9827A94257EDBDB0006D64F /* MockableScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockableScreen.swift; sourceTree = ""; }; 73 | A9827A9A257EDC1A0006D64F /* TestProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestProtocol.swift; sourceTree = ""; }; 74 | A9827AA6257EDC760006D64F /* TestMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMock.swift; sourceTree = ""; }; 75 | A9827AC7257EDCFD0006D64F /* TestMockable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMockable.swift; sourceTree = ""; }; 76 | A9CD2C90281C33D20044E20E /* mockingkit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = mockingkit; path = ..; sourceTree = ""; }; 77 | A9DAE5FF259DE55C00ADA604 /* MockingKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = MockingKit.podspec; path = ../MockingKit.podspec; sourceTree = ""; }; 78 | /* End PBXFileReference section */ 79 | 80 | /* Begin PBXFrameworksBuildPhase section */ 81 | A9827A2E257ED48A0006D64F /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | A9CD2C93281C33F90044E20E /* MockingKit in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | A9827A36257ED48A0006D64F /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | A9CD2C95281C33FD0044E20E /* MockingKit in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | A9827A24257ED4880006D64F = { 101 | isa = PBXGroup; 102 | children = ( 103 | A9CD2C8F281C33D20044E20E /* Packages */, 104 | A9827A33257ED48A0006D64F /* iOS */, 105 | A9827A3A257ED48A0006D64F /* macOS */, 106 | A9827A29257ED4880006D64F /* Shared */, 107 | A9827A32257ED48A0006D64F /* Products */, 108 | A9827A5E257ED7900006D64F /* Project Files */, 109 | A9CD2C91281C33F90044E20E /* Frameworks */, 110 | ); 111 | sourceTree = ""; 112 | }; 113 | A9827A29257ED4880006D64F /* Shared */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | A9827A4D257ED4AF0006D64F /* Demo */, 117 | A9827AA3257EDC610006D64F /* Mocks */, 118 | A9827A99257EDC040006D64F /* Protocols */, 119 | A9827A53257ED4CE0006D64F /* Resources */, 120 | A9827A8E257EDBC80006D64F /* Screens */, 121 | ); 122 | path = Shared; 123 | sourceTree = ""; 124 | }; 125 | A9827A32257ED48A0006D64F /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | A9827A31257ED48A0006D64F /* Demo.app */, 129 | A9827A39257ED48A0006D64F /* Demo.app */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | A9827A33257ED48A0006D64F /* iOS */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | A9827A34257ED48A0006D64F /* Info.plist */, 138 | ); 139 | path = iOS; 140 | sourceTree = ""; 141 | }; 142 | A9827A3A257ED48A0006D64F /* macOS */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | A9827A3B257ED48A0006D64F /* Info.plist */, 146 | A9827A3C257ED48A0006D64F /* macOS.entitlements */, 147 | ); 148 | path = macOS; 149 | sourceTree = ""; 150 | }; 151 | A9827A4D257ED4AF0006D64F /* Demo */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | A9827A2B257ED4880006D64F /* ContentView.swift */, 155 | A9827A2A257ED4880006D64F /* DemoApp.swift */, 156 | A9827A6D257ED9BB0006D64F /* DemoList.swift */, 157 | A9827A6A257ED9BB0006D64F /* DemoListButton.swift */, 158 | A9827A6E257ED9BB0006D64F /* DemoListItem.swift */, 159 | A9827A6B257ED9BB0006D64F /* DemoListLink.swift */, 160 | A9827A6C257ED9BB0006D64F /* DemoListText.swift */, 161 | ); 162 | path = Demo; 163 | sourceTree = ""; 164 | }; 165 | A9827A53257ED4CE0006D64F /* Resources */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | A9827A87257EDAA10006D64F /* Assets.xcassets */, 169 | A9827A7F257EDA990006D64F /* Fonts */, 170 | A9827A54257ED4E60006D64F /* Color+Demo.swift */, 171 | A9827A55257ED4E60006D64F /* Image+Demo.swift */, 172 | ); 173 | path = Resources; 174 | sourceTree = ""; 175 | }; 176 | A9827A5E257ED7900006D64F /* Project Files */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | A9827A65257ED7B20006D64F /* .gitignore */, 180 | A9827A5F257ED7B20006D64F /* .swiftlint.yml */, 181 | A9827A67257ED7BA0006D64F /* Fastfile */, 182 | A9827A63257ED7B20006D64F /* LICENSE */, 183 | A9DAE5FF259DE55C00ADA604 /* MockingKit.podspec */, 184 | A9827A61257ED7B20006D64F /* Package.resolved */, 185 | A9827A64257ED7B20006D64F /* Package.swift */, 186 | A9827A66257ED7B20006D64F /* README.md */, 187 | A9827A60257ED7B20006D64F /* RELEASE_NOTES.md */, 188 | ); 189 | name = "Project Files"; 190 | sourceTree = ""; 191 | }; 192 | A9827A7F257EDA990006D64F /* Fonts */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | A9827A80257EDA990006D64F /* Knewave-Regular.ttf */, 196 | ); 197 | path = Fonts; 198 | sourceTree = ""; 199 | }; 200 | A9827A8E257EDBC80006D64F /* Screens */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | A9827A94257EDBDB0006D64F /* MockableScreen.swift */, 204 | A9827A8F257EDBD10006D64F /* MockScreen.swift */, 205 | ); 206 | path = Screens; 207 | sourceTree = ""; 208 | }; 209 | A9827A99257EDC040006D64F /* Protocols */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | A9827A9A257EDC1A0006D64F /* TestProtocol.swift */, 213 | ); 214 | path = Protocols; 215 | sourceTree = ""; 216 | }; 217 | A9827AA3257EDC610006D64F /* Mocks */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | A9827AA6257EDC760006D64F /* TestMock.swift */, 221 | A9827AC7257EDCFD0006D64F /* TestMockable.swift */, 222 | ); 223 | path = Mocks; 224 | sourceTree = ""; 225 | }; 226 | A9CD2C8F281C33D20044E20E /* Packages */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | A9CD2C90281C33D20044E20E /* mockingkit */, 230 | ); 231 | name = Packages; 232 | sourceTree = ""; 233 | }; 234 | A9CD2C91281C33F90044E20E /* Frameworks */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | ); 238 | name = Frameworks; 239 | sourceTree = ""; 240 | }; 241 | /* End PBXGroup section */ 242 | 243 | /* Begin PBXNativeTarget section */ 244 | A9827A30257ED48A0006D64F /* Demo (iOS) */ = { 245 | isa = PBXNativeTarget; 246 | buildConfigurationList = A9827A45257ED48A0006D64F /* Build configuration list for PBXNativeTarget "Demo (iOS)" */; 247 | buildPhases = ( 248 | A9827AE1257EE9F10006D64F /* [SwiftLint] */, 249 | A9827A2D257ED48A0006D64F /* Sources */, 250 | A9827A2E257ED48A0006D64F /* Frameworks */, 251 | A9827A2F257ED48A0006D64F /* Resources */, 252 | ); 253 | buildRules = ( 254 | ); 255 | dependencies = ( 256 | ); 257 | name = "Demo (iOS)"; 258 | packageProductDependencies = ( 259 | A9CD2C92281C33F90044E20E /* MockingKit */, 260 | ); 261 | productName = "Demo (iOS)"; 262 | productReference = A9827A31257ED48A0006D64F /* Demo.app */; 263 | productType = "com.apple.product-type.application"; 264 | }; 265 | A9827A38257ED48A0006D64F /* Demo (macOS) */ = { 266 | isa = PBXNativeTarget; 267 | buildConfigurationList = A9827A48257ED48A0006D64F /* Build configuration list for PBXNativeTarget "Demo (macOS)" */; 268 | buildPhases = ( 269 | A9827A35257ED48A0006D64F /* Sources */, 270 | A9827A36257ED48A0006D64F /* Frameworks */, 271 | A9827A37257ED48A0006D64F /* Resources */, 272 | ); 273 | buildRules = ( 274 | ); 275 | dependencies = ( 276 | ); 277 | name = "Demo (macOS)"; 278 | packageProductDependencies = ( 279 | A9CD2C94281C33FD0044E20E /* MockingKit */, 280 | ); 281 | productName = "Demo (macOS)"; 282 | productReference = A9827A39257ED48A0006D64F /* Demo.app */; 283 | productType = "com.apple.product-type.application"; 284 | }; 285 | /* End PBXNativeTarget section */ 286 | 287 | /* Begin PBXProject section */ 288 | A9827A25257ED4880006D64F /* Project object */ = { 289 | isa = PBXProject; 290 | attributes = { 291 | LastSwiftUpdateCheck = 1220; 292 | LastUpgradeCheck = 1220; 293 | ORGANIZATIONNAME = "Daniel Saidi"; 294 | TargetAttributes = { 295 | A9827A30257ED48A0006D64F = { 296 | CreatedOnToolsVersion = 12.2; 297 | }; 298 | A9827A38257ED48A0006D64F = { 299 | CreatedOnToolsVersion = 12.2; 300 | }; 301 | }; 302 | }; 303 | buildConfigurationList = A9827A28257ED4880006D64F /* Build configuration list for PBXProject "Demo" */; 304 | compatibilityVersion = "Xcode 9.3"; 305 | developmentRegion = en; 306 | hasScannedForEncodings = 0; 307 | knownRegions = ( 308 | en, 309 | Base, 310 | ); 311 | mainGroup = A9827A24257ED4880006D64F; 312 | packageReferences = ( 313 | ); 314 | productRefGroup = A9827A32257ED48A0006D64F /* Products */; 315 | projectDirPath = ""; 316 | projectRoot = ""; 317 | targets = ( 318 | A9827A30257ED48A0006D64F /* Demo (iOS) */, 319 | A9827A38257ED48A0006D64F /* Demo (macOS) */, 320 | ); 321 | }; 322 | /* End PBXProject section */ 323 | 324 | /* Begin PBXResourcesBuildPhase section */ 325 | A9827A2F257ED48A0006D64F /* Resources */ = { 326 | isa = PBXResourcesBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | A9827A88257EDAA10006D64F /* Assets.xcassets in Resources */, 330 | A9827A81257EDA990006D64F /* Knewave-Regular.ttf in Resources */, 331 | ); 332 | runOnlyForDeploymentPostprocessing = 0; 333 | }; 334 | A9827A37257ED48A0006D64F /* Resources */ = { 335 | isa = PBXResourcesBuildPhase; 336 | buildActionMask = 2147483647; 337 | files = ( 338 | A9827A89257EDAA10006D64F /* Assets.xcassets in Resources */, 339 | A9827A82257EDA990006D64F /* Knewave-Regular.ttf in Resources */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | /* End PBXResourcesBuildPhase section */ 344 | 345 | /* Begin PBXShellScriptBuildPhase section */ 346 | A9827AE1257EE9F10006D64F /* [SwiftLint] */ = { 347 | isa = PBXShellScriptBuildPhase; 348 | buildActionMask = 2147483647; 349 | files = ( 350 | ); 351 | inputFileListPaths = ( 352 | ); 353 | inputPaths = ( 354 | ); 355 | name = "[SwiftLint]"; 356 | outputFileListPaths = ( 357 | ); 358 | outputPaths = ( 359 | ); 360 | runOnlyForDeploymentPostprocessing = 0; 361 | shellPath = /bin/sh; 362 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nfi\n"; 363 | }; 364 | /* End PBXShellScriptBuildPhase section */ 365 | 366 | /* Begin PBXSourcesBuildPhase section */ 367 | A9827A2D257ED48A0006D64F /* Sources */ = { 368 | isa = PBXSourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | A9827A6F257ED9BB0006D64F /* DemoListButton.swift in Sources */, 372 | A9827A58257ED4E60006D64F /* Image+Demo.swift in Sources */, 373 | A9827A90257EDBD10006D64F /* MockScreen.swift in Sources */, 374 | A9827AC8257EDCFD0006D64F /* TestMockable.swift in Sources */, 375 | A9827A73257ED9BB0006D64F /* DemoListText.swift in Sources */, 376 | A9827A95257EDBDB0006D64F /* MockableScreen.swift in Sources */, 377 | A9827A9B257EDC1A0006D64F /* TestProtocol.swift in Sources */, 378 | A9827AA7257EDC760006D64F /* TestMock.swift in Sources */, 379 | A9827A71257ED9BB0006D64F /* DemoListLink.swift in Sources */, 380 | A9827A77257ED9BB0006D64F /* DemoListItem.swift in Sources */, 381 | A9827A3F257ED48A0006D64F /* ContentView.swift in Sources */, 382 | A9827A56257ED4E60006D64F /* Color+Demo.swift in Sources */, 383 | A9827A75257ED9BB0006D64F /* DemoList.swift in Sources */, 384 | A9827A3D257ED48A0006D64F /* DemoApp.swift in Sources */, 385 | ); 386 | runOnlyForDeploymentPostprocessing = 0; 387 | }; 388 | A9827A35257ED48A0006D64F /* Sources */ = { 389 | isa = PBXSourcesBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | A9827A70257ED9BB0006D64F /* DemoListButton.swift in Sources */, 393 | A9827A59257ED4E60006D64F /* Image+Demo.swift in Sources */, 394 | A9827A91257EDBD10006D64F /* MockScreen.swift in Sources */, 395 | A9827AC9257EDCFD0006D64F /* TestMockable.swift in Sources */, 396 | A9827A74257ED9BB0006D64F /* DemoListText.swift in Sources */, 397 | A9827A96257EDBDB0006D64F /* MockableScreen.swift in Sources */, 398 | A9827A9C257EDC1A0006D64F /* TestProtocol.swift in Sources */, 399 | A9827AA8257EDC760006D64F /* TestMock.swift in Sources */, 400 | A9827A72257ED9BB0006D64F /* DemoListLink.swift in Sources */, 401 | A9827A78257ED9BB0006D64F /* DemoListItem.swift in Sources */, 402 | A9827A40257ED48A0006D64F /* ContentView.swift in Sources */, 403 | A9827A57257ED4E60006D64F /* Color+Demo.swift in Sources */, 404 | A9827A76257ED9BB0006D64F /* DemoList.swift in Sources */, 405 | A9827A3E257ED48A0006D64F /* DemoApp.swift in Sources */, 406 | ); 407 | runOnlyForDeploymentPostprocessing = 0; 408 | }; 409 | /* End PBXSourcesBuildPhase section */ 410 | 411 | /* Begin XCBuildConfiguration section */ 412 | A9827A43257ED48A0006D64F /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ALWAYS_SEARCH_USER_PATHS = NO; 416 | CLANG_ANALYZER_NONNULL = YES; 417 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 418 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 419 | CLANG_CXX_LIBRARY = "libc++"; 420 | CLANG_ENABLE_MODULES = YES; 421 | CLANG_ENABLE_OBJC_ARC = YES; 422 | CLANG_ENABLE_OBJC_WEAK = YES; 423 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 424 | CLANG_WARN_BOOL_CONVERSION = YES; 425 | CLANG_WARN_COMMA = YES; 426 | CLANG_WARN_CONSTANT_CONVERSION = YES; 427 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 428 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 429 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 430 | CLANG_WARN_EMPTY_BODY = YES; 431 | CLANG_WARN_ENUM_CONVERSION = YES; 432 | CLANG_WARN_INFINITE_RECURSION = YES; 433 | CLANG_WARN_INT_CONVERSION = YES; 434 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 436 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 437 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 438 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 439 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 440 | CLANG_WARN_STRICT_PROTOTYPES = YES; 441 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 442 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 443 | CLANG_WARN_UNREACHABLE_CODE = YES; 444 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 445 | COPY_PHASE_STRIP = NO; 446 | DEBUG_INFORMATION_FORMAT = dwarf; 447 | ENABLE_STRICT_OBJC_MSGSEND = YES; 448 | ENABLE_TESTABILITY = YES; 449 | GCC_C_LANGUAGE_STANDARD = gnu11; 450 | GCC_DYNAMIC_NO_PIC = NO; 451 | GCC_NO_COMMON_BLOCKS = YES; 452 | GCC_OPTIMIZATION_LEVEL = 0; 453 | GCC_PREPROCESSOR_DEFINITIONS = ( 454 | "DEBUG=1", 455 | "$(inherited)", 456 | ); 457 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 458 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 459 | GCC_WARN_UNDECLARED_SELECTOR = YES; 460 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 461 | GCC_WARN_UNUSED_FUNCTION = YES; 462 | GCC_WARN_UNUSED_VARIABLE = YES; 463 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 464 | MTL_FAST_MATH = YES; 465 | ONLY_ACTIVE_ARCH = YES; 466 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 467 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 468 | }; 469 | name = Debug; 470 | }; 471 | A9827A44257ED48A0006D64F /* Release */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | ALWAYS_SEARCH_USER_PATHS = NO; 475 | CLANG_ANALYZER_NONNULL = YES; 476 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 477 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 478 | CLANG_CXX_LIBRARY = "libc++"; 479 | CLANG_ENABLE_MODULES = YES; 480 | CLANG_ENABLE_OBJC_ARC = YES; 481 | CLANG_ENABLE_OBJC_WEAK = YES; 482 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 483 | CLANG_WARN_BOOL_CONVERSION = YES; 484 | CLANG_WARN_COMMA = YES; 485 | CLANG_WARN_CONSTANT_CONVERSION = YES; 486 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 487 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 488 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 489 | CLANG_WARN_EMPTY_BODY = YES; 490 | CLANG_WARN_ENUM_CONVERSION = YES; 491 | CLANG_WARN_INFINITE_RECURSION = YES; 492 | CLANG_WARN_INT_CONVERSION = YES; 493 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 494 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 495 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 496 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 497 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 498 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 499 | CLANG_WARN_STRICT_PROTOTYPES = YES; 500 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 501 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 502 | CLANG_WARN_UNREACHABLE_CODE = YES; 503 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 504 | COPY_PHASE_STRIP = NO; 505 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 506 | ENABLE_NS_ASSERTIONS = NO; 507 | ENABLE_STRICT_OBJC_MSGSEND = YES; 508 | GCC_C_LANGUAGE_STANDARD = gnu11; 509 | GCC_NO_COMMON_BLOCKS = YES; 510 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 511 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 512 | GCC_WARN_UNDECLARED_SELECTOR = YES; 513 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 514 | GCC_WARN_UNUSED_FUNCTION = YES; 515 | GCC_WARN_UNUSED_VARIABLE = YES; 516 | MTL_ENABLE_DEBUG_INFO = NO; 517 | MTL_FAST_MATH = YES; 518 | SWIFT_COMPILATION_MODE = wholemodule; 519 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 520 | }; 521 | name = Release; 522 | }; 523 | A9827A46257ED48A0006D64F /* Debug */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 527 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 528 | CODE_SIGN_STYLE = Automatic; 529 | DEVELOPMENT_TEAM = PMEDFW438U; 530 | ENABLE_PREVIEWS = YES; 531 | INFOPLIST_FILE = iOS/Info.plist; 532 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 533 | LD_RUNPATH_SEARCH_PATHS = ( 534 | "$(inherited)", 535 | "@executable_path/Frameworks", 536 | ); 537 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.mockery.demo; 538 | PRODUCT_NAME = Demo; 539 | SDKROOT = iphoneos; 540 | SWIFT_VERSION = 5.0; 541 | TARGETED_DEVICE_FAMILY = "1,2"; 542 | }; 543 | name = Debug; 544 | }; 545 | A9827A47257ED48A0006D64F /* Release */ = { 546 | isa = XCBuildConfiguration; 547 | buildSettings = { 548 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 549 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 550 | CODE_SIGN_STYLE = Automatic; 551 | DEVELOPMENT_TEAM = PMEDFW438U; 552 | ENABLE_PREVIEWS = YES; 553 | INFOPLIST_FILE = iOS/Info.plist; 554 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 555 | LD_RUNPATH_SEARCH_PATHS = ( 556 | "$(inherited)", 557 | "@executable_path/Frameworks", 558 | ); 559 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.mockery.demo; 560 | PRODUCT_NAME = Demo; 561 | SDKROOT = iphoneos; 562 | SWIFT_VERSION = 5.0; 563 | TARGETED_DEVICE_FAMILY = "1,2"; 564 | VALIDATE_PRODUCT = YES; 565 | }; 566 | name = Release; 567 | }; 568 | A9827A49257ED48A0006D64F /* Debug */ = { 569 | isa = XCBuildConfiguration; 570 | buildSettings = { 571 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 572 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 573 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; 574 | CODE_SIGN_STYLE = Automatic; 575 | COMBINE_HIDPI_IMAGES = YES; 576 | DEVELOPMENT_TEAM = PMEDFW438U; 577 | ENABLE_HARDENED_RUNTIME = YES; 578 | ENABLE_PREVIEWS = YES; 579 | INFOPLIST_FILE = macOS/Info.plist; 580 | LD_RUNPATH_SEARCH_PATHS = ( 581 | "$(inherited)", 582 | "@executable_path/../Frameworks", 583 | ); 584 | MACOSX_DEPLOYMENT_TARGET = 11.0; 585 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.mockery.demo; 586 | PRODUCT_NAME = Demo; 587 | SDKROOT = macosx; 588 | SWIFT_VERSION = 5.0; 589 | }; 590 | name = Debug; 591 | }; 592 | A9827A4A257ED48A0006D64F /* Release */ = { 593 | isa = XCBuildConfiguration; 594 | buildSettings = { 595 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 596 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 597 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; 598 | CODE_SIGN_STYLE = Automatic; 599 | COMBINE_HIDPI_IMAGES = YES; 600 | DEVELOPMENT_TEAM = PMEDFW438U; 601 | ENABLE_HARDENED_RUNTIME = YES; 602 | ENABLE_PREVIEWS = YES; 603 | INFOPLIST_FILE = macOS/Info.plist; 604 | LD_RUNPATH_SEARCH_PATHS = ( 605 | "$(inherited)", 606 | "@executable_path/../Frameworks", 607 | ); 608 | MACOSX_DEPLOYMENT_TARGET = 11.0; 609 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.mockery.demo; 610 | PRODUCT_NAME = Demo; 611 | SDKROOT = macosx; 612 | SWIFT_VERSION = 5.0; 613 | }; 614 | name = Release; 615 | }; 616 | /* End XCBuildConfiguration section */ 617 | 618 | /* Begin XCConfigurationList section */ 619 | A9827A28257ED4880006D64F /* Build configuration list for PBXProject "Demo" */ = { 620 | isa = XCConfigurationList; 621 | buildConfigurations = ( 622 | A9827A43257ED48A0006D64F /* Debug */, 623 | A9827A44257ED48A0006D64F /* Release */, 624 | ); 625 | defaultConfigurationIsVisible = 0; 626 | defaultConfigurationName = Release; 627 | }; 628 | A9827A45257ED48A0006D64F /* Build configuration list for PBXNativeTarget "Demo (iOS)" */ = { 629 | isa = XCConfigurationList; 630 | buildConfigurations = ( 631 | A9827A46257ED48A0006D64F /* Debug */, 632 | A9827A47257ED48A0006D64F /* Release */, 633 | ); 634 | defaultConfigurationIsVisible = 0; 635 | defaultConfigurationName = Release; 636 | }; 637 | A9827A48257ED48A0006D64F /* Build configuration list for PBXNativeTarget "Demo (macOS)" */ = { 638 | isa = XCConfigurationList; 639 | buildConfigurations = ( 640 | A9827A49257ED48A0006D64F /* Debug */, 641 | A9827A4A257ED48A0006D64F /* Release */, 642 | ); 643 | defaultConfigurationIsVisible = 0; 644 | defaultConfigurationName = Release; 645 | }; 646 | /* End XCConfigurationList section */ 647 | 648 | /* Begin XCSwiftPackageProductDependency section */ 649 | A9CD2C92281C33F90044E20E /* MockingKit */ = { 650 | isa = XCSwiftPackageProductDependency; 651 | productName = MockingKit; 652 | }; 653 | A9CD2C94281C33FD0044E20E /* MockingKit */ = { 654 | isa = XCSwiftPackageProductDependency; 655 | productName = MockingKit; 656 | }; 657 | /* End XCSwiftPackageProductDependency section */ 658 | }; 659 | rootObject = A9827A25257ED4880006D64F /* Project object */; 660 | } 661 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcuserdata/danielsaidi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Demo (iOS).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | Demo (macOS).xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Demo/Shared/Demo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-12-07. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | 13 | var body: some View { 14 | NavigationView { 15 | DemoList("MockingKit") { 16 | Section(header: Text("Mocks")) { 17 | DemoListLink("Mock", .circleDashedFilled, MockScreen()) 18 | DemoListLink("Mockable", .circleDashed, MockableScreen()) 19 | } 20 | } 21 | }.withPlatformSpecificNavigationStyle() 22 | } 23 | } 24 | 25 | private extension View { 26 | 27 | func withPlatformSpecificNavigationStyle() -> some View { 28 | #if os(iOS) 29 | return self.navigationViewStyle(StackNavigationViewStyle()) 30 | #else 31 | return self 32 | #endif 33 | } 34 | } 35 | 36 | struct ContentView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | ContentView() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demo/Shared/Demo/DemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoApp.swift 3 | // Shared 4 | // 5 | // Created by Daniel Saidi on 2020-12-07. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | @main 12 | struct DemoApp: App { 13 | 14 | var body: some Scene { 15 | WindowGroup { 16 | ContentView() 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Demo/Shared/Demo/DemoList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoList.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-11-27. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DemoList: View { 12 | 13 | init(_ title: String, @ViewBuilder content: @escaping () -> Content) { 14 | self.title = title 15 | self.content = content 16 | } 17 | 18 | private let title: String 19 | private let content: () -> Content 20 | 21 | var body: some View { 22 | List { 23 | Section() {} // Spacer 24 | content() 25 | } 26 | .navigationTitle(title) 27 | .withPlatformSpecificListStyle() 28 | } 29 | } 30 | 31 | private extension View { 32 | 33 | func withPlatformSpecificListStyle() -> some View { 34 | #if os(iOS) || os(tvOS) || os(watchOS) 35 | return self.listStyle(InsetGroupedListStyle()) 36 | #else 37 | return self 38 | #endif 39 | } 40 | } 41 | 42 | struct DemoList_Previews: PreviewProvider { 43 | static var previews: some View { 44 | DemoList("List") { 45 | Text("This") 46 | Text("is") 47 | Text("a") 48 | Text("Spartan") 49 | Text("list!") 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Demo/Shared/Demo/DemoListButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoListButton.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-11-27. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DemoListButton: View { 12 | 13 | init(_ text: String, _ action: @escaping () -> Void) { 14 | self.init(text, nil, action) 15 | } 16 | 17 | init(_ text: String, _ image: Image?, _ action: @escaping () -> Void) { 18 | self.text = text 19 | self.image = image 20 | self.action = action 21 | } 22 | 23 | private let text: String 24 | private let image: Image? 25 | private let action: () -> Void 26 | 27 | var body: some View { 28 | Button(action: action) { 29 | HStack { 30 | DemoListItem(text, image) 31 | Spacer() 32 | }.background(Color.white.opacity(0.001)) 33 | } 34 | .buttonStyle(PlainButtonStyle()) 35 | } 36 | } 37 | 38 | struct DemoListButton_Previews: PreviewProvider { 39 | static var previews: some View { 40 | DemoListButton("This is a button", .circle) { print("You tapped me!") } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Demo/Shared/Demo/DemoListItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoListItem.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-11-27. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DemoListItem: View { 12 | 13 | init(_ text: String, _ image: Image?) { 14 | self.text = text 15 | self.image = image 16 | } 17 | 18 | private let text: String 19 | private let image: Image? 20 | 21 | var body: some View { 22 | HStack { 23 | if let image = image { 24 | image.frame(width: 20).padding(.trailing, 10) 25 | } 26 | Text(text) 27 | } 28 | } 29 | } 30 | 31 | struct DemoListItem_Previews: PreviewProvider { 32 | static var previews: some View { 33 | DemoListItem("This is a list item", .circle) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Demo/Shared/Demo/DemoListLink.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoListLink.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-11-27. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DemoListLink: View { 12 | 13 | init(_ text: String, _ image: Image, _ destination: Destination) { 14 | link = NavigationLink(destination: destination) { 15 | DemoListItem(text, image) 16 | } 17 | } 18 | 19 | private let link: NavigationLink 20 | 21 | var body: some View { link } 22 | } 23 | 24 | struct DemoListLink_Previews: PreviewProvider { 25 | static var previews: some View { 26 | DemoListLink("This is a link", .circle, Text("You navigated!")) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Demo/Shared/Demo/DemoListText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoListText.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-11-27. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DemoListText: View { 12 | 13 | init(_ text: String) { 14 | self.text = text 15 | } 16 | 17 | private let text: String 18 | 19 | var body: some View { 20 | Text(text) 21 | .lineSpacing(8) 22 | .padding(.vertical, 13) 23 | } 24 | } 25 | 26 | struct DemoListText_Previews: PreviewProvider { 27 | static var previews: some View { 28 | DemoListText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Demo/Shared/Mocks/TestMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestMock.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-12-07. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import MockingKit 10 | 11 | /** 12 | Inherit `Mock` if your mock doesn't have to inherit another 13 | class, which should be most cases. 14 | */ 15 | class TestMock: Mock, TestProtocol { 16 | 17 | lazy var doStuffRef = MockReference(doStuff) 18 | lazy var doStuffWithArgsRef = MockReference(doStuffWithArgs) 19 | 20 | func doStuff() { 21 | call(doStuffRef, args: ()) 22 | } 23 | 24 | func doStuffWithArgs(name: String, age: Int) { 25 | call(doStuffWithArgsRef, args: (name, age)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Demo/Shared/Mocks/TestMockable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestMockable.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-12-07. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MockingKit 11 | 12 | /** 13 | Implement the `Mockable` protocol if your mock must inherit 14 | another class, which e.g. is needed when you want to create 15 | mock implementations of for instance a `NotificationCenter`. 16 | */ 17 | class TestMockable: NotificationCenter, Mockable, TestProtocol { 18 | 19 | let mock = Mock() 20 | 21 | lazy var doStuffRef = MockReference(doStuff) 22 | lazy var doStuffWithArgsRef = MockReference(doStuffWithArgs) 23 | 24 | func doStuff() { 25 | call(doStuffRef, args: ()) 26 | } 27 | 28 | func doStuffWithArgs(name: String, age: Int) { 29 | call(doStuffWithArgsRef, args: (name, age)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Demo/Shared/Protocols/TestProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestProtocol.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-12-07. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol TestProtocol { 12 | 13 | func doStuff() 14 | func doStuffWithArgs(name: String, age: Int) 15 | } 16 | -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x00", 9 | "green" : "0x00", 10 | "red" : "0x00" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-iOS-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "Icon-macOS-16.png", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Icon-macOS-32.png", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "Icon-macOS-32.png", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Icon-macOS-64.png", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "Icon-macOS-128.png", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Icon-macOS-256.png", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "Icon-macOS-256.png", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Icon-macOS-512.png", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "Icon-macOS-512.png", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "Icon-macOS-1024.png", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-iOS-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-iOS-1024.png -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-1024.png -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-128.png -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-16.png -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-256.png -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-32.png -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-512.png -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Assets.xcassets/AppIcon.appiconset/Icon-macOS-64.png -------------------------------------------------------------------------------- /Demo/Shared/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Shared/Resources/Color+Demo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Demo.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-12-07. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | 12 | extension UIColor { 13 | 14 | static var accent: UIColor { 15 | UIColor(named: "AccentColor") ?? .black 16 | } 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Demo/Shared/Resources/Fonts/Knewave-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Demo/Shared/Resources/Fonts/Knewave-Regular.ttf -------------------------------------------------------------------------------- /Demo/Shared/Resources/Image+Demo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image+Demo.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-11-26. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension Image { 12 | 13 | static var about: Image { Image(systemName: "questionmark") } 14 | static var circle: Image { Image(systemName: "circle") } 15 | static var circleDashed: Image { Image(systemName: "circle.dashed") } 16 | static var circleDashedFilled: Image { Image(systemName: "circle.dashed.inset.fill") } 17 | } 18 | -------------------------------------------------------------------------------- /Demo/Shared/Screens/MockScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockScreen.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-12-07. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import MockingKit 10 | import SwiftUI 11 | 12 | /** 13 | This screen demonstrates how to trigger a mock and make use 14 | of the resulting call information. 15 | */ 16 | struct MockScreen: View { 17 | 18 | init(mock: TestMock = TestMock()) { 19 | self.mock = mock 20 | } 21 | 22 | private let mock: TestMock 23 | 24 | private let matchCount = 5 25 | 26 | @State private var hasCalled = false 27 | @State private var hasCalledCount = 0 28 | @State private var hasCalledMatch = false 29 | @State private var hasCalledWithArgs = false 30 | @State private var hasCalledWithArgsCount = 0 31 | @State private var hasCalledWithArgsMatch = false 32 | @State private var hasCalledWithArgsName = "" 33 | @State private var hasCalledWithArgsAge = 0 34 | 35 | var body: some View { 36 | DemoList("Mock") { 37 | Section(header: Text("About")) { 38 | DemoListText("This demo uses a TestMock to show how you can trigger funcs and inspect the resulting calls.") 39 | } 40 | 41 | Section(header: Text("Actions")) { 42 | DemoListButton("Trigger doStuff", doStuff) 43 | DemoListButton("Trigger doStuffWithArg w. random args", doStuffWithArgs) 44 | } 45 | 46 | Section(header: Text("Result: doStuff")) { 47 | DemoListText("Has called? \(hasCalled ? "Yes" : "No")") 48 | DemoListText("Has called \(hasCalledCount) times") 49 | DemoListText("Has called \(matchCount) times? \(hasCalledMatch ? "Yes" : "No")") 50 | } 51 | 52 | Section(header: Text("Result: doStuffWithArguments")) { 53 | DemoListText("Has called: \(hasCalledWithArgs ? "Yes" : "No")") 54 | DemoListText("Has called \(hasCalledWithArgsCount) times") 55 | DemoListText("Has called \(matchCount) times? \(hasCalledWithArgsMatch ? "Yes" : "No")") 56 | if hasCalledWithArgs { 57 | DemoListText("Last name arg: \(hasCalledWithArgsName)") 58 | DemoListText("Last age arg: \(hasCalledWithArgsAge)") 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | private extension MockScreen { 66 | 67 | func doStuff() { 68 | mock.doStuff() 69 | hasCalled = mock.hasCalled(mock.doStuffRef) 70 | hasCalledCount = mock.calls(to: mock.doStuffRef).count 71 | hasCalledMatch = mock.hasCalled(mock.doStuffRef, numberOfTimes: matchCount) 72 | } 73 | 74 | func doStuffWithArgs() { 75 | let name = "Member #\(Int.random(in: 1_000...9_999))" 76 | let age = Int.random(in: 18...100) 77 | mock.doStuffWithArgs(name: name, age: age) 78 | let calls = mock.calls(to: mock.doStuffWithArgsRef) 79 | hasCalledWithArgs = mock.hasCalled(mock.doStuffWithArgsRef) 80 | hasCalledWithArgsCount = calls.count 81 | hasCalledWithArgsMatch = mock.hasCalled(mock.doStuffWithArgsRef, numberOfTimes: matchCount) 82 | hasCalledWithArgsName = calls.last?.arguments.0 ?? "" 83 | hasCalledWithArgsAge = calls.last?.arguments.1 ?? -1 84 | } 85 | } 86 | 87 | struct MockScreen_Previews: PreviewProvider { 88 | static var previews: some View { 89 | MockScreen() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Demo/Shared/Screens/MockableScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockableScreen.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2020-12-07. 6 | // Copyright © 2020 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import MockingKit 10 | import SwiftUI 11 | 12 | struct MockableScreen: View { 13 | 14 | init(mock: TestMockable = TestMockable()) { 15 | self.mock = mock 16 | } 17 | 18 | private let mock: TestMockable 19 | 20 | private let matchCount = 5 21 | 22 | @State private var hasCalled = false 23 | @State private var hasCalledCount = 0 24 | @State private var hasCalledMatch = false 25 | @State private var hasCalledWithArgs = false 26 | @State private var hasCalledWithArgsCount = 0 27 | @State private var hasCalledWithArgsMatch = false 28 | @State private var hasCalledWithArgsName = "" 29 | @State private var hasCalledWithArgsAge = 0 30 | 31 | var body: some View { 32 | DemoList("Mockable") { 33 | Section(header: Text("About")) { 34 | DemoListText("This demo uses a TestMockable to show how you can trigger funcs and inspect the resulting calls.") 35 | } 36 | 37 | Section(header: Text("Actions")) { 38 | DemoListButton("Trigger doStuff", doStuff) 39 | DemoListButton("Trigger doStuffWithArg w. random args", doStuffWithArgs) 40 | } 41 | 42 | Section(header: Text("Result: doStuff")) { 43 | DemoListText("Has called? \(hasCalled ? "Yes" : "No")") 44 | DemoListText("Has called \(hasCalledCount) times") 45 | DemoListText("Has called \(matchCount) times? \(hasCalledMatch ? "Yes" : "No")") 46 | } 47 | 48 | Section(header: Text("Result: doStuffWithArguments")) { 49 | DemoListText("Has called: \(hasCalledWithArgs ? "Yes" : "No")") 50 | DemoListText("Has called \(hasCalledWithArgsCount) times") 51 | DemoListText("Has called \(matchCount) times? \(hasCalledWithArgsMatch ? "Yes" : "No")") 52 | if hasCalledWithArgs { 53 | DemoListText("Last name arg: \(hasCalledWithArgsName)") 54 | DemoListText("Last age arg: \(hasCalledWithArgsAge)") 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | private extension MockableScreen { 62 | 63 | func doStuff() { 64 | mock.doStuff() 65 | hasCalled = mock.hasCalled(mock.doStuffRef) 66 | hasCalledCount = mock.calls(to: mock.doStuffRef).count 67 | hasCalledMatch = mock.hasCalled(mock.doStuffRef, numberOfTimes: matchCount) 68 | } 69 | 70 | func doStuffWithArgs() { 71 | let name = "Member #\(Int.random(in: 1_000...9_999))" 72 | let age = Int.random(in: 18...100) 73 | mock.doStuffWithArgs(name: name, age: age) 74 | let calls = mock.calls(to: mock.doStuffWithArgsRef) 75 | hasCalledWithArgs = mock.hasCalled(mock.doStuffWithArgsRef) 76 | hasCalledWithArgsCount = calls.count 77 | hasCalledWithArgsMatch = mock.hasCalled(mock.doStuffWithArgsRef, numberOfTimes: matchCount) 78 | hasCalledWithArgsName = calls.last?.arguments.0 ?? "" 79 | hasCalledWithArgsAge = calls.last?.arguments.1 ?? -1 80 | } 81 | } 82 | 83 | struct MockableScreen_Previews: PreviewProvider { 84 | static var previews: some View { 85 | MockableScreen() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Demo/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MockingKit 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIAppFonts 26 | 27 | Knewave-Regular.ttf 28 | 29 | UIApplicationSceneManifest 30 | 31 | UIApplicationSupportsMultipleScenes 32 | 33 | 34 | UIApplicationSupportsIndirectInputEvents 35 | 36 | UILaunchScreen 37 | 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | UIViewControllerBasedStatusBarAppearance 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Demo/macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIAppFonts 6 | 7 | Knewave-Regular.ttf 8 | 9 | CFBundleDevelopmentRegion 10 | $(DEVELOPMENT_LANGUAGE) 11 | CFBundleDisplayName 12 | MockingKit 13 | CFBundleExecutable 14 | $(EXECUTABLE_NAME) 15 | CFBundleIconFile 16 | 17 | CFBundleIdentifier 18 | $(PRODUCT_BUNDLE_IDENTIFIER) 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleName 22 | $(PRODUCT_NAME) 23 | CFBundlePackageType 24 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 25 | CFBundleShortVersionString 26 | 1.0 27 | CFBundleVersion 28 | 1 29 | LSMinimumSystemVersion 30 | $(MACOSX_DEPLOYMENT_TARGET) 31 | 32 | 33 | -------------------------------------------------------------------------------- /Demo/macOS/macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Daniel Saidi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "MockingKit", 7 | platforms: [ 8 | .iOS(.v13), 9 | .tvOS(.v13), 10 | .watchOS(.v6), 11 | .macOS(.v10_15), 12 | .visionOS(.v1) 13 | ], 14 | products: [ 15 | .library( 16 | name: "MockingKit", 17 | targets: ["MockingKit"] 18 | ) 19 | ], 20 | dependencies: [], 21 | targets: [ 22 | .target( 23 | name: "MockingKit" 24 | ), 25 | .testTarget( 26 | name: "MockingKitTests", 27 | dependencies: ["MockingKit"] 28 | ) 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Project Icon 3 |

4 | 5 |

6 | Version 7 | Swift 6.0 8 | Documentation 9 | MIT License 10 | Sponsor my work 11 |

12 | 13 | 14 | # MockingKit 15 | 16 | MockingKit is a mocking library for Swift that lets you create mocks of any protocol or class. This can be used to mock dependencies in unit tests, and to fake not yet implemented features in your apps. 17 | 18 | MockingKit automatically records every method call, to let you verify that your code behaves as you expect. You can also register register dynamic function results to control your test outcome. 19 | 20 | MockingKit doesn't require any setup or build scripts, and puts no restrictions on your code or architecture. Just create a mock, set up how you want to use and inspect it, and you're good to go. 21 | 22 | 23 | 24 | ## Installation 25 | 26 | MockingKit can be installed with the Swift Package Manager: 27 | 28 | ``` 29 | https://github.com/danielsaidi/MockingKit.git 30 | ``` 31 | 32 | 33 | 34 | ## Support My Work 35 | 36 | Maintaining my various [open-source tools][OpenSource] takes significant time and effort. You can [become a sponsor][Sponsors] to help me dedicate more time to creating, maintaining, and improving these projects. Every contribution, no matter the size, makes a real difference in keeping these tools free and actively developed. Thank you for considering! 37 | 38 | 39 | 40 | ## Getting started 41 | 42 | MockingKit lets you mock any protocol or open class. For instance, consider this simple protocol: 43 | 44 | ```swift 45 | protocol MyProtocol { 46 | 47 | func doStuff(int: Int, string: String) -> String 48 | } 49 | ``` 50 | 51 | With MockingKit, you can easily create a mock implementation of this protocol: 52 | 53 | ```swift 54 | import MockingKit 55 | 56 | class MyMock: Mock, MyProtocol { 57 | 58 | // Define a lazy reference for each function you want to mock 59 | lazy var doStuffRef = MockReference(doStuff) 60 | 61 | // Functions must then call the reference to be recorded 62 | func doStuff(int: Int, string: String) -> String { 63 | call(doStuffRef, args: (int, string)) 64 | } 65 | } 66 | ``` 67 | 68 | You can now use the mock to `register` function results, `call` functions and `inspect` recorded calls. 69 | 70 | ```swift 71 | // Create a mock instance 72 | let mock = MyMock() 73 | 74 | // Register a result to be returned by doStuff 75 | mock.registerResult(for: mock.doStuffRef) { args in String(args.1.reversed()) } 76 | 77 | // Calling doStuff will now return the pre-registered result 78 | let result = mock.doStuff(int: 42, string: "string") // => "gnirts" 79 | 80 | // You can now inspect calls made to doStuff 81 | let calls = mock.calls(to: \.doStuffRef) // => 1 item 82 | calls[0].arguments // => (42, "string") 83 | calls[0].result // => "gnirts" 84 | mock.hasCalled(\.doStuffRef) // => true 85 | ``` 86 | 87 | See the online [getting started guide][Getting-Started] for more information. 88 | 89 | 90 | 91 | ## Documentation 92 | 93 | The online [documentation][Documentation] has more information, articles, code examples, etc. 94 | 95 | 96 | 97 | ## Demo Application 98 | 99 | The `Demo` folder has an app that lets you explore the library and see how its mocks behave. 100 | 101 | 102 | 103 | ## Contact 104 | 105 | Feel free to reach out if you have questions or if you want to contribute in any way: 106 | 107 | * Website: [danielsaidi.com][Website] 108 | * E-mail: [daniel.saidi@gmail.com][Email] 109 | * Bluesky: [@danielsaidi@bsky.social][Bluesky] 110 | * Mastodon: [@danielsaidi@mastodon.social][Mastodon] 111 | 112 | 113 | 114 | ## License 115 | 116 | MockingKit is available under the MIT license. See the [LICENSE][License] file for more info. 117 | 118 | 119 | 120 | [Email]: mailto:daniel.saidi@gmail.com 121 | [Website]: https://www.danielsaidi.com 122 | [GitHub]: https://www.github.com/danielsaidi 123 | [OpenSource]: https://danielsaidi.com/opensource 124 | [Sponsors]: https://github.com/sponsors/danielsaidi 125 | 126 | [Bluesky]: https://bsky.app/profile/danielsaidi.bsky.social 127 | [Mastodon]: https://mastodon.social/@danielsaidi 128 | [Twitter]: https://www.twitter.com/danielsaidi 129 | 130 | [Documentation]: https://danielsaidi.github.io/MockingKit 131 | [Getting-Started]: https://danielsaidi.github.io/MockingKit/documentation/mockingkit/getting-started 132 | [License]: https://github.com/danielsaidi/MockingKit/blob/master/LICENSE 133 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | 4 | ## 1.5 5 | 6 | This version makes the SDK use Swift 6. 7 | 8 | 9 | 10 | ## 1.4.2 11 | 12 | This version adds support for strict concurrency. 13 | 14 | 15 | 16 | ## 1.4.1 17 | 18 | This version adds support for visionOS. 19 | 20 | 21 | 22 | ## 1.4 23 | 24 | This version bumps Swift to 5.9. 25 | 26 | 27 | 28 | ## 1.3 29 | 30 | Thanks to [Tim Andersson](https://github.com/Boerworz) and the people at BookBeat, MockingKit now supports keypaths. 31 | 32 | ### ✨ New Features 33 | 34 | * `Mockable` now supports keypaths for all functions except `call`. 35 | 36 | 37 | 38 | ## 1.2 39 | 40 | This version removes all external dependencies. 41 | 42 | ### 📦 Dependencies 43 | 44 | * MockingKit no longer uses Quick and Nimble. 45 | 46 | 47 | 48 | ## 1.1.1 49 | 50 | This version adds a mock pasteboard for macOS and adjusts the documentation setup. 51 | 52 | ### ✨ New Features 53 | 54 | * `MockPasteboard` now supports macOS as well. 55 | 56 | ### 💡 Behavior changes 57 | 58 | * MockingKit no longer uses the DocC documentation plugin. 59 | 60 | 61 | 62 | ## 1.1 63 | 64 | ### ✨ New Features 65 | 66 | * This version adds support for Swift concurrency and `async` functions. 67 | 68 | ### 💥 Breaking Changes 69 | 70 | * Since Swift Concurrency requires iOS 13 and tvOS and later, the minimum platform versions for this library have been bumped. 71 | 72 | 73 | 74 | ## 1.0 75 | 76 | This version bumps the package Swift version to 5.5 to enable extended DocC support. 77 | 78 | It also removes previously deprecated parts of the library and removes `call` functions with implicitly unwrapped args. 79 | 80 | 81 | 82 | ## 0.9.4 83 | 84 | This version replaces the accidental SSH dependencies to Quick and Nimble with HTTPS ones. 85 | 86 | Big thanks to Dave Verwer and [SPI](https://swiftpackageindex.com) for noticing this! 87 | 88 | 89 | 90 | ## 0.9.3 91 | 92 | `MockTextDocumentProxy` no longer modifies its state when calling its functions. 93 | 94 | 95 | 96 | ## 0.9.2 97 | 98 | `MockTextDocumentProxy` has a new `keyboardAppearance` property. 99 | 100 | 101 | 102 | ## 0.9.1 103 | 104 | Thanks to [@jinuman](https://github.com/jinuman) this version fixes the incorrectly high deployment targets. 105 | 106 | This version also annotates another invoke function as deprecated. 107 | 108 | 109 | 110 | ## 0.9.0 111 | 112 | This version renames invoke/invocation to call/calls to make the code cleaner and less technical: 113 | 114 | * `AnyInvokation` → `AnyCall` 115 | * `MockInvokation` → `MockCall` 116 | * `Mock` `registeredInvokations` → `registeredCalls` 117 | * `Mockable` `hasInvoked(_)` → `hasCalled(_)` 118 | * `Mockable` `hasInvoked(_, numberOfTimes:)` → `hasCalled(_, numberOfTimes:)` 119 | * `Mockable` `invoke` → `call` 120 | * `Mockable` `invokations(of:)` → `calls(to:)` 121 | * `Mockable` `resetInvokations` → `resetCalls` 122 | * `Mockable` `resetInvokations(for:)` → `resetCalls(to:)` 123 | 124 | This also solves that with my Swedish eyes spelled invocation as invokation, which is how it's spelled here :) 125 | 126 | The old invoke/invokation parts are marked as deprecated and will be removed in 1.0. 127 | 128 | 129 | 130 | ## 0.8.2 131 | 132 | This version adds a `MockPasteboard` and a `MockTextDocumentProxy`. 133 | 134 | 135 | 136 | ## 0.8.1 137 | 138 | This version adds `stringArray` support to `MockUserDefaults`. 139 | 140 | 141 | 142 | ## 0.8.0 143 | 144 | This version renames Mockery to MockingKit. 145 | 146 | 147 | 148 | ## 0.7.0 149 | 150 | This version's podspec has been adjusted to support macOS 10.10. 151 | 152 | There is also a new demo app with more demos and examples. 153 | 154 | 155 | 156 | ## 0.6.1 157 | 158 | This version supports macOS 10.10 instead of 10.15. 159 | 160 | 161 | 162 | ## 0.6.0 163 | 164 | This version adds improved support for watchOS, tvOS and macOS. 165 | 166 | 167 | 168 | ## 0.5.0 169 | 170 | This version adds mock classes that lets you commonly used `Foundation` classes: 171 | 172 | * `MockNotificationCenter` 173 | * `MockUserDefaults` 174 | 175 | 176 | 177 | ## 0.4.0 178 | 179 | This version replaces the memory address resolve logic with a new `MockReference` approach. 180 | 181 | `MockReference`, replaces the brittle memory address-based approach from previous versions and makes invokations much more reliable. This means that it's now possible to use Mockery for more architectures and devices than 64 bit, that mocks can be shared across frameworks etc. 182 | 183 | 184 | 185 | ## 0.3.3 186 | 187 | This version removes deprecations and updates to latest Quick and Nimble. 188 | 189 | 190 | 191 | ## 0.3.2 192 | 193 | This version updates Nimble to `8.0.7`. 194 | 195 | 196 | 197 | ## 0.3.1 198 | 199 | This version fixes a typo in the `executions(of:)` deprecation. 200 | 201 | 202 | 203 | ## 0.3.0 204 | 205 | This version adds a `Mockable` protocol, which removes the need for the previous recorder approach. `Mock` implements `Mockable` by providing itself as mock. 206 | 207 | 208 | 209 | ## 0.2.0 210 | 211 | This version renames some methods and introduces new typealiases to make the api more readable. 212 | 213 | * The new typealias `MemoryAddress` is used instead of `Int`, which is the underlying type. 214 | * `AnyExecution` has been renamed to `AnyInvokation` 215 | * `Execution` has been renamed to `Invokation` 216 | * `executions(of:)` has been renamed to `invocations(of:)` 217 | * There are new `didInvoke` functions that help simplify inspections. 218 | * The `default` invoke argument has been renamed to `fallback` 219 | 220 | This version also specifies an explicit "current project version", to avoid some Carthage problems. 221 | 222 | 223 | 224 | ## 0.1.0 225 | 226 | This version renames Mock n Roll to Mockery and adds support for Xcode 11 and SPM. 227 | 228 | 229 | 230 | ## 0.0.1 231 | 232 | This is the first versioned push to the CocoaPods trunk, but I will polish it a bit more until I bump it up to a real version. 233 | -------------------------------------------------------------------------------- /Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Resources/Icon.png -------------------------------------------------------------------------------- /Sources/MockingKit/AsyncMockReference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncTests.swift 3 | // MockingKit 4 | // 5 | // Created by Tobias Boogh on 2022-05-04. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This type can be used to create mock function references. 12 | /// 13 | /// Function references get unique IDs, which means that the 14 | /// mock reference instance can be uniquely identified. 15 | public struct AsyncMockReference: Identifiable { 16 | 17 | public init(_ function: @escaping (Arguments) async throws -> Result) { 18 | self.id = UUID() 19 | self.function = function 20 | } 21 | 22 | public let id: UUID 23 | public let function: (Arguments) async throws -> Result 24 | } 25 | -------------------------------------------------------------------------------- /Sources/MockingKit/Internals/Escaping.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Escaping.swift 3 | // MockingKit 4 | // 5 | // Original implementation: https://github.com/devxoul/Stubber 6 | // 7 | 8 | /// These functions are used by mock calls where one or many 9 | /// arguments are escaping blocks. 10 | 11 | import Foundation 12 | 13 | public func escaping(_ arg1: Arg1) -> (Arg1)! { return (arg1) } 14 | public func escaping(_ arg1: Arg1, _ arg2: Arg2) -> (Arg1, Arg2)! { return (arg1, arg2) } 15 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> (Arg1, Arg2, Arg3)! { return (arg1, arg2, arg3) } 16 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> (Arg1, Arg2, Arg3, Arg4)! { return (arg1, arg2, arg3, arg4) } 17 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> (Arg1, Arg2, Arg3, Arg4, Arg5)! { return (arg1, arg2, arg3, arg4, arg5) } 18 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6)! { return (arg1, arg2, arg3, arg4, arg5, arg6) } 19 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7) } 20 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) } 21 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) } 22 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) } 23 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) } 24 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) } 25 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12, _ arg13: Arg13) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13) } 26 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12, _ arg13: Arg13, _ arg14: Arg14) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) } 27 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12, _ arg13: Arg13, _ arg14: Arg14, _ arg15: Arg15) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) } 28 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12, _ arg13: Arg13, _ arg14: Arg14, _ arg15: Arg15, _ arg16: Arg16) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) } 29 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12, _ arg13: Arg13, _ arg14: Arg14, _ arg15: Arg15, _ arg16: Arg16, _ arg17: Arg17) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17) } 30 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12, _ arg13: Arg13, _ arg14: Arg14, _ arg15: Arg15, _ arg16: Arg16, _ arg17: Arg17, _ arg18: Arg18) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18) } 31 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12, _ arg13: Arg13, _ arg14: Arg14, _ arg15: Arg15, _ arg16: Arg16, _ arg17: Arg17, _ arg18: Arg18, _ arg19: Arg19) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19) } 32 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, _ arg10: Arg10, _ arg11: Arg11, _ arg12: Arg12, _ arg13: Arg13, _ arg14: Arg14, _ arg15: Arg15, _ arg16: Arg16, _ arg17: Arg17, _ arg18: Arg18, _ arg19: Arg19, _ arg20: Arg20) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, Arg20)! { return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20) } 33 | -------------------------------------------------------------------------------- /Sources/MockingKit/Mock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mock.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-04-16. 6 | // Copyright © 2019-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This class can be inherited when you want to create mock 12 | /// classes that don't have to inherit any other classes. 13 | /// 14 | /// The class implements ``Mockable`` by returning `self` as 15 | /// the ``mock`` property. 16 | /// 17 | /// Inherit this type instead of implementing the ``Mockable`` 18 | /// protocol, to save some code for every mock you create. 19 | open class Mock: Mockable { 20 | 21 | public init() {} 22 | 23 | public var mock: Mock { self } 24 | 25 | var registeredCalls: [UUID: [AnyCall]] = [:] 26 | var registeredResults: [UUID: Function] = [:] 27 | } 28 | -------------------------------------------------------------------------------- /Sources/MockingKit/MockCall.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockCall.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-11-11. 6 | // Copyright © 2019-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This struct represents function calls, with the provided 12 | /// ``arguments`` and the returned ``result``. 13 | /// 14 | /// Function that don't return anything have a `Void` result. 15 | public struct MockCall: AnyCall { 16 | 17 | public let arguments: Arguments 18 | public let result: Result? 19 | } 20 | 21 | /// This protocol represents any kind of mock function call. 22 | /// It's used to type erase the generic `MockCall`. 23 | public protocol AnyCall {} 24 | -------------------------------------------------------------------------------- /Sources/MockingKit/MockReference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReference.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2020-07-16. 6 | // Copyright © 2020-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This type can be used to create mock function references. 12 | /// 13 | /// Function references get unique IDs, which means that the 14 | /// mock reference instance can be uniquely identified. 15 | public struct MockReference: Identifiable { 16 | 17 | public init(_ function: @escaping (Arguments) throws -> Result) { 18 | self.id = UUID() 19 | self.function = function 20 | } 21 | 22 | public let id: UUID 23 | public let function: (Arguments) throws -> Result 24 | } 25 | -------------------------------------------------------------------------------- /Sources/MockingKit/Mockable+Call.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mockable+Call.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-11-25. 6 | // Copyright © 2019-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Mockable { 12 | 13 | /// Call a mock reference with a `non-optional` result. 14 | /// 15 | /// This will return any pre-registered result, or crash 16 | /// if no result has been registered. 17 | /// 18 | /// - Parameters: 19 | /// - ref: The mock reference to call. 20 | /// - args: The arguments to call the functions with. 21 | func call( 22 | _ ref: MockReference, 23 | args: Arguments, 24 | file: StaticString = #file, 25 | line: UInt = #line, 26 | functionCall: StaticString = #function 27 | ) -> Result { 28 | if Result.self == Void.self { 29 | let void = unsafeBitCast((), to: Result.self) 30 | let call = MockCall(arguments: args, result: void) 31 | registerCall(call, for: ref) 32 | return void 33 | } 34 | 35 | guard let result = try? registeredResult(for: ref)?(args) else { 36 | let message = "You must register a result for '\(functionCall)' with `registerResult(for:)` before calling this function." 37 | preconditionFailure(message, file: file, line: line) 38 | } 39 | let call = MockCall(arguments: args, result: result) 40 | registerCall(call, for: ref) 41 | return result 42 | } 43 | 44 | /// Call a mock reference with a `non-optional` result. 45 | /// 46 | /// This will return any pre-registered result, or crash 47 | /// if no result has been registered. 48 | /// 49 | /// - Parameters: 50 | /// - ref: The mock reference to call. 51 | /// - args: The arguments to call the functions with. 52 | func call( 53 | _ ref: AsyncMockReference, 54 | args: Arguments, 55 | file: StaticString = #file, 56 | line: UInt = #line, 57 | functionCall: StaticString = #function 58 | ) async -> Result { 59 | if Result.self == Void.self { 60 | let void = unsafeBitCast((), to: Result.self) 61 | let call = MockCall(arguments: args, result: void) 62 | registerCall(call, for: ref) 63 | return void 64 | } 65 | 66 | guard let result = try? await registeredResult(for: ref)?(args) else { 67 | let message = "You must register a result for '\(functionCall)' with `registerResult(for:)` before calling this function." 68 | preconditionFailure(message, file: file, line: line) 69 | } 70 | let call = MockCall(arguments: args, result: result) 71 | registerCall(call, for: ref) 72 | return result 73 | } 74 | 75 | /// Call a mock reference with a `non-optional` result. 76 | /// 77 | /// This will return any pre-registered result or return 78 | /// a `fallback` value if no result has been registered. 79 | /// 80 | /// - Parameters: 81 | /// - ref: The mock reference to call. 82 | /// - args: The arguments to call the functions with. 83 | /// - fallback: The value to return if no result has been registered. 84 | func call( 85 | _ ref: MockReference, 86 | args: Arguments, 87 | fallback: @autoclosure () -> Result 88 | ) -> Result { 89 | let result = (try? registeredResult(for: ref)?(args)) ?? fallback() 90 | registerCall(MockCall(arguments: args, result: result), for: ref) 91 | return result 92 | } 93 | 94 | /// Call a mock reference with a `non-optional` result. 95 | /// 96 | /// This will return any pre-registered result or return 97 | /// a `fallback` value if no result has been registered. 98 | /// 99 | /// - Parameters: 100 | /// - ref: The mock reference to call. 101 | /// - args: The arguments to call the functions with. 102 | /// - fallback: The value to return if no result has been registered. 103 | func call( 104 | _ ref: AsyncMockReference, 105 | args: Arguments, 106 | fallback: @autoclosure () -> Result 107 | ) async -> Result { 108 | let result = (try? await registeredResult(for: ref)?(args)) ?? fallback() 109 | registerCall(MockCall(arguments: args, result: result), for: ref) 110 | return result 111 | } 112 | 113 | /// Call a mock reference with an `optional` result. 114 | /// 115 | /// This will return a pre-registered result or `nil` if 116 | /// no result has been registered. 117 | /// 118 | /// - Parameters: 119 | /// - ref: The mock reference to call. 120 | /// - args: The arguments to call the functions with. 121 | func call( 122 | _ ref: MockReference, 123 | args: Arguments 124 | ) -> Result? { 125 | let result = try? registeredResult(for: ref)?(args) 126 | registerCall(MockCall(arguments: args, result: result), for: ref) 127 | return result 128 | } 129 | 130 | /// Call a mock reference with an `optional` result. 131 | /// 132 | /// This will return a pre-registered result or `nil` if 133 | /// no result has been registered. 134 | /// 135 | /// - Parameters: 136 | /// - ref: The mock reference to call. 137 | /// - args: The arguments to call the functions with. 138 | func call( 139 | _ ref: AsyncMockReference, 140 | args: Arguments 141 | ) async -> Result? { 142 | let result = try? await registeredResult(for: ref)?(args) 143 | registerCall(MockCall(arguments: args, result: result), for: ref) 144 | return result 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Sources/MockingKit/Mockable+Inspect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mockable+Inspect.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-11-25. 6 | // Copyright © 2019-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Mockable { 12 | 13 | /// Get all calls to a certain mock reference. 14 | /// 15 | /// - Parameters: 16 | /// - ref: The mock reference to check calls for. 17 | func calls( 18 | to ref: MockReference 19 | ) -> [MockCall] { 20 | registeredCalls(for: ref) 21 | } 22 | 23 | /// Get all calls to a certain mock reference. 24 | /// 25 | /// - Parameters: 26 | /// - refKeyPath: A key path to the mock reference to check calls for. 27 | func calls( 28 | to refKeyPath: KeyPath> 29 | ) -> [MockCall] { 30 | calls(to: self[keyPath: refKeyPath]) 31 | } 32 | 33 | /// Get all calls to a certain async mock reference. 34 | /// 35 | /// - Parameters: 36 | /// - ref: The mock reference to check calls for. 37 | func calls( 38 | to ref: AsyncMockReference 39 | ) -> [MockCall] { 40 | registeredCalls(for: ref) 41 | } 42 | 43 | /// Get all calls to a certain async mock reference. 44 | /// 45 | /// - Parameters: 46 | /// - refKeyPath: A key path to the mock reference to check calls for. 47 | func calls( 48 | to refKeyPath: KeyPath> 49 | ) -> [MockCall] { 50 | calls(to: self[keyPath: refKeyPath]) 51 | } 52 | 53 | /// Check if a mock reference has been called. 54 | /// 55 | /// - Parameters: 56 | /// - ref: The mock reference to check calls for. 57 | func hasCalled( 58 | _ ref: MockReference 59 | ) -> Bool { 60 | calls(to: ref).count > 0 61 | } 62 | 63 | /// Check if a mock reference has been called. 64 | /// 65 | /// - Parameters: 66 | /// - refKeyPath: A key path to the mock reference to check calls for. 67 | func hasCalled( 68 | _ refKeyPath: KeyPath> 69 | ) -> Bool { 70 | hasCalled(self[keyPath: refKeyPath]) 71 | } 72 | 73 | /// Check if an async mock reference has been called. 74 | /// 75 | /// - Parameters: 76 | /// - ref: The mock reference to check calls for. 77 | func hasCalled( 78 | _ ref: AsyncMockReference 79 | ) -> Bool { 80 | calls(to: ref).count > 0 81 | } 82 | 83 | /// Check if an async mock reference has been called. 84 | /// 85 | /// - Parameters: 86 | /// - refKeyPath: A key path to the mock reference to check calls for. 87 | func hasCalled( 88 | _ refKeyPath: KeyPath> 89 | ) -> Bool { 90 | hasCalled(self[keyPath: refKeyPath]) 91 | } 92 | 93 | /// Check if a mock reference has been called. 94 | /// 95 | /// For this function to return `true` the actual number 96 | /// of calls must match the provided `numberOfCalls`. 97 | /// 98 | /// - Parameters: 99 | /// - ref: The mock reference to check calls for. 100 | /// - numberOfTimes: The expected number of calls. 101 | func hasCalled( 102 | _ ref: MockReference, 103 | numberOfTimes: Int 104 | ) -> Bool { 105 | calls(to: ref).count == numberOfTimes 106 | } 107 | 108 | /// Check if a mock reference has been called. 109 | /// 110 | /// For this function to return `true` the actual number 111 | /// of calls must match the provided `numberOfCalls`. 112 | /// 113 | /// - Parameters: 114 | /// - refKeyPath: A key path to the mock reference to check calls for. 115 | /// - numberOfTimes: The expected number of calls. 116 | func hasCalled( 117 | _ refKeyPath: KeyPath>, 118 | numberOfTimes: Int 119 | ) -> Bool { 120 | hasCalled(self[keyPath: refKeyPath], numberOfTimes: numberOfTimes) 121 | } 122 | 123 | /// Check if an async mock reference has been called. 124 | /// 125 | /// For this function to return `true` the actual number 126 | /// of calls must match the provided `numberOfCalls`. 127 | /// 128 | /// - Parameters: 129 | /// - ref: The mock reference to check calls for. 130 | func hasCalled( 131 | _ ref: AsyncMockReference, 132 | numberOfTimes: Int 133 | ) -> Bool { 134 | calls(to: ref).count == numberOfTimes 135 | } 136 | 137 | /// Check if an async mock reference has been called. 138 | /// 139 | /// For this function to return `true` the actual number 140 | /// of calls must match the provided `numberOfCalls`. 141 | /// 142 | /// - Parameters: 143 | /// - refKeyPath: A key path to the mock reference to check calls for. 144 | func hasCalled( 145 | _ refKeyPath: KeyPath>, 146 | numberOfTimes: Int 147 | ) -> Bool { 148 | hasCalled(self[keyPath: refKeyPath], numberOfTimes: numberOfTimes) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Sources/MockingKit/Mockable+Register.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mockable+Register.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-11-25. 6 | // Copyright © 2019-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Mockable { 12 | 13 | /// Register a result value for a mock reference. 14 | /// - Parameters: 15 | /// - ref: The mock reference to register a result for. 16 | /// - result: What to return when the function is called. 17 | func registerResult( 18 | for ref: MockReference, 19 | result: @escaping (Arguments) throws -> Result 20 | ) { 21 | mock.registeredResults[ref.id] = result 22 | } 23 | 24 | /// Register a result value for a mock reference. 25 | /// - Parameters: 26 | /// - refKeyPath: A key path to the mock reference to register a result for. 27 | /// - result: What to return when the function is called. 28 | func registerResult( 29 | for refKeyPath: KeyPath>, 30 | result: @escaping (Arguments) throws -> Result 31 | ) { 32 | registerResult(for: self[keyPath: refKeyPath], result: result) 33 | } 34 | 35 | /// Register a result value for an async mock reference. 36 | /// - Parameters: 37 | /// - ref: The mock reference to register a result for. 38 | /// - result: What to return when the function is called. 39 | func registerResult( 40 | for ref: AsyncMockReference, 41 | result: @escaping (Arguments) async throws -> Result 42 | ) { 43 | mock.registeredResults[ref.id] = result 44 | } 45 | 46 | /// Register a result value for an async mock reference. 47 | /// - Parameters: 48 | /// - refKeyPath: A key path to the mock reference to register a result for. 49 | /// - result: What to return when the function is called. 50 | func registerResult( 51 | for refKeyPath: KeyPath>, 52 | result: @escaping (Arguments) async throws -> Result 53 | ) { 54 | registerResult(for: self[keyPath: refKeyPath], result: result) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/MockingKit/Mockable+Reset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mockable+Reset.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-11-25. 6 | // Copyright © 2019-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Mockable { 12 | 13 | /// Reset all registered calls. 14 | func resetCalls() { 15 | mock.registeredCalls = [:] 16 | } 17 | 18 | /// Reset all registered calls for a mock reference. 19 | /// 20 | /// - Parameters: 21 | /// - ref: The mock reference to reset any calls for. 22 | func resetCalls( 23 | to ref: MockReference 24 | ) { 25 | mock.registeredCalls[ref.id] = [] 26 | } 27 | 28 | /// Reset all registered calls for a mock reference. 29 | /// 30 | /// - Parameters: 31 | /// - refKeyPath: A key path to the mock reference to reset any calls for. 32 | func resetCalls( 33 | to refKeyPath: KeyPath> 34 | ) { 35 | resetCalls(to: self[keyPath: refKeyPath]) 36 | } 37 | 38 | /// Reset all registered calls for a mock reference. 39 | /// 40 | /// - Parameters: 41 | /// - ref: The mock reference to reset any calls for. 42 | func resetCalls( 43 | to ref: AsyncMockReference 44 | ) { 45 | mock.registeredCalls[ref.id] = [] 46 | } 47 | 48 | /// Reset all registered calls for a mock reference. 49 | /// 50 | /// - Parameters: 51 | /// - refKeyPath: A key path to the mock reference to reset any calls for. 52 | func resetCalls( 53 | to refKeyPath: KeyPath> 54 | ) { 55 | resetCalls(to: self[keyPath: refKeyPath]) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/MockingKit/Mockable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mockable.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-11-25. 6 | // Copyright © 2019-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This class can be inherited when you want to create mock 12 | /// classes that have to inherit other classes. 13 | /// 14 | /// To implement this protocol, just inherit your base class 15 | /// and provide a ``mock`` property: 16 | /// 17 | /// ``` 18 | /// class MyMock: BaseClass, Mockable { 19 | /// let mock = Mock() 20 | /// } 21 | /// ``` 22 | /// 23 | /// Implement this protocol instead of inheriting the ``Mock`` 24 | /// base class, to save some code for every mock you create. 25 | public protocol Mockable { 26 | 27 | typealias Function = Any 28 | 29 | var mock: Mock { get } 30 | } 31 | 32 | 33 | // MARK: - Internal Functions 34 | 35 | extension Mockable { 36 | 37 | func registerCall( 38 | _ call: MockCall, 39 | for ref: MockReference 40 | ) { 41 | let calls = mock.registeredCalls[ref.id] ?? [] 42 | mock.registeredCalls[ref.id] = calls + [call] 43 | } 44 | 45 | func registerCall( 46 | _ call: MockCall, 47 | for ref: AsyncMockReference 48 | ) { 49 | let calls = mock.registeredCalls[ref.id] ?? [] 50 | mock.registeredCalls[ref.id] = calls + [call] 51 | } 52 | 53 | func registeredCalls( 54 | for ref: MockReference 55 | ) -> [MockCall] { 56 | let calls = mock.registeredCalls[ref.id] 57 | return (calls as? [MockCall]) ?? [] 58 | } 59 | 60 | func registeredCalls( 61 | for ref: AsyncMockReference 62 | ) -> [MockCall] { 63 | let calls = mock.registeredCalls[ref.id] 64 | return (calls as? [MockCall]) ?? [] 65 | } 66 | 67 | func registeredResult( 68 | for ref: MockReference 69 | ) -> ((Arguments) throws -> Result)? { 70 | mock.registeredResults[ref.id] as? (Arguments) throws -> Result 71 | } 72 | 73 | func registeredResult( 74 | for ref: AsyncMockReference 75 | ) -> ((Arguments) async throws -> Result)? { 76 | mock.registeredResults[ref.id] as? (Arguments) async throws -> Result 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/MockingKit/MockingKit.docc/Getting-Started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | This article explains how to get started with MockingKit. 4 | 5 | @Metadata { 6 | 7 | @PageImage( 8 | purpose: card, 9 | source: "Page", 10 | alt: "Page icon" 11 | ) 12 | 13 | @PageColor(blue) 14 | } 15 | 16 | 17 | ## Terminology 18 | 19 | Before we start, let's clarify some terms: 20 | 21 | * **Mock** is a simulated object that mimic the behaviour of real objects in controlled ways. 22 | * **Mocking** is to use configurable and inspectable functionality, for instance in unit tests. 23 | * **Call/Invoke** is calling a function in a way that records the call and its arguments & result. 24 | * **Registration** is to register dynamic return values for a mock function, based on its arguments. 25 | * **Inspection** is to inspect a recorded call, e.g. to verify that it has been called, its arguments, etc. 26 | 27 | Let's have a look at how this works in MockingKit. 28 | 29 | 30 | 31 | ## Creating a mock 32 | 33 | MockingKit lets you mock any protocol or open class, after which you can **call** functions, **register** results, **record** method invocations, and **inspect** recorded calls. 34 | 35 | For instance, consider this simple protocol: 36 | 37 | ```swift 38 | protocol MyProtocol { 39 | 40 | func doStuff(int: Int, string: String) -> String 41 | } 42 | ``` 43 | 44 | With MockingKit, you can easily create a mock implementation of this protocol: 45 | 46 | ```swift 47 | import MockingKit 48 | 49 | class MyMock: Mock, MyProtocol { 50 | 51 | // Define a lazy reference for each function you want to mock 52 | lazy var doStuffRef = MockReference(doStuff) 53 | 54 | // Functions must then call the reference to be recorded 55 | func doStuff(int: Int, string: String) -> String { 56 | call(doStuffRef, args: (int, string)) 57 | } 58 | } 59 | ``` 60 | 61 | To mock a class, you instead have to subclass the class and implement the ``Mockable`` protocol: 62 | 63 | ```swift 64 | import MockingKit 65 | 66 | class MockUserDefaults: UserDefaults, Mockable { 67 | 68 | // You must provide a mock when implementing Mockable 69 | var mock = Mock() 70 | 71 | // You can now create lazy references just like in the protocol mock above 72 | } 73 | ``` 74 | 75 | ``Mock`` is actually just a ``Mockable`` that returns itself as its ``Mockable/mock``. 76 | 77 | With the mock in place, you can now start mocking functionality in your unit tests or app. 78 | 79 | 80 | 81 | ## Using the mock 82 | 83 | We can now use the mock to register dynamic function results, call mocked functions and inspect all recorded calls. 84 | 85 | The easiest and most compact way to do this is to use keypaths: 86 | 87 | ```swift 88 | // Create a mock 89 | let mock = MyMock() 90 | 91 | // Register a doStuff result 92 | mock.registerResult(for: \.doStuffRef) { args in String(args.1.reversed()) } 93 | 94 | // Calling doStuff will now return the pre-registered result 95 | let result = mock.doStuff(int: 42, string: "string") // => "gnirts" 96 | 97 | // You can now inspect calls made to doStuff 98 | let calls = mock.calls(to: \.doStuffRef) // => 1 item 99 | calls[0].arguments.0 // => 42 100 | calls[0].arguments.1 // => "string" 101 | calls[0].result // => "gnirts" 102 | mock.hasCalled(\.doStuffRef) // => true 103 | mock.hasCalled(\.doStuffRef, numberOfTimes: 1) // => true 104 | mock.hasCalled(\.doStuffRef, numberOfTimes: 2) // => false 105 | ``` 106 | 107 | You can configure the mock in any way you want to change the behavior of your tests at any time, call the mock instead of a real implementation (e.g. a network service, a database etc.), and inspect the mock to ensure that your code calls it as expected. 108 | 109 | 110 | 111 | ## Important about registering return values 112 | 113 | There are some things to consider when registering mock function return values: 114 | 115 | * **Optional** functions will return **nil** if you don't register a return value before calling them. 116 | * **Non-optional** functions will **crash** if you don't register a return value before calling them. 117 | 118 | You can register new return values at any time, for instance to try many different return values within the same test or test case. 119 | 120 | 121 | ## Multiple function arguments 122 | 123 | Since mock function arguments are handled as tuples, inspection behaves a bit different if a mocked function has multiple arguments. 124 | 125 | For instance, consider this protocol: 126 | 127 | ```swift 128 | protocol MyProtocol { 129 | 130 | func one(_ int: Int) -> String 131 | func two(_ int: Int, _ string: String) -> String 132 | } 133 | ``` 134 | 135 | A MockingKit mock of this protocol could look like this: 136 | 137 | ```swift 138 | class MyMock: Mock, MyProtocol { 139 | 140 | lazy var oneRef = MockReference(one) 141 | lazy var twoRef = MockReference(two) 142 | 143 | func one(_ int: Int) -> String { 144 | call(oneRef, args: (int, string)) 145 | } 146 | 147 | func two(_ int: Int, _ string: String) -> String { 148 | call(oneRef, args: (int, string)) 149 | } 150 | } 151 | ``` 152 | 153 | Functions with a single argument can inspect the argument directly, while functions with two or more arguments require inspecting the arguments as tuples: 154 | 155 | ```swift 156 | let mock = MyMock() 157 | mock.registerResult(for: mock.oneRef) { args in -args } 158 | mock.registerResult(for: mock.twoRef) { args in String(args.1.reversed()) } 159 | let res1 = mock.one(int: 1) // => -1 160 | let res2 = mock.two(int: 2, string: "string") // => "gnirts" 161 | let inv1 = mock.calls(to: mock.oneRef) // => 1 item 162 | let inv2 = mock.calls(to: mock.twoRef) // => 1 item 163 | inv1[0].arguments // => -1 164 | inv2[0].arguments.0 // => 2 165 | inv2[0].arguments.1 // => "message" 166 | ``` 167 | 168 | There is no upper-limit to the number of arguments you can use in a mocked function. 169 | 170 | 171 | 172 | ## Multiple functions with the same name 173 | 174 | Mocked function references require some considerations when protocol or class has multiple functions with the same name. 175 | 176 | For instance, consider a protocol that looks like this: 177 | 178 | ```swift 179 | protocol MyProtocol { 180 | 181 | func doStuff(with int: Int) -> Bool 182 | func doStuff(with int: Int, string: String) -> String 183 | } 184 | ``` 185 | 186 | You must then specify the function signature when creating the mock references: 187 | 188 | ```swift 189 | class MyMock: Mock, MyProtocol { 190 | 191 | lazy var doStuffWithIntRef = MockReference(doStuff as (Int) -> Bool) 192 | lazy var doStuffWithIntAndStringRef = MockReference(doStuff as (Int, String) -> String) 193 | 194 | func doStuff(with int: Int) -> Bool { 195 | call(doStuffWithInt, args: (int)) 196 | } 197 | 198 | func doStuff(with int: Int, string: String) -> String { 199 | call(doStuffWithIntAndStringRef, args: (int, string)) 200 | } 201 | } 202 | ``` 203 | 204 | This gives you a unique references for each function, which you can use just like above. The rest works exactly the same. 205 | 206 | 207 | 208 | ## Properties 209 | 210 | Properties can't currently be mocked, since the reference model requires a function. 211 | 212 | If you want to mock properties, you can invoke custom function references in the mock's getter and/or setter. 213 | 214 | 215 | 216 | ## Async functions 217 | 218 | MockingKit supports Swift concurrency and lets you mock any `async` function. 219 | 220 | Mocking `async` functions works exactly like mocking non-async functions. No additional code is required. 221 | 222 | 223 | 224 | ## Completion blocks 225 | 226 | Functions with completion blocks are just `Void` functions where the completion block is just an argument. 227 | 228 | Mocking these kind of functions works exactly like mocking any other functions. No additional code is required. 229 | -------------------------------------------------------------------------------- /Sources/MockingKit/MockingKit.docc/MockingKit.md: -------------------------------------------------------------------------------- 1 | # ``MockingKit`` 2 | 3 | MockingKit is a Swift SDK that lets you easily mock protocols and classes in `Swift`. 4 | 5 | 6 | 7 | ## Overview 8 | 9 | ![MockingKit logo](Logo.png) 10 | 11 | MockingKit is a mocking library for Swift that lets you create mocks of any protocol or class. This can be used to mock dependencies in unit tests, and to fake not yet implemented features in your apps. 12 | 13 | MockingKit automatically records all method calls, to let you verify that your code behaves as you expect. You can also register register dynamic function results to control your test outcome. 14 | 15 | MockingKit doesn't require any setup or build scripts, and puts no restrictions on your code or architecture. Just create a mock, set up how you want to use and inspect it, and you're good to go. 16 | 17 | 18 | 19 | ## Installation 20 | 21 | MockingKit can be installed with the Swift Package Manager: 22 | 23 | ``` 24 | https://github.com/danielsaidi/MockingKit.git 25 | ``` 26 | 27 | 28 | 29 | ## Getting started 30 | 31 | The article helps you get started with MockingKit. 32 | 33 | 34 | 35 | ## Repository 36 | 37 | For more information, source code, etc., visit the [project repository](https://github.com/danielsaidi/MockingKit). 38 | 39 | 40 | 41 | ## License 42 | 43 | MockingKit is available under the MIT license. 44 | 45 | 46 | 47 | ## Topics 48 | 49 | ### Articles 50 | 51 | - 52 | 53 | ### Foundation 54 | 55 | - ``Mock`` 56 | - ``Mockable`` 57 | - ``MockCall`` 58 | - ``MockReference`` 59 | - ``AsyncMockReference`` 60 | 61 | ### System Mocks 62 | 63 | - ``MockNotificationCenter`` 64 | - ``MockPasteboard`` 65 | - ``MockUserDefaults`` 66 | 67 | 68 | 69 | [Email]: mailto:daniel.saidi@gmail.com 70 | [Website]: https://danielsaidi.com 71 | [GitHub]: https://github.com/danielsaidi 72 | [OpenSource]: https://danielsaidi.com/opensource 73 | [Sponsors]: https://github.com/sponsors/danielsaidi 74 | -------------------------------------------------------------------------------- /Sources/MockingKit/MockingKit.docc/Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Sources/MockingKit/MockingKit.docc/Resources/Logo.png -------------------------------------------------------------------------------- /Sources/MockingKit/MockingKit.docc/Resources/Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/MockingKit/c2aa0af61c2a9953458642c1f5e7d09bd16316b9/Sources/MockingKit/MockingKit.docc/Resources/Page.png -------------------------------------------------------------------------------- /Sources/MockingKit/Mocks/MockNotificationCenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockNotificationCenter.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2020-08-03. 6 | // Copyright © 2020-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This class can be used to mock `NotificationCenter`. 12 | open class MockNotificationCenter: NotificationCenter, Mockable, @unchecked Sendable { 13 | 14 | public lazy var addObserverForNameRef = MockReference(mockAddObserverForName) 15 | public lazy var addObserverWithSelectorRef = MockReference(mockAddObserverWithSelector) 16 | public lazy var postNotificationRef = MockReference(mockPostNotification) 17 | public lazy var postNotificationNameRef = MockReference(mockPostNotificationName) 18 | public lazy var removeObserverRef = MockReference(mockRemoveObserver) 19 | 20 | public let mock = Mock() 21 | 22 | open override func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?) { 23 | mockAddObserverWithSelector(observer, selector: aSelector, name: aName, object: anObject) 24 | } 25 | 26 | open override func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol { 27 | mockAddObserverForName(name, object: obj, queue: queue, using: block) 28 | } 29 | 30 | open override func post(_ notification: Notification) { 31 | mockPostNotification(notification) 32 | } 33 | 34 | open override func post(name aName: NSNotification.Name, object anObject: Any?) { 35 | mockPostNotificationName(name: aName, object: anObject, userInfo: nil) 36 | } 37 | 38 | open override func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable: Any]? = nil) { 39 | mockPostNotificationName(name: aName, object: anObject, userInfo: aUserInfo) 40 | } 41 | 42 | open override func removeObserver(_ observer: Any) { 43 | mockRemoveObserver(observer, name: nil, object: nil) 44 | } 45 | 46 | open override func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object anObject: Any?) { 47 | mockRemoveObserver(observer, name: aName, object: anObject) 48 | } 49 | } 50 | 51 | /** 52 | These functions provide more explicit names, which makes it 53 | easier to create the refs without having to use typealiases. 54 | */ 55 | private extension MockNotificationCenter { 56 | 57 | func mockAddObserverForName(_ name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol { 58 | call(addObserverForNameRef, args: (name, obj, queue, block)) 59 | } 60 | 61 | func mockAddObserverWithSelector(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?) { 62 | call(addObserverWithSelectorRef, args: (observer, aSelector, aName, anObject)) 63 | } 64 | 65 | func mockPostNotification(_ notification: Notification) { 66 | call(postNotificationRef, args: (notification)) 67 | } 68 | 69 | func mockPostNotificationName(name aName: NSNotification.Name, object: Any?, userInfo: [AnyHashable: Any]?) { 70 | call(postNotificationNameRef, args: (aName, object, userInfo)) 71 | } 72 | 73 | func mockRemoveObserver(_ observer: Any, name aName: NSNotification.Name?, object anObject: Any?) { 74 | call(removeObserverRef, args: (observer, aName, anObject)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/MockingKit/Mocks/MockPasteboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockPasteboard.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-05-28. 6 | // Copyright © 2021-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | 12 | /// This class can be used to mock `UIPasteboard`. 13 | /// 14 | /// This mock only mocks `setData(_:forPasteboardType:)` for 15 | /// now, but you can subclass it and mock more functionality. 16 | open class MockPasteboard: UIPasteboard, Mockable, @unchecked Sendable { 17 | 18 | public lazy var setDataRef = MockReference(setData) 19 | 20 | public let mock = Mock() 21 | 22 | open override func setData(_ data: Data, forPasteboardType pasteboardType: String) { 23 | call(setDataRef, args: (data, pasteboardType)) 24 | } 25 | } 26 | #elseif os(macOS) 27 | import AppKit 28 | 29 | /** 30 | This class can be used to mock `NSPasteboard`. 31 | 32 | This mock only mocks `setValue(_:forKey:)` for now, but you 33 | can subclass this class and mock more functionality. 34 | */ 35 | public class MockPasteboard: NSPasteboard, Mockable { 36 | 37 | public lazy var setValueForKeyRef = MockReference(setValueForKey) 38 | 39 | public let mock = Mock() 40 | 41 | public override func setValue(_ value: Any?, forKey key: String) { 42 | setValueForKey(value, key: key) 43 | } 44 | 45 | /// This way to work around functions with the same name, 46 | /// especially when there are static and class functions 47 | /// with the same name, can also be used. Just add a new, 48 | /// private function and have it call the reference. The 49 | /// real function (as above) can then call this function. 50 | func setValueForKey(_ value: Any?, key: String) { 51 | call(setValueForKeyRef, args: (value, key)) 52 | } 53 | } 54 | #else 55 | import Foundation 56 | 57 | /** 58 | This class can be used to mock a system pasteboard. 59 | 60 | This mock doesn't do anything, since this platform does not 61 | have a pasteboard. It's only here for documentation harmony. 62 | */ 63 | open class MockPasteboard: Mock {} 64 | #endif 65 | -------------------------------------------------------------------------------- /Sources/MockingKit/Mocks/MockUserDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockReference.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2020-07-17. 6 | // Copyright © 2020-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This class can be used to mock `UserDefaults`. 12 | open class MockUserDefaults: UserDefaults, Mockable { 13 | 14 | public lazy var boolRef = MockReference(bool) 15 | public lazy var arrayRef = MockReference(array) 16 | public lazy var dataRef = MockReference(data) 17 | public lazy var doubleRef = MockReference(double) 18 | public lazy var floatRef = MockReference(float) 19 | public lazy var integerRef = MockReference(integer) 20 | public lazy var objectRef = MockReference(object) 21 | public lazy var stringRef = MockReference(string) 22 | public lazy var stringArrayRef = MockReference(stringArray) 23 | public lazy var urlRef = MockReference(url) 24 | 25 | public lazy var setBoolRef = MockReference(set as (Bool, String) -> Void) 26 | public lazy var setDoubleRef = MockReference(set as (Double, String) -> Void) 27 | public lazy var setIntegerRef = MockReference(set as (Int, String) -> Void) 28 | public lazy var setFloatRef = MockReference(set as (Float, String) -> Void) 29 | public lazy var setUrlRef = MockReference(set as (URL?, String) -> Void) 30 | public lazy var setValueRef = MockReference(setValueWithInstance as (Any?, String) -> Void) 31 | 32 | public var mock = Mock() 33 | 34 | 35 | open override func array(forKey defaultName: String) -> [Any]? { 36 | mock.call(arrayRef, args: defaultName) 37 | } 38 | 39 | open override func bool(forKey defaultName: String) -> Bool { 40 | mock.call(boolRef, args: defaultName) 41 | } 42 | 43 | open override func data(forKey defaultName: String) -> Data? { 44 | mock.call(dataRef, args: defaultName) 45 | } 46 | 47 | open override func double(forKey defaultName: String) -> Double { 48 | mock.call(doubleRef, args: defaultName) 49 | } 50 | 51 | open override func float(forKey defaultName: String) -> Float { 52 | mock.call(floatRef, args: defaultName) 53 | } 54 | 55 | open override func integer(forKey defaultName: String) -> Int { 56 | mock.call(integerRef, args: defaultName) 57 | } 58 | 59 | open override func object(forKey defaultName: String) -> Any? { 60 | mock.call(objectRef, args: defaultName) 61 | } 62 | 63 | open override func string(forKey defaultName: String) -> String? { 64 | mock.call(stringRef, args: defaultName) 65 | } 66 | 67 | open override func stringArray(forKey defaultName: String) -> [String]? { 68 | mock.call(stringArrayRef, args: defaultName) 69 | } 70 | 71 | open override func url(forKey defaultName: String) -> URL? { 72 | mock.call(urlRef, args: defaultName) 73 | } 74 | 75 | open override func set(_ value: Bool, forKey defaultName: String) { 76 | mock.call(self.setBoolRef, args: (value, defaultName)) 77 | } 78 | 79 | open override func set(_ value: Double, forKey defaultName: String) { 80 | mock.call(self.setDoubleRef, args: (value, defaultName)) 81 | } 82 | 83 | open override func set(_ value: Float, forKey defaultName: String) { 84 | mock.call(self.setFloatRef, args: (value, defaultName)) 85 | } 86 | 87 | open override func set(_ value: Int, forKey defaultName: String) { 88 | mock.call(self.setIntegerRef, args: (value, defaultName)) 89 | } 90 | 91 | open override func set(_ url: URL?, forKey defaultName: String) { 92 | mock.call(self.setUrlRef, args: (url, defaultName)) 93 | } 94 | 95 | open override func set(_ value: Any?, forKey defaultName: String) { 96 | setValueWithInstance(value, forKey: defaultName) 97 | } 98 | 99 | open override func setValue(_ value: Any?, forKey key: String) { 100 | setValueWithInstance(value, forKey: key) 101 | } 102 | } 103 | 104 | private extension MockUserDefaults { 105 | 106 | func setValueWithInstance(_ value: Any?, forKey key: String) { 107 | mock.call(self.setValueRef, args: (value, key)) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Tests/MockingKitTests/Foundation/MockNotificationCenterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockNotificationCenterTests.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2020-08-03. 6 | // Copyright © 2020-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MockingKit 11 | import XCTest 12 | 13 | final class MockNotificationCenterTests: XCTestCase { 14 | 15 | private var center: MockNotificationCenter! 16 | private var obj: TestClass! 17 | 18 | private let notification = Notification.Name("com.test.notification") 19 | private let info: [AnyHashable: Any] = ["key": "value"] 20 | 21 | override func setUp() { 22 | center = MockNotificationCenter() 23 | obj = TestClass() 24 | } 25 | 26 | private func value(_ value: Any?, is obj: TestClass) -> Bool { 27 | (value as? TestClass)?.id == obj.id 28 | } 29 | 30 | func testCanMockAddingObserverWithName() { 31 | let observer = TestClass() 32 | var blockExecution: Notification? 33 | let block: (Notification) -> Void = { notification in blockExecution = notification } 34 | let queue = OperationQueue.current 35 | center.registerResult(for: center.addObserverForNameRef) { _ in observer } 36 | let result = center.addObserver(forName: notification, object: obj, queue: queue, using: block) 37 | let calls = center.calls(to: center.addObserverForNameRef).first 38 | XCTAssertEqual(calls?.arguments.0, notification) 39 | XCTAssertTrue(value(calls?.arguments.1, is: obj)) 40 | XCTAssertTrue(calls?.arguments.2 === queue) 41 | calls?.arguments.3(Notification(name: notification)) 42 | XCTAssertEqual(blockExecution?.name, notification) 43 | XCTAssertTrue(calls?.result === observer) 44 | XCTAssertTrue(result === observer) 45 | } 46 | 47 | func testCanMockAddingObserverWithSelector() { 48 | let observer = TestClass() 49 | center.addObserver(observer, selector: #selector(observer.testFunc), name: notification, object: obj) 50 | let call = center.calls(to: center.addObserverWithSelectorRef).first 51 | XCTAssertTrue(value(call?.arguments.0, is: observer)) 52 | XCTAssertNotNil(call?.arguments.1) 53 | XCTAssertEqual(call?.arguments.2, notification) 54 | XCTAssertTrue(value(call?.arguments.3, is: obj)) 55 | } 56 | 57 | func testCanMockPostingNotification() { 58 | center.post(Notification(name: notification)) 59 | let calls = center.calls(to: center.postNotificationRef).first 60 | XCTAssertEqual(calls?.arguments.name, notification) 61 | } 62 | 63 | func testCanMockPostingNotificationWithData() { 64 | center.post(name: notification, object: nil) 65 | center.post(name: notification, object: obj) 66 | center.post(name: notification, object: obj, userInfo: nil) 67 | center.post(name: notification, object: obj, userInfo: info) 68 | let calls = center.calls(to: center.postNotificationNameRef) 69 | 70 | XCTAssertEqual(calls.count, 4) 71 | 72 | XCTAssertEqual(calls[0].arguments.0, notification) 73 | XCTAssertNil(calls[0].arguments.1) 74 | XCTAssertNil(calls[0].arguments.2) 75 | 76 | XCTAssertEqual(calls[1].arguments.0, notification) 77 | XCTAssertTrue(value(calls[1].arguments.1, is: obj)) 78 | XCTAssertNil(calls[1].arguments.2) 79 | 80 | XCTAssertEqual(calls[2].arguments.0, notification) 81 | XCTAssertTrue(value(calls[1].arguments.1, is: obj)) 82 | XCTAssertNil(calls[2].arguments.2) 83 | 84 | XCTAssertEqual(calls[3].arguments.0, notification) 85 | XCTAssertTrue(value(calls[1].arguments.1, is: obj)) 86 | XCTAssertEqual(calls[3].arguments.2?["key"] as? String, "value") 87 | } 88 | 89 | func testCanMockRemovingObserver() { 90 | let observer = TestClass() 91 | center.removeObserver(observer) 92 | center.removeObserver(observer, name: notification, object: obj) 93 | 94 | let calls = center.calls(to: center.removeObserverRef) 95 | 96 | XCTAssertEqual(calls.count, 2) 97 | 98 | XCTAssertTrue(value(calls[0].arguments.0, is: observer)) 99 | XCTAssertNil(calls[0].arguments.1) 100 | XCTAssertNil(calls[0].arguments.2) 101 | 102 | XCTAssertTrue(value(calls[1].arguments.0, is: observer)) 103 | XCTAssertEqual(calls[1].arguments.1, notification) 104 | XCTAssertTrue(value(calls[1].arguments.2, is: obj)) 105 | } 106 | } 107 | 108 | private class TestClass: NSObject { 109 | 110 | let id = UUID() 111 | 112 | @objc func testFunc() {} 113 | } 114 | -------------------------------------------------------------------------------- /Tests/MockingKitTests/Foundation/MockUserDefaultsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockUserDefaultsTests.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2020-07-17. 6 | // Copyright © 2020-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MockingKit 11 | import XCTest 12 | 13 | final class MockUserDefaultsTests: XCTestCase { 14 | 15 | var defaults: MockUserDefaults! 16 | 17 | override func setUp() { 18 | defaults = MockUserDefaults() 19 | } 20 | 21 | func testCanMockGettingArray() { 22 | defaults.registerResult(for: defaults.arrayRef) { _ in [1, 2, 3] } 23 | let result = defaults.array(forKey: "abc") as? [Int] 24 | XCTAssertEqual(result, [1, 2, 3]) 25 | } 26 | 27 | func testCanMockGettingBool() { 28 | defaults.registerResult(for: defaults.boolRef) { _ in true } 29 | XCTAssertEqual(defaults.bool(forKey: "abc"), true) 30 | } 31 | 32 | func testCanMockGettingData() { 33 | let data = "123".data(using: .utf8) 34 | defaults.registerResult(for: defaults.dataRef) { _ in data } 35 | XCTAssertEqual(defaults.data(forKey: "abc"), data) 36 | } 37 | 38 | func testCanMockGettingDouble() { 39 | defaults.registerResult(for: defaults.doubleRef) { _ in 123 } 40 | XCTAssertEqual(defaults.double(forKey: "abc"), 123) 41 | } 42 | 43 | func testCanMockGettingFloat() { 44 | defaults.registerResult(for: defaults.floatRef) { _ in 123 } 45 | XCTAssertEqual(defaults.float(forKey: "abc"), 123) 46 | } 47 | 48 | func testCanMockGettingInteger() { 49 | defaults.registerResult(for: defaults.integerRef) { _ in 123 } 50 | XCTAssertEqual(defaults.integer(forKey: "abc"), 123) 51 | } 52 | 53 | func testCanMockGettingObject() { 54 | let obj = "123" 55 | defaults.registerResult(for: defaults.objectRef) { _ in obj } 56 | let result = defaults.object(forKey: "abc") as? String 57 | XCTAssertEqual(result, "123") 58 | } 59 | 60 | func testCanMockGettingString() { 61 | defaults.registerResult(for: defaults.stringRef) { _ in "123" } 62 | XCTAssertEqual(defaults.string(forKey: "abc"), "123") 63 | } 64 | 65 | func testCanMockGettingUrl() { 66 | let url = URL(string: "http://test.com")! 67 | defaults.registerResult(for: defaults.urlRef) { _ in url } 68 | XCTAssertEqual(defaults.url(forKey: "abc"), url) 69 | } 70 | 71 | func testCanMockSettingBool() { 72 | defaults.set(true, forKey: "abc") 73 | let calls = defaults.calls(to: defaults.setBoolRef).first 74 | XCTAssertEqual(calls?.arguments.0, true) 75 | XCTAssertEqual(calls?.arguments.1, "abc") 76 | } 77 | 78 | func testCanMockSettingDouble() { 79 | defaults.set(123 as Double, forKey: "abc") 80 | let calls = defaults.calls(to: defaults.setDoubleRef).first 81 | XCTAssertEqual(calls?.arguments.0, 123) 82 | XCTAssertEqual(calls?.arguments.1, "abc") 83 | } 84 | 85 | func testCanMockSettingFloat() { 86 | defaults.set(123 as Float, forKey: "abc") 87 | let calls = defaults.calls(to: defaults.setFloatRef).first 88 | XCTAssertEqual(calls?.arguments.0, 123) 89 | XCTAssertEqual(calls?.arguments.1, "abc") 90 | } 91 | 92 | func testCanMockSettingInt() { 93 | defaults.set(123, forKey: "abc") 94 | let calls = defaults.calls(to: defaults.setIntegerRef).first 95 | XCTAssertEqual(calls?.arguments.0, 123) 96 | XCTAssertEqual(calls?.arguments.1, "abc") 97 | } 98 | 99 | func testCanMockSettingURL() { 100 | let url = URL(string: "http://test.com")! 101 | defaults.set(url, forKey: "abc") 102 | let calls = defaults.calls(to: defaults.setUrlRef).first 103 | XCTAssertEqual(calls?.arguments.0, url) 104 | XCTAssertEqual(calls?.arguments.1, "abc") 105 | } 106 | 107 | func testCanMockSettingAny() { 108 | let value = "123" 109 | defaults.set(value, forKey: "abc") 110 | defaults.setValue(value, forKey: "def") 111 | let calls = defaults.calls(to: defaults.setValueRef) 112 | XCTAssertEqual(calls.first?.arguments.0 as? String, value) 113 | XCTAssertEqual(calls.first?.arguments.1, "abc") 114 | XCTAssertEqual(calls.last?.arguments.0 as? String, value) 115 | XCTAssertEqual(calls.last?.arguments.1, "def") 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Tests/MockingKitTests/GenericTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockableTests.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2020-07-17. 6 | // Copyright © 2020-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MockingKit 11 | import XCTest 12 | 13 | final class GenericTests: XCTestCase { 14 | 15 | func testCanMockGettingArray() { 16 | let mock = GenericMock() 17 | mock.doit(with: 42) 18 | let call = mock.calls(to: mock.doitRef) 19 | XCTAssertEqual(call.count, 1) 20 | XCTAssertEqual(call[0].arguments, 42) 21 | } 22 | } 23 | 24 | private class GenericMock: Mock { 25 | 26 | lazy var doitRef = MockReference(doit) 27 | 28 | func doit(with value: T) { 29 | call(doitRef, args: (value)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/MockingKitTests/MockableAsyncTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockableAsyncTests.swift 3 | // MockingKit 4 | // 5 | // Created by Tobias Boogh on 2022-05-04. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MockingKit 11 | 12 | class MockableAsyncTests: XCTestCase { 13 | 14 | fileprivate var mock: TestClass! 15 | 16 | override func setUp() { 17 | mock = TestClass() 18 | } 19 | 20 | func testCanRegisterFunctionWithReferenceIdAsResult() { 21 | let ref = mock.functionWithIntResultRef 22 | mock.registerResult(for: ref) { _, int in int * 2 } 23 | let obj = mock.mock.registeredResults[ref.id] 24 | XCTAssertNotNil(obj) 25 | } 26 | 27 | func testCanCallFunctionWithNonOptionalResultAndDifferentResultTypes() async { 28 | let user = User(name: "a user") 29 | let thing = Thing(name: "a thing") 30 | 31 | mock.registerResult(for: mock.functionWithIntResultRef) { _ in 123 } 32 | mock.registerResult(for: mock.functionWithStringResultRef) { _ in "a string" } 33 | mock.registerResult(for: \.functionWithStructResultRef) { _ in user } 34 | mock.registerResult(for: \.functionWithClassResultRef) { _ in thing } 35 | 36 | let intResult = await mock.functionWithIntResult(arg1: "abc", arg2: 123) 37 | let stringResult = await mock.functionWithStringResult(arg1: "abc", arg2: 123) 38 | let structResult = await mock.functionWithStructResult(arg1: "abc", arg2: 123) 39 | let classResult = await mock.functionWithClassResult(arg1: "abc", arg2: 123) 40 | 41 | XCTAssertEqual(intResult, 123) 42 | XCTAssertEqual(stringResult, "a string") 43 | XCTAssertEqual(structResult, user) 44 | XCTAssertTrue(classResult === thing) 45 | } 46 | 47 | func testCanCallFunctionWithNonOptionalResultAndDifferentReturnValuesForDifferentArgumentValues() async { 48 | mock.registerResult(for: mock.functionWithIntResultRef) { _, arg2 in arg2 } 49 | mock.registerResult(for: \.functionWithStringResultRef) { arg1, _ in arg1 } 50 | 51 | let intResult = await mock.functionWithIntResult(arg1: "abc", arg2: 123) 52 | let intResult2 = await mock.functionWithIntResult(arg1: "abc", arg2: 456) 53 | let stringResult = await mock.functionWithStringResult(arg1: "abc", arg2: 123) 54 | let stringResult2 = await mock.functionWithStringResult(arg1: "def", arg2: 123) 55 | 56 | XCTAssertEqual(intResult, 123) 57 | XCTAssertEqual(intResult2, 456) 58 | XCTAssertEqual(stringResult, "abc") 59 | XCTAssertEqual(stringResult2, "def") 60 | } 61 | 62 | func testCallingFunctionWithNonOptionalResultRegistersCalls() async { 63 | mock.registerResult(for: mock.functionWithIntResultRef) { _, arg2 in arg2 } 64 | mock.registerResult(for: \.functionWithStringResultRef) { arg1, _ in arg1 } 65 | 66 | _ = await mock.functionWithIntResult(arg1: "abc", arg2: 123) 67 | _ = await mock.functionWithIntResult(arg1: "abc", arg2: 456) 68 | _ = await mock.functionWithIntResult(arg1: "abc", arg2: 789) 69 | _ = await mock.functionWithStringResult(arg1: "abc", arg2: 123) 70 | _ = await mock.functionWithStringResult(arg1: "def", arg2: 123) 71 | 72 | let intCalls = mock.calls(to: mock.functionWithIntResultRef) 73 | let strCalls = mock.calls(to: mock.functionWithStringResultRef) 74 | 75 | XCTAssertEqual(intCalls.count, 3) 76 | XCTAssertEqual(strCalls.count, 2) 77 | XCTAssertEqual(intCalls[0].arguments.0, "abc") 78 | XCTAssertEqual(intCalls[0].arguments.1, 123) 79 | XCTAssertEqual(intCalls[1].arguments.0, "abc") 80 | XCTAssertEqual(intCalls[1].arguments.1, 456) 81 | XCTAssertEqual(intCalls[2].arguments.0, "abc") 82 | XCTAssertEqual(intCalls[2].arguments.1, 789) 83 | XCTAssertEqual(strCalls[0].arguments.0, "abc") 84 | XCTAssertEqual(strCalls[0].arguments.1, 123) 85 | XCTAssertEqual(strCalls[1].arguments.0, "def") 86 | XCTAssertEqual(strCalls[1].arguments.1, 123) 87 | } 88 | 89 | func testCallingFunctionWithOptionalResultDoesNotFailWithPreconditionFailureIfNoResultIsRegistered() async { 90 | let intResult = await mock.functionWithOptionalIntResult(arg1: "abc", arg2: 123) 91 | let stringResult = await mock.functionWithOptionalStringResult(arg1: "abc", arg2: 123) 92 | let structResult = await mock.functionWithOptionalStructResult(arg1: "abc", arg2: 123) 93 | let classResult = await mock.functionWithOptionalClassResult(arg1: "abc", arg2: 123) 94 | 95 | XCTAssertNil(intResult) 96 | XCTAssertNil(stringResult) 97 | XCTAssertNil(structResult) 98 | XCTAssertNil(classResult) 99 | } 100 | 101 | func testCallingFunctionWithOptionalResultSupportsDifferentResultTypes() async { 102 | let user = User(name: "a user") 103 | let thing = Thing(name: "a thing") 104 | 105 | mock.registerResult(for: mock.functionWithOptionalIntResultRef) { _ in 123 } 106 | mock.registerResult(for: mock.functionWithOptionalStringResultRef) { _ in "a string" } 107 | mock.registerResult(for: \.functionWithOptionalStructResultRef) { _ in user } 108 | mock.registerResult(for: \.functionWithOptionalClassResultRef) { _ in thing } 109 | 110 | let intResult = await mock.functionWithOptionalIntResult(arg1: "abc", arg2: 123) 111 | let stringResult = await mock.functionWithOptionalStringResult(arg1: "abc", arg2: 123) 112 | let structResult = await mock.functionWithOptionalStructResult(arg1: "abc", arg2: 123) 113 | let classResult = await mock.functionWithOptionalClassResult(arg1: "abc", arg2: 123) 114 | 115 | XCTAssertEqual(intResult, 123) 116 | XCTAssertEqual(stringResult, "a string") 117 | XCTAssertEqual(structResult, user) 118 | XCTAssertTrue(classResult === thing) 119 | } 120 | 121 | func testCallingFunctionWithOptionalResultCanRegisterDifferentReturnValuesForDifferentArgumentValues() async { 122 | mock.registerResult(for: mock.functionWithOptionalIntResultRef) { _, arg2 in arg2 } 123 | mock.registerResult(for: \.functionWithOptionalStringResultRef) { arg1, _ in arg1 } 124 | 125 | let intResult = await mock.functionWithOptionalIntResult(arg1: "abc", arg2: 123) 126 | let int2Result = await mock.functionWithOptionalIntResult(arg1: "abc", arg2: 456) 127 | let stringResult = await mock.functionWithOptionalStringResult(arg1: "abc", arg2: 123) 128 | let string2Result = await mock.functionWithOptionalStringResult(arg1: "def", arg2: 123) 129 | 130 | XCTAssertEqual(intResult, 123) 131 | XCTAssertEqual(int2Result, 456) 132 | XCTAssertEqual(stringResult, "abc") 133 | XCTAssertEqual(string2Result, "def") 134 | } 135 | func testCallingFunctionWithOptionalResultRegistersCalls() async { 136 | mock.registerResult(for: mock.functionWithOptionalIntResultRef) { _, arg2 in arg2 } 137 | mock.registerResult(for: \.functionWithOptionalStringResultRef) { arg1, _ in arg1 } 138 | 139 | _ = await mock.functionWithOptionalIntResult(arg1: "abc", arg2: 123) 140 | _ = await mock.functionWithOptionalIntResult(arg1: "abc", arg2: 456) 141 | _ = await mock.functionWithOptionalIntResult(arg1: "abc", arg2: 789) 142 | _ = await mock.functionWithOptionalStringResult(arg1: "abc", arg2: 123) 143 | _ = await mock.functionWithOptionalStringResult(arg1: "def", arg2: 123) 144 | 145 | let intCalls = mock.calls(to: mock.functionWithOptionalIntResultRef) 146 | let strCalls = mock.calls(to: \.functionWithOptionalStringResultRef) 147 | 148 | XCTAssertEqual(intCalls.count, 3) 149 | XCTAssertEqual(strCalls.count, 2) 150 | XCTAssertEqual(intCalls[0].arguments.0, "abc") 151 | XCTAssertEqual(intCalls[0].arguments.1, 123) 152 | XCTAssertEqual(intCalls[1].arguments.0, "abc") 153 | XCTAssertEqual(intCalls[1].arguments.1, 456) 154 | XCTAssertEqual(intCalls[2].arguments.0, "abc") 155 | XCTAssertEqual(intCalls[2].arguments.1, 789) 156 | XCTAssertEqual(strCalls[0].arguments.0, "abc") 157 | XCTAssertEqual(strCalls[0].arguments.1, 123) 158 | XCTAssertEqual(strCalls[1].arguments.0, "def") 159 | XCTAssertEqual(strCalls[1].arguments.1, 123) 160 | } 161 | 162 | func testCallingFunctionWithFallbackReturnsDefaultValueIfNoValueIsRegistered() async { 163 | let intResult = await mock.call(mock.functionWithIntResultRef, args: ("abc", 123), fallback: 456) 164 | let stringResult = await mock.call(mock.functionWithStringResultRef, args: ("abc", 123), fallback: "def") 165 | 166 | XCTAssertEqual(intResult, 456) 167 | XCTAssertEqual(stringResult, "def") 168 | } 169 | 170 | func testCallingFunctionWithFallbackReturnsRegisteredValueIfAValueIsRegistered() async { 171 | mock.registerResult(for: mock.functionWithIntResultRef) { _ in 123 } 172 | mock.registerResult(for: \.functionWithStringResultRef) { _ in "a string" } 173 | 174 | let intResult = await mock.call(mock.functionWithIntResultRef, args: ("abc", 123), fallback: 456) 175 | let stringResult = await mock.call(mock.functionWithStringResultRef, args: ("abc", 123), fallback: "def") 176 | 177 | XCTAssertEqual(intResult, 123) 178 | XCTAssertEqual(stringResult, "a string") 179 | } 180 | 181 | func testCallingFunctionWithVoidResultDoesNotFailWithPreconditionFailureIfNoResultIsRegistered() async { 182 | await mock.functionWithVoidResult(arg1: "abc", arg2: 123) 183 | } 184 | 185 | func testCallingFunctionWithVoidResultRegistersCalls() async { 186 | mock.registerResult(for: mock.functionWithOptionalIntResultRef) { _, arg2 in arg2 } 187 | mock.registerResult(for: \.functionWithOptionalStringResultRef) { arg1, _ in arg1 } 188 | 189 | await mock.functionWithVoidResult(arg1: "abc", arg2: 123) 190 | await mock.functionWithVoidResult(arg1: "abc", arg2: 456) 191 | await mock.functionWithVoidResult(arg1: "abc", arg2: 789) 192 | 193 | let calls = mock.calls(to: mock.functionWithVoidResultRef) 194 | 195 | XCTAssertEqual(calls.count, 3) 196 | XCTAssertEqual(calls[0].arguments.0, "abc") 197 | XCTAssertEqual(calls[0].arguments.1, 123) 198 | XCTAssertEqual(calls[1].arguments.0, "abc") 199 | XCTAssertEqual(calls[1].arguments.1, 456) 200 | XCTAssertEqual(calls[2].arguments.0, "abc") 201 | XCTAssertEqual(calls[2].arguments.1, 789) 202 | } 203 | 204 | func testInspectingCallsRegistersAllCalls() async { 205 | await mock.functionWithVoidResult(arg1: "abc", arg2: 123) 206 | await mock.functionWithVoidResult(arg1: "abc", arg2: 456) 207 | await mock.functionWithVoidResult(arg1: "abc", arg2: 789) 208 | 209 | let calls = mock.calls(to: mock.functionWithVoidResultRef) 210 | 211 | XCTAssertEqual(calls.count, 3) 212 | } 213 | 214 | func testInspectingCallsCanVerifyIfAtLeastOneCallHasBeenMade() async { 215 | XCTAssertFalse(mock.hasCalled(mock.functionWithVoidResultRef)) 216 | await mock.functionWithVoidResult(arg1: "abc", arg2: 123) 217 | XCTAssertTrue(mock.hasCalled(\.functionWithVoidResultRef)) 218 | await mock.functionWithVoidResult(arg1: "abc", arg2: 456) 219 | XCTAssertTrue(mock.hasCalled(mock.functionWithVoidResultRef)) 220 | } 221 | 222 | func testInspectingCallsCanVerifyIfAnExactNumberOrCallsHaveBeenMade() async { 223 | XCTAssertFalse(mock.hasCalled(mock.functionWithVoidResultRef, numberOfTimes: 2)) 224 | await mock.functionWithVoidResult(arg1: "abc", arg2: 123) 225 | XCTAssertFalse(mock.hasCalled(\.functionWithVoidResultRef, numberOfTimes: 2)) 226 | await mock.functionWithVoidResult(arg1: "abc", arg2: 456) 227 | XCTAssertTrue(mock.hasCalled(mock.functionWithVoidResultRef, numberOfTimes: 2)) 228 | } 229 | 230 | func testResettingCallsCanResetAllCalls() async { 231 | mock.registerResult(for: mock.functionWithIntResultRef) { _, arg2 in arg2 } 232 | mock.registerResult(for: \.functionWithStringResultRef) { arg1, _ in arg1 } 233 | 234 | _ = await mock.functionWithIntResult(arg1: "abc", arg2: 123) 235 | _ = await mock.functionWithStringResult(arg1: "abc", arg2: 123) 236 | 237 | mock.resetCalls() 238 | 239 | XCTAssertFalse(mock.hasCalled(mock.functionWithIntResultRef)) 240 | XCTAssertFalse(mock.hasCalled(\.functionWithStringResultRef)) 241 | } 242 | 243 | func testResettingCalls_canResetAllCallsForACertainFunction() async { 244 | mock.registerResult(for: mock.functionWithIntResultRef) { _, arg2 in arg2 } 245 | mock.registerResult(for: mock.functionWithStringResultRef) { arg1, _ in arg1 } 246 | 247 | _ = await mock.functionWithIntResult(arg1: "abc", arg2: 123) 248 | _ = await mock.functionWithStringResult(arg1: "abc", arg2: 123) 249 | 250 | mock.resetCalls(to: mock.functionWithIntResultRef) 251 | 252 | XCTAssertFalse(mock.hasCalled(mock.functionWithIntResultRef)) 253 | XCTAssertTrue(mock.hasCalled(\.functionWithStringResultRef)) 254 | } 255 | } 256 | 257 | private class TestClass: AsyncTestProtocol, Mockable { 258 | 259 | var mock = Mock() 260 | 261 | lazy var functionWithIntResultRef = AsyncMockReference(functionWithIntResult) 262 | lazy var functionWithStringResultRef = AsyncMockReference(functionWithStringResult) 263 | lazy var functionWithStructResultRef = AsyncMockReference(functionWithStructResult) 264 | lazy var functionWithClassResultRef = AsyncMockReference(functionWithClassResult) 265 | lazy var functionWithOptionalIntResultRef = AsyncMockReference(functionWithOptionalIntResult) 266 | lazy var functionWithOptionalStringResultRef = AsyncMockReference(functionWithOptionalStringResult) 267 | lazy var functionWithOptionalStructResultRef = AsyncMockReference(functionWithOptionalStructResult) 268 | lazy var functionWithOptionalClassResultRef = AsyncMockReference(functionWithOptionalClassResult) 269 | lazy var functionWithVoidResultRef = AsyncMockReference(functionWithVoidResult) 270 | 271 | func functionWithIntResult(arg1: String, arg2: Int) async -> Int { 272 | await call(functionWithIntResultRef, args: (arg1, arg2)) 273 | } 274 | 275 | func functionWithStringResult(arg1: String, arg2: Int) async -> String { 276 | await call(functionWithStringResultRef, args: (arg1, arg2)) 277 | } 278 | 279 | func functionWithStructResult(arg1: String, arg2: Int) async -> User { 280 | await call(functionWithStructResultRef, args: (arg1, arg2)) 281 | } 282 | 283 | func functionWithClassResult(arg1: String, arg2: Int) async -> Thing { 284 | await call(functionWithClassResultRef, args: (arg1, arg2)) 285 | } 286 | 287 | func functionWithOptionalIntResult(arg1: String, arg2: Int) async -> Int? { 288 | await call(functionWithOptionalIntResultRef, args: (arg1, arg2)) 289 | } 290 | 291 | func functionWithOptionalStringResult(arg1: String, arg2: Int) async -> String? { 292 | await call(functionWithOptionalStringResultRef, args: (arg1, arg2)) 293 | } 294 | 295 | func functionWithOptionalStructResult(arg1: String, arg2: Int) async -> User? { 296 | await call(functionWithOptionalStructResultRef, args: (arg1, arg2)) 297 | } 298 | 299 | func functionWithOptionalClassResult(arg1: String, arg2: Int) async -> Thing? { 300 | await call(functionWithOptionalClassResultRef, args: (arg1, arg2)) 301 | } 302 | 303 | func functionWithVoidResult(arg1: String, arg2: Int) async { 304 | await call(functionWithVoidResultRef, args: (arg1, arg2)) 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /Tests/MockingKitTests/MockableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockableTests.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-11-25. 6 | // Copyright © 2020-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MockingKit 11 | 12 | class MockableTests: XCTestCase { 13 | 14 | fileprivate var mock: TestClass! 15 | 16 | override func setUp() { 17 | mock = TestClass() 18 | } 19 | 20 | func testCanRegisterFunctionWithReferenceIdAsResult() { 21 | let ref = mock.functionWithIntResultRef 22 | mock.registerResult(for: ref) { _, int in int * 2 } 23 | let obj = mock.mock.registeredResults[ref.id] 24 | XCTAssertNotNil(obj) 25 | } 26 | 27 | func testCanCallFunctionWithNonOptionalResultAndDifferentResultTypes() { 28 | let user = User(name: "a user") 29 | let thing = Thing(name: "a thing") 30 | 31 | mock.registerResult(for: mock.functionWithIntResultRef) { _ in 123 } 32 | mock.registerResult(for: mock.functionWithStringResultRef) { _ in "a string" } 33 | mock.registerResult(for: \.functionWithStructResultRef) { _ in user } 34 | mock.registerResult(for: \.functionWithClassResultRef) { _ in thing } 35 | 36 | let intResult = mock.functionWithIntResult(arg1: "abc", arg2: 123) 37 | let stringResult = mock.functionWithStringResult(arg1: "abc", arg2: 123) 38 | let structResult = mock.functionWithStructResult(arg1: "abc", arg2: 123) 39 | let classResult = mock.functionWithClassResult(arg1: "abc", arg2: 123) 40 | 41 | XCTAssertEqual(intResult, 123) 42 | XCTAssertEqual(stringResult, "a string") 43 | XCTAssertEqual(structResult, user) 44 | XCTAssertTrue(classResult === thing) 45 | } 46 | 47 | func testCanCallFunctionWithNonOptionalResultAndDifferentReturnValuesForDifferentArgumentValues() { 48 | mock.registerResult(for: mock.functionWithIntResultRef) { _, arg2 in arg2 } 49 | mock.registerResult(for: \.functionWithStringResultRef) { arg1, _ in arg1 } 50 | 51 | let intResult = mock.functionWithIntResult(arg1: "abc", arg2: 123) 52 | let intResult2 = mock.functionWithIntResult(arg1: "abc", arg2: 456) 53 | let stringResult = mock.functionWithStringResult(arg1: "abc", arg2: 123) 54 | let stringResult2 = mock.functionWithStringResult(arg1: "def", arg2: 123) 55 | 56 | XCTAssertEqual(intResult, 123) 57 | XCTAssertEqual(intResult2, 456) 58 | XCTAssertEqual(stringResult, "abc") 59 | XCTAssertEqual(stringResult2, "def") 60 | } 61 | 62 | func testCallingFunctionWithNonOptionalResultRegistersCalls() { 63 | mock.registerResult(for: mock.functionWithIntResultRef) { _, arg2 in arg2 } 64 | mock.registerResult(for: \.functionWithStringResultRef) { arg1, _ in arg1 } 65 | 66 | _ = mock.functionWithIntResult(arg1: "abc", arg2: 123) 67 | _ = mock.functionWithIntResult(arg1: "abc", arg2: 456) 68 | _ = mock.functionWithIntResult(arg1: "abc", arg2: 789) 69 | _ = mock.functionWithStringResult(arg1: "abc", arg2: 123) 70 | _ = mock.functionWithStringResult(arg1: "def", arg2: 123) 71 | 72 | let intCalls = mock.calls(to: mock.functionWithIntResultRef) 73 | let strCalls = mock.calls(to: mock.functionWithStringResultRef) 74 | 75 | XCTAssertEqual(intCalls.count, 3) 76 | XCTAssertEqual(strCalls.count, 2) 77 | XCTAssertEqual(intCalls[0].arguments.0, "abc") 78 | XCTAssertEqual(intCalls[0].arguments.1, 123) 79 | XCTAssertEqual(intCalls[1].arguments.0, "abc") 80 | XCTAssertEqual(intCalls[1].arguments.1, 456) 81 | XCTAssertEqual(intCalls[2].arguments.0, "abc") 82 | XCTAssertEqual(intCalls[2].arguments.1, 789) 83 | XCTAssertEqual(strCalls[0].arguments.0, "abc") 84 | XCTAssertEqual(strCalls[0].arguments.1, 123) 85 | XCTAssertEqual(strCalls[1].arguments.0, "def") 86 | XCTAssertEqual(strCalls[1].arguments.1, 123) 87 | } 88 | 89 | func testCallingFunctionWithOptionalResultDoesNotFailWithPreconditionFailureIfNoResultIsRegistered() { 90 | let intResult = mock.functionWithOptionalIntResult(arg1: "abc", arg2: 123) 91 | let stringResult = mock.functionWithOptionalStringResult(arg1: "abc", arg2: 123) 92 | let structResult = mock.functionWithOptionalStructResult(arg1: "abc", arg2: 123) 93 | let classResult = mock.functionWithOptionalClassResult(arg1: "abc", arg2: 123) 94 | 95 | XCTAssertNil(intResult) 96 | XCTAssertNil(stringResult) 97 | XCTAssertNil(structResult) 98 | XCTAssertNil(classResult) 99 | } 100 | 101 | func testCallingFunctionWithOptionalResultSupportsDifferentResultTypes() { 102 | let user = User(name: "a user") 103 | let thing = Thing(name: "a thing") 104 | 105 | mock.registerResult(for: mock.functionWithOptionalIntResultRef) { _ in 123 } 106 | mock.registerResult(for: mock.functionWithOptionalStringResultRef) { _ in "a string" } 107 | mock.registerResult(for: \.functionWithOptionalStructResultRef) { _ in user } 108 | mock.registerResult(for: \.functionWithOptionalClassResultRef) { _ in thing } 109 | 110 | let intResult = mock.functionWithOptionalIntResult(arg1: "abc", arg2: 123) 111 | let stringResult = mock.functionWithOptionalStringResult(arg1: "abc", arg2: 123) 112 | let structResult = mock.functionWithOptionalStructResult(arg1: "abc", arg2: 123) 113 | let classResult = mock.functionWithOptionalClassResult(arg1: "abc", arg2: 123) 114 | 115 | XCTAssertEqual(intResult, 123) 116 | XCTAssertEqual(stringResult, "a string") 117 | XCTAssertEqual(structResult, user) 118 | XCTAssertTrue(classResult === thing) 119 | } 120 | 121 | func testCallingFunctionWithOptionalResultCanRegisterDifferentReturnValuesForDifferentArgumentValues() { 122 | mock.registerResult(for: mock.functionWithOptionalIntResultRef) { _, arg2 in arg2 } 123 | mock.registerResult(for: \.functionWithOptionalStringResultRef) { arg1, _ in arg1 } 124 | 125 | let intResult = mock.functionWithOptionalIntResult(arg1: "abc", arg2: 123) 126 | let int2Result = mock.functionWithOptionalIntResult(arg1: "abc", arg2: 456) 127 | let stringResult = mock.functionWithOptionalStringResult(arg1: "abc", arg2: 123) 128 | let string2Result = mock.functionWithOptionalStringResult(arg1: "def", arg2: 123) 129 | 130 | XCTAssertEqual(intResult, 123) 131 | XCTAssertEqual(int2Result, 456) 132 | XCTAssertEqual(stringResult, "abc") 133 | XCTAssertEqual(string2Result, "def") 134 | } 135 | 136 | func testCallingFunctionWithOptionalResultRegistersCalls() { 137 | mock.registerResult(for: mock.functionWithOptionalIntResultRef) { _, arg2 in arg2 } 138 | mock.registerResult(for: \.functionWithOptionalStringResultRef) { arg1, _ in arg1 } 139 | 140 | _ = mock.functionWithOptionalIntResult(arg1: "abc", arg2: 123) 141 | _ = mock.functionWithOptionalIntResult(arg1: "abc", arg2: 456) 142 | _ = mock.functionWithOptionalIntResult(arg1: "abc", arg2: 789) 143 | _ = mock.functionWithOptionalStringResult(arg1: "abc", arg2: 123) 144 | _ = mock.functionWithOptionalStringResult(arg1: "def", arg2: 123) 145 | 146 | let intCalls = mock.calls(to: mock.functionWithOptionalIntResultRef) 147 | let strCalls = mock.calls(to: \.functionWithOptionalStringResultRef) 148 | 149 | XCTAssertEqual(intCalls.count, 3) 150 | XCTAssertEqual(strCalls.count, 2) 151 | XCTAssertEqual(intCalls[0].arguments.0, "abc") 152 | XCTAssertEqual(intCalls[0].arguments.1, 123) 153 | XCTAssertEqual(intCalls[1].arguments.0, "abc") 154 | XCTAssertEqual(intCalls[1].arguments.1, 456) 155 | XCTAssertEqual(intCalls[2].arguments.0, "abc") 156 | XCTAssertEqual(intCalls[2].arguments.1, 789) 157 | XCTAssertEqual(strCalls[0].arguments.0, "abc") 158 | XCTAssertEqual(strCalls[0].arguments.1, 123) 159 | XCTAssertEqual(strCalls[1].arguments.0, "def") 160 | XCTAssertEqual(strCalls[1].arguments.1, 123) 161 | } 162 | 163 | func testCallingFunctionWithFallbackReturnsDefaultValueIfNoValueIsRegistered() { 164 | let intResult = mock.call(mock.functionWithIntResultRef, args: ("abc", 123), fallback: 456) 165 | let stringResult = mock.call(mock.functionWithStringResultRef, args: ("abc", 123), fallback: "def") 166 | 167 | XCTAssertEqual(intResult, 456) 168 | XCTAssertEqual(stringResult, "def") 169 | } 170 | 171 | func testCallingFunctionWithFallbackReturnsRegisteredValueIfAValueIsRegistered() { 172 | mock.registerResult(for: mock.functionWithIntResultRef) { _ in 123 } 173 | mock.registerResult(for: \.functionWithStringResultRef) { _ in "a string" } 174 | 175 | let intResult = mock.call(mock.functionWithIntResultRef, args: ("abc", 123), fallback: 456) 176 | let stringResult = mock.call(mock.functionWithStringResultRef, args: ("abc", 123), fallback: "def") 177 | 178 | XCTAssertEqual(intResult, 123) 179 | XCTAssertEqual(stringResult, "a string") 180 | } 181 | 182 | func testCallingFunctionWithVoidResultDoesNotFailWithPreconditionFailureIfNoResultIsRegistered() { 183 | mock.functionWithVoidResult(arg1: "abc", arg2: 123) 184 | } 185 | 186 | func testCallingFunctionWithVoidResultRegistersCalls() { 187 | mock.registerResult(for: mock.functionWithOptionalIntResultRef) { _, arg2 in arg2 } 188 | mock.registerResult(for: \.functionWithOptionalStringResultRef) { arg1, _ in arg1 } 189 | 190 | mock.functionWithVoidResult(arg1: "abc", arg2: 123) 191 | mock.functionWithVoidResult(arg1: "abc", arg2: 456) 192 | mock.functionWithVoidResult(arg1: "abc", arg2: 789) 193 | 194 | let calls = mock.calls(to: mock.functionWithVoidResultRef) 195 | 196 | XCTAssertEqual(calls.count, 3) 197 | XCTAssertEqual(calls[0].arguments.0, "abc") 198 | XCTAssertEqual(calls[0].arguments.1, 123) 199 | XCTAssertEqual(calls[1].arguments.0, "abc") 200 | XCTAssertEqual(calls[1].arguments.1, 456) 201 | XCTAssertEqual(calls[2].arguments.0, "abc") 202 | XCTAssertEqual(calls[2].arguments.1, 789) 203 | } 204 | 205 | 206 | func testInspectingCalls_RegistersAllCalls() { 207 | mock.functionWithVoidResult(arg1: "abc", arg2: 123) 208 | mock.functionWithVoidResult(arg1: "abc", arg2: 456) 209 | mock.functionWithVoidResult(arg1: "abc", arg2: 789) 210 | 211 | let calls = mock.calls(to: mock.functionWithVoidResultRef) 212 | let callsKeypath = mock.calls(to: \.functionWithVoidResultRef) 213 | 214 | XCTAssertEqual(calls.count, 3) 215 | XCTAssertEqual(callsKeypath.count, 3) 216 | } 217 | 218 | func testInspectingCalls_canVerifyIfAtLeastOneCallHasBeenMade() { 219 | XCTAssertFalse(mock.hasCalled(mock.functionWithVoidResultRef)) 220 | mock.functionWithVoidResult(arg1: "abc", arg2: 123) 221 | XCTAssertTrue(mock.hasCalled(\.functionWithVoidResultRef)) 222 | mock.functionWithVoidResult(arg1: "abc", arg2: 456) 223 | XCTAssertTrue(mock.hasCalled(mock.functionWithVoidResultRef)) 224 | } 225 | 226 | func testInspectingCalls_CanVerifyIfAnExactNumberOrCallsHaveBeenMade() { 227 | XCTAssertFalse(mock.hasCalled(mock.functionWithVoidResultRef, numberOfTimes: 2)) 228 | mock.functionWithVoidResult(arg1: "abc", arg2: 123) 229 | XCTAssertFalse(mock.hasCalled(\.functionWithVoidResultRef, numberOfTimes: 2)) 230 | mock.functionWithVoidResult(arg1: "abc", arg2: 456) 231 | XCTAssertTrue(mock.hasCalled(mock.functionWithVoidResultRef, numberOfTimes: 2)) 232 | } 233 | 234 | func testResettingCalls_CanResetAllCalls() { 235 | mock.registerResult(for: mock.functionWithIntResultRef) { _, arg2 in arg2 } 236 | mock.registerResult(for: \.functionWithStringResultRef) { arg1, _ in arg1 } 237 | 238 | _ = mock.functionWithIntResult(arg1: "abc", arg2: 123) 239 | _ = mock.functionWithStringResult(arg1: "abc", arg2: 123) 240 | 241 | mock.resetCalls() 242 | 243 | XCTAssertFalse(mock.hasCalled(mock.functionWithIntResultRef)) 244 | XCTAssertFalse(mock.hasCalled(\.functionWithStringResultRef)) 245 | } 246 | 247 | func testResettingCalls_canResetAllCallsForACertainFunction() { 248 | mock.registerResult(for: mock.functionWithIntResultRef) { _, arg2 in arg2 } 249 | mock.registerResult(for: \.functionWithStringResultRef) { arg1, _ in arg1 } 250 | 251 | _ = mock.functionWithIntResult(arg1: "abc", arg2: 123) 252 | _ = mock.functionWithStringResult(arg1: "abc", arg2: 123) 253 | 254 | mock.resetCalls(to: mock.functionWithIntResultRef) 255 | 256 | XCTAssertFalse(mock.hasCalled(mock.functionWithIntResultRef)) 257 | XCTAssertTrue(mock.hasCalled(\.functionWithStringResultRef)) 258 | } 259 | } 260 | 261 | private class TestClass: AsyncTestProtocol, Mockable { 262 | 263 | var mock = Mock() 264 | 265 | lazy var functionWithIntResultRef = MockReference(functionWithIntResult) 266 | lazy var functionWithStringResultRef = MockReference(functionWithStringResult) 267 | lazy var functionWithStructResultRef = MockReference(functionWithStructResult) 268 | lazy var functionWithClassResultRef = MockReference(functionWithClassResult) 269 | lazy var functionWithOptionalIntResultRef = MockReference(functionWithOptionalIntResult) 270 | lazy var functionWithOptionalStringResultRef = MockReference(functionWithOptionalStringResult) 271 | lazy var functionWithOptionalStructResultRef = MockReference(functionWithOptionalStructResult) 272 | lazy var functionWithOptionalClassResultRef = MockReference(functionWithOptionalClassResult) 273 | lazy var functionWithVoidResultRef = MockReference(functionWithVoidResult) 274 | 275 | func functionWithIntResult(arg1: String, arg2: Int) -> Int { 276 | call(functionWithIntResultRef, args: (arg1, arg2)) 277 | } 278 | 279 | func functionWithStringResult(arg1: String, arg2: Int) -> String { 280 | call(functionWithStringResultRef, args: (arg1, arg2)) 281 | } 282 | 283 | func functionWithStructResult(arg1: String, arg2: Int) -> User { 284 | call(functionWithStructResultRef, args: (arg1, arg2)) 285 | } 286 | 287 | func functionWithClassResult(arg1: String, arg2: Int) -> Thing { 288 | call(functionWithClassResultRef, args: (arg1, arg2)) 289 | } 290 | 291 | func functionWithOptionalIntResult(arg1: String, arg2: Int) -> Int? { 292 | call(functionWithOptionalIntResultRef, args: (arg1, arg2)) 293 | } 294 | 295 | func functionWithOptionalStringResult(arg1: String, arg2: Int) -> String? { 296 | call(functionWithOptionalStringResultRef, args: (arg1, arg2)) 297 | } 298 | 299 | func functionWithOptionalStructResult(arg1: String, arg2: Int) -> User? { 300 | call(functionWithOptionalStructResultRef, args: (arg1, arg2)) 301 | } 302 | 303 | func functionWithOptionalClassResult(arg1: String, arg2: Int) -> Thing? { 304 | call(functionWithOptionalClassResultRef, args: (arg1, arg2)) 305 | } 306 | 307 | func functionWithVoidResult(arg1: String, arg2: Int) { 308 | call(functionWithVoidResultRef, args: (arg1, arg2)) 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /Tests/MockingKitTests/TestTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestProtocol.swift 3 | // MockingKit 4 | // 5 | // Created by Daniel Saidi on 2019-04-16. 6 | // Copyright © 2019-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol TestProtocol { 12 | 13 | func functionWithIntResult(arg1: String, arg2: Int) -> Int 14 | func functionWithStringResult(arg1: String, arg2: Int) -> String 15 | func functionWithStructResult(arg1: String, arg2: Int) -> User 16 | func functionWithClassResult(arg1: String, arg2: Int) -> Thing 17 | 18 | func functionWithOptionalIntResult(arg1: String, arg2: Int) -> Int? 19 | func functionWithOptionalStringResult(arg1: String, arg2: Int) -> String? 20 | func functionWithOptionalStructResult(arg1: String, arg2: Int) -> User? 21 | func functionWithOptionalClassResult(arg1: String, arg2: Int) -> Thing? 22 | 23 | func functionWithVoidResult(arg1: String, arg2: Int) 24 | 25 | func asyncFunction(arg1: String, completion: @escaping (Error?) -> Void) 26 | } 27 | 28 | protocol AsyncTestProtocol { 29 | 30 | func functionWithIntResult(arg1: String, arg2: Int) async -> Int 31 | func functionWithStringResult(arg1: String, arg2: Int) async -> String 32 | func functionWithStructResult(arg1: String, arg2: Int) async -> User 33 | func functionWithClassResult(arg1: String, arg2: Int) async -> Thing 34 | 35 | func functionWithOptionalIntResult(arg1: String, arg2: Int) async -> Int? 36 | func functionWithOptionalStringResult(arg1: String, arg2: Int) async -> String? 37 | func functionWithOptionalStructResult(arg1: String, arg2: Int) async -> User? 38 | func functionWithOptionalClassResult(arg1: String, arg2: Int) async -> Thing? 39 | 40 | func functionWithVoidResult(arg1: String, arg2: Int) async 41 | } 42 | 43 | struct User: Equatable { 44 | 45 | var name: String 46 | } 47 | 48 | class Thing { 49 | 50 | init(name: String) { 51 | self.name = name 52 | } 53 | 54 | var name: String 55 | } 56 | -------------------------------------------------------------------------------- /package_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new project version for the current project. 5 | # You can customize this to fit your project when you copy these scripts. 6 | # You can pass in a custom branch if you don't want to use the default one. 7 | 8 | SCRIPT="scripts/package_version.sh" 9 | chmod +x $SCRIPT 10 | bash $SCRIPT 11 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds a for all provided . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # build.sh [ default:iOS macOS tvOS watchOS xrOS] 10 | # e.g. `bash scripts/build.sh MyTarget iOS macOS` 11 | 12 | # Exit immediately if a command exits with a non-zero status 13 | set -e 14 | 15 | # Verify that all required arguments are provided 16 | if [ $# -eq 0 ]; then 17 | echo "Error: This script requires at least one argument" 18 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 19 | echo "For instance: $0 MyTarget iOS macOS" 20 | exit 1 21 | fi 22 | 23 | # Define argument variables 24 | TARGET=$1 25 | 26 | # Remove TARGET from arguments list 27 | shift 28 | 29 | # Define platforms variable 30 | if [ $# -eq 0 ]; then 31 | set -- iOS macOS tvOS watchOS xrOS 32 | fi 33 | PLATFORMS=$@ 34 | 35 | # A function that builds $TARGET for a specific platform 36 | build_platform() { 37 | 38 | # Define a local $PLATFORM variable 39 | local PLATFORM=$1 40 | 41 | # Build $TARGET for the $PLATFORM 42 | echo "Building $TARGET for $PLATFORM..." 43 | if ! xcodebuild -scheme $TARGET -derivedDataPath .build -destination generic/platform=$PLATFORM; then 44 | echo "Failed to build $TARGET for $PLATFORM" 45 | return 1 46 | fi 47 | 48 | # Complete successfully 49 | echo "Successfully built $TARGET for $PLATFORM" 50 | } 51 | 52 | # Start script 53 | echo "" 54 | echo "Building $TARGET for [$PLATFORMS]..." 55 | echo "" 56 | 57 | # Loop through all platforms and call the build function 58 | for PLATFORM in $PLATFORMS; do 59 | if ! build_platform "$PLATFORM"; then 60 | exit 1 61 | fi 62 | done 63 | 64 | # Complete successfully 65 | echo "" 66 | echo "Building $TARGET completed successfully!" 67 | echo "" 68 | -------------------------------------------------------------------------------- /scripts/chmod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script makes all scripts in this folder executable. 5 | 6 | # Usage: 7 | # scripts_chmod.sh 8 | # e.g. `bash scripts/chmod.sh` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Use the script folder to refer to other scripts. 14 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 15 | 16 | # Find all .sh files in the FOLDER except chmod.sh 17 | find "$FOLDER" -name "*.sh" ! -name "chmod.sh" -type f | while read -r script; do 18 | chmod +x "$script" 19 | done 20 | -------------------------------------------------------------------------------- /scripts/docc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC for a and certain . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | # The documentation ends up in to .build/docs-. 8 | 9 | # Usage: 10 | # docc.sh [ default:iOS macOS tvOS watchOS xrOS] 11 | # e.g. `bash scripts/docc.sh MyTarget iOS macOS` 12 | 13 | # Exit immediately if a command exits with a non-zero status 14 | set -e 15 | 16 | # Fail if any command in a pipeline fails 17 | set -o pipefail 18 | 19 | # Verify that all required arguments are provided 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires at least one argument" 22 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 23 | echo "For instance: $0 MyTarget iOS macOS" 24 | exit 1 25 | fi 26 | 27 | # Define argument variables 28 | TARGET=$1 29 | TARGET_LOWERCASED=$(echo "$1" | tr '[:upper:]' '[:lower:]') 30 | 31 | # Remove TARGET from arguments list 32 | shift 33 | 34 | # Define platforms variable 35 | if [ $# -eq 0 ]; then 36 | set -- iOS macOS tvOS watchOS xrOS 37 | fi 38 | PLATFORMS=$@ 39 | 40 | # Prepare the package for DocC 41 | swift package resolve; 42 | 43 | # A function that builds $TARGET for a specific platform 44 | build_platform() { 45 | 46 | # Define a local $PLATFORM variable and set an exit code 47 | local PLATFORM=$1 48 | local EXIT_CODE=0 49 | 50 | # Define the build folder name, based on the $PLATFORM 51 | case $PLATFORM in 52 | "iOS") 53 | DEBUG_PATH="Debug-iphoneos" 54 | ;; 55 | "macOS") 56 | DEBUG_PATH="Debug" 57 | ;; 58 | "tvOS") 59 | DEBUG_PATH="Debug-appletvos" 60 | ;; 61 | "watchOS") 62 | DEBUG_PATH="Debug-watchos" 63 | ;; 64 | "xrOS") 65 | DEBUG_PATH="Debug-xros" 66 | ;; 67 | *) 68 | echo "Error: Unsupported platform '$PLATFORM'" 69 | exit 1 70 | ;; 71 | esac 72 | 73 | # Build $TARGET docs for the $PLATFORM 74 | echo "Building $TARGET docs for $PLATFORM..." 75 | if ! xcodebuild docbuild -scheme $TARGET -derivedDataPath .build/docbuild -destination "generic/platform=$PLATFORM"; then 76 | echo "Error: Failed to build documentation for $PLATFORM" >&2 77 | return 1 78 | fi 79 | 80 | # Transform docs for static hosting 81 | if ! $(xcrun --find docc) process-archive \ 82 | transform-for-static-hosting .build/docbuild/Build/Products/$DEBUG_PATH/$TARGET.doccarchive \ 83 | --output-path .build/docs-$PLATFORM \ 84 | --hosting-base-path "$TARGET"; then 85 | echo "Error: Failed to transform documentation for $PLATFORM" >&2 86 | return 1 87 | fi 88 | 89 | # Inject a root redirect script on the root page 90 | echo "" > .build/docs-$PLATFORM/index.html; 91 | 92 | # Complete successfully 93 | echo "Successfully built $TARGET docs for $PLATFORM" 94 | return 0 95 | } 96 | 97 | # Start script 98 | echo "" 99 | echo "Building $TARGET docs for [$PLATFORMS]..." 100 | echo "" 101 | 102 | # Loop through all platforms and call the build function 103 | for PLATFORM in $PLATFORMS; do 104 | if ! build_platform "$PLATFORM"; then 105 | exit 1 106 | fi 107 | done 108 | 109 | # Complete successfully 110 | echo "" 111 | echo "Building $TARGET docs completed successfully!" 112 | echo "" 113 | -------------------------------------------------------------------------------- /scripts/framework.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC for a and certain . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Important: 9 | # This script doesn't work on packages, only on .xcproj projects that generate a framework. 10 | 11 | # Usage: 12 | # framework.sh [ default:iOS macOS tvOS watchOS xrOS] 13 | # e.g. `bash scripts/framework.sh MyTarget iOS macOS` 14 | 15 | # Exit immediately if a command exits with a non-zero status 16 | set -e 17 | 18 | # Verify that all required arguments are provided 19 | if [ $# -eq 0 ]; then 20 | echo "Error: This script requires exactly one argument" 21 | echo "Usage: $0 " 22 | exit 1 23 | fi 24 | 25 | # Define argument variables 26 | TARGET=$1 27 | 28 | # Remove TARGET from arguments list 29 | shift 30 | 31 | # Define platforms variable 32 | if [ $# -eq 0 ]; then 33 | set -- iOS macOS tvOS watchOS xrOS 34 | fi 35 | PLATFORMS=$@ 36 | 37 | # Define local variables 38 | BUILD_FOLDER=.build 39 | BUILD_FOLDER_ARCHIVES=.build/framework_archives 40 | BUILD_FILE=$BUILD_FOLDER/$TARGET.xcframework 41 | BUILD_ZIP=$BUILD_FOLDER/$TARGET.zip 42 | 43 | # Start script 44 | echo "" 45 | echo "Building $TARGET XCFramework for [$PLATFORMS]..." 46 | echo "" 47 | 48 | # Delete old builds 49 | echo "Cleaning old builds..." 50 | rm -rf $BUILD_ZIP 51 | rm -rf $BUILD_FILE 52 | rm -rf $BUILD_FOLDER_ARCHIVES 53 | 54 | 55 | # Generate XCArchive files for all platforms 56 | echo "Generating XCArchives..." 57 | 58 | # Initialize the xcframework command 59 | XCFRAMEWORK_CMD="xcodebuild -create-xcframework" 60 | 61 | # Build iOS archives and append to the xcframework command 62 | if [[ " ${PLATFORMS[@]} " =~ " iOS " ]]; then 63 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=iOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-iOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 64 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=iOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-iOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 65 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-iOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 66 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-iOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 67 | fi 68 | 69 | # Build iOS archive and append to the xcframework command 70 | if [[ " ${PLATFORMS[@]} " =~ " macOS " ]]; then 71 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=macOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-macOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 72 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-macOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 73 | fi 74 | 75 | # Build tvOS archives and append to the xcframework command 76 | if [[ " ${PLATFORMS[@]} " =~ " tvOS " ]]; then 77 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=tvOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 78 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=tvOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 79 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 80 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 81 | fi 82 | 83 | # Build watchOS archives and append to the xcframework command 84 | if [[ " ${PLATFORMS[@]} " =~ " watchOS " ]]; then 85 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=watchOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 86 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=watchOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 87 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 88 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 89 | fi 90 | 91 | # Build xrOS archives and append to the xcframework command 92 | if [[ " ${PLATFORMS[@]} " =~ " xrOS " ]]; then 93 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=xrOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 94 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=xrOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 95 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 96 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 97 | fi 98 | 99 | # Genererate XCFramework 100 | echo "Generating XCFramework..." 101 | XCFRAMEWORK_CMD+=" -output $BUILD_FILE" 102 | eval "$XCFRAMEWORK_CMD" 103 | 104 | # Genererate iOS XCFramework zip 105 | echo "Generating XCFramework zip..." 106 | zip -r $BUILD_ZIP $BUILD_FILE 107 | echo "" 108 | echo "***** CHECKSUM *****" 109 | swift package compute-checksum $BUILD_ZIP 110 | echo "********************" 111 | echo "" 112 | 113 | # Complete successfully 114 | echo "" 115 | echo "$TARGET XCFramework created successfully!" 116 | echo "" 117 | -------------------------------------------------------------------------------- /scripts/git_default_branch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script echos the default git branch name. 5 | 6 | # Usage: 7 | # git_default_branch.sh 8 | # e.g. `bash scripts/git_default_branch.sh` 9 | 10 | BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@') 11 | echo $BRANCH 12 | -------------------------------------------------------------------------------- /scripts/package_docc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC documentation for `Package.swift`. 5 | # This script targets iOS by default, but you can pass in custom . 6 | 7 | # Usage: 8 | # package_docc.sh [ default:iOS] 9 | # e.g. `bash scripts/package_docc.sh iOS macOS` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 17 | SCRIPT_DOCC="$FOLDER/docc.sh" 18 | 19 | # Define platforms variable 20 | if [ $# -eq 0 ]; then 21 | set -- iOS 22 | fi 23 | PLATFORMS=$@ 24 | 25 | # Get package name 26 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 27 | 28 | # Build package documentation 29 | bash $SCRIPT_DOCC $PACKAGE_NAME $PLATFORMS || { echo "DocC script failed"; exit 1; } 30 | -------------------------------------------------------------------------------- /scripts/package_framework.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script generates an XCFramework for `Package.swift`. 5 | # This script targets iOS by default, but you can pass in custom . 6 | 7 | # Usage: 8 | # package_framework.sh [ default:iOS] 9 | # e.g. `bash scripts/package_framework.sh iOS macOS` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 17 | SCRIPT_FRAMEWORK="$FOLDER/framework.sh" 18 | 19 | # Define platforms variable 20 | if [ $# -eq 0 ]; then 21 | set -- iOS 22 | fi 23 | PLATFORMS=$@ 24 | 25 | # Get package name 26 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 27 | 28 | # Build package framework 29 | bash $SCRIPT_FRAMEWORK $PACKAGE_NAME $PLATFORMS 30 | -------------------------------------------------------------------------------- /scripts/package_name.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script finds the main target name in `Package.swift`. 5 | 6 | # Usage: 7 | # package_name.sh 8 | # e.g. `bash scripts/package_name.sh` 9 | 10 | # Exit immediately if a command exits with non-zero status 11 | set -e 12 | 13 | # Check that a Package.swift file exists 14 | if [ ! -f "Package.swift" ]; then 15 | echo "Error: Package.swift not found in current directory" 16 | exit 1 17 | fi 18 | 19 | # Using grep and sed to extract the package name 20 | # 1. grep finds the line containing "name:" 21 | # 2. sed extracts the text between quotes 22 | package_name=$(grep -m 1 'name:.*"' Package.swift | sed -n 's/.*name:[[:space:]]*"\([^"]*\)".*/\1/p') 23 | 24 | if [ -z "$package_name" ]; then 25 | echo "Error: Could not find package name in Package.swift" 26 | exit 1 27 | else 28 | echo "$package_name" 29 | fi 30 | -------------------------------------------------------------------------------- /scripts/package_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new version for `Package.swift`. 5 | # You can pass in a to validate any non-main branch. 6 | 7 | # Usage: 8 | # package_version.sh 9 | # e.g. `bash scripts/package_version.sh master` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_BRANCH_NAME="$FOLDER/git_default_branch.sh" 17 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 18 | SCRIPT_VERSION="$FOLDER/version.sh" 19 | 20 | # Get branch name 21 | DEFAULT_BRANCH=$("$SCRIPT_BRANCH_NAME") || { echo "Failed to get branch name"; exit 1; } 22 | BRANCH_NAME=${1:-$DEFAULT_BRANCH} 23 | 24 | # Get package name 25 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 26 | 27 | # Build package version 28 | bash $SCRIPT_VERSION $PACKAGE_NAME $BRANCH_NAME 29 | -------------------------------------------------------------------------------- /scripts/sync_from.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script syncs Swift Package Scripts from a . 5 | # This script will overwrite the existing "scripts" folder. 6 | # Only pass in the full path to a Swift Package Scripts root. 7 | 8 | # Usage: 9 | # package_name.sh 10 | # e.g. `bash sync_from.sh ../SwiftPackageScripts` 11 | 12 | # Define argument variables 13 | SOURCE=$1 14 | 15 | # Define variables 16 | FOLDER="scripts/" 17 | SOURCE_FOLDER="$SOURCE/$FOLDER" 18 | 19 | # Start script 20 | echo "" 21 | echo "Syncing scripts from $SOURCE_FOLDER..." 22 | echo "" 23 | 24 | # Remove existing folder 25 | rm -rf $FOLDER 26 | 27 | # Copy folder 28 | cp -r "$SOURCE_FOLDER/" "$FOLDER/" 29 | 30 | # Complete successfully 31 | echo "" 32 | echo "Script syncing from $SOURCE_FOLDER completed successfully!" 33 | echo "" 34 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script tests a for all provided . 5 | 6 | # Usage: 7 | # test.sh [ default:iOS macOS tvOS watchOS xrOS] 8 | # e.g. `bash scripts/test.sh MyTarget iOS macOS` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Verify that all required arguments are provided 14 | if [ $# -eq 0 ]; then 15 | echo "Error: This script requires at least one argument" 16 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 17 | echo "For instance: $0 MyTarget iOS macOS" 18 | exit 1 19 | fi 20 | 21 | # Define argument variables 22 | TARGET=$1 23 | 24 | # Remove TARGET from arguments list 25 | shift 26 | 27 | # Define platforms variable 28 | if [ $# -eq 0 ]; then 29 | set -- iOS macOS tvOS watchOS xrOS 30 | fi 31 | PLATFORMS=$@ 32 | 33 | # Start script 34 | echo "" 35 | echo "Testing $TARGET for [$PLATFORMS]..." 36 | echo "" 37 | 38 | # A function that gets the latest simulator for a certain OS. 39 | get_latest_simulator() { 40 | local PLATFORM=$1 41 | local SIMULATOR_TYPE 42 | 43 | case $PLATFORM in 44 | "iOS") 45 | SIMULATOR_TYPE="iPhone" 46 | ;; 47 | "tvOS") 48 | SIMULATOR_TYPE="Apple TV" 49 | ;; 50 | "watchOS") 51 | SIMULATOR_TYPE="Apple Watch" 52 | ;; 53 | "xrOS") 54 | SIMULATOR_TYPE="Apple Vision" 55 | ;; 56 | *) 57 | echo "Error: Unsupported platform for simulator '$PLATFORM'" 58 | return 1 59 | ;; 60 | esac 61 | 62 | # Get the latest simulator for the platform 63 | xcrun simctl list devices available | grep "$SIMULATOR_TYPE" | tail -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/' 64 | } 65 | 66 | # A function that tests $TARGET for a specific platform 67 | test_platform() { 68 | 69 | # Define a local $PLATFORM variable 70 | local PLATFORM="${1//_/ }" 71 | 72 | # Define the destination, based on the $PLATFORM 73 | case $PLATFORM in 74 | "iOS"|"tvOS"|"watchOS"|"xrOS") 75 | local SIMULATOR_UDID=$(get_latest_simulator "$PLATFORM") 76 | if [ -z "$SIMULATOR_UDID" ]; then 77 | echo "Error: No simulator found for $PLATFORM" 78 | return 1 79 | fi 80 | DESTINATION="id=$SIMULATOR_UDID" 81 | ;; 82 | "macOS") 83 | DESTINATION="platform=macOS" 84 | ;; 85 | *) 86 | echo "Error: Unsupported platform '$PLATFORM'" 87 | return 1 88 | ;; 89 | esac 90 | 91 | # Test $TARGET for the $DESTINATION 92 | echo "Testing $TARGET for $PLATFORM..." 93 | xcodebuild test -scheme $TARGET -derivedDataPath .build -destination "$DESTINATION" -enableCodeCoverage YES 94 | local TEST_RESULT=$? 95 | 96 | if [[ $TEST_RESULT -ne 0 ]]; then 97 | return $TEST_RESULT 98 | fi 99 | 100 | # Complete successfully 101 | echo "Successfully tested $TARGET for $PLATFORM" 102 | return 0 103 | } 104 | 105 | # Loop through all platforms and call the test function 106 | for PLATFORM in $PLATFORMS; do 107 | if ! test_platform "$PLATFORM"; then 108 | exit 1 109 | fi 110 | done 111 | 112 | # Complete successfully 113 | echo "" 114 | echo "Testing $TARGET completed successfully!" 115 | echo "" 116 | -------------------------------------------------------------------------------- /scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new version for the provided and . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # version.sh [ default:iOS macOS tvOS watchOS xrOS]" 10 | # e.g. `scripts/version.sh MyTarget master iOS macOS` 11 | 12 | # This script will: 13 | # * Call version_validate_git.sh to validate the git repo. 14 | # * Call version_validate_target to run tests, swiftlint, etc. 15 | # * Call version_bump.sh if all validation steps above passed. 16 | 17 | # Exit immediately if a command exits with a non-zero status 18 | set -e 19 | 20 | # Verify that all required arguments are provided 21 | if [ $# -lt 2 ]; then 22 | echo "Error: This script requires at least two arguments" 23 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 24 | echo "For instance: $0 MyTarget master iOS macOS" 25 | exit 1 26 | fi 27 | 28 | # Define argument variables 29 | TARGET=$1 30 | BRANCH=${2:-main} 31 | 32 | # Remove TARGET and BRANCH from arguments list 33 | shift 34 | shift 35 | 36 | # Read platform arguments or use default value 37 | if [ $# -eq 0 ]; then 38 | set -- iOS macOS tvOS watchOS xrOS 39 | fi 40 | 41 | # Use the script folder to refer to other scripts. 42 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 43 | SCRIPT_VALIDATE_GIT="$FOLDER/version_validate_git.sh" 44 | SCRIPT_VALIDATE_TARGET="$FOLDER/version_validate_target.sh" 45 | SCRIPT_VERSION_BUMP="$FOLDER/version_bump.sh" 46 | 47 | # A function that run a certain script and checks for errors 48 | run_script() { 49 | local script="$1" 50 | shift # Remove the first argument (the script path) 51 | 52 | if [ ! -f "$script" ]; then 53 | echo "Error: Script not found: $script" 54 | exit 1 55 | fi 56 | 57 | chmod +x "$script" 58 | if ! "$script" "$@"; then 59 | echo "Error: Script $script failed" 60 | exit 1 61 | fi 62 | } 63 | 64 | # Start script 65 | echo "" 66 | echo "Creating a new version for $TARGET on the $BRANCH branch..." 67 | echo "" 68 | 69 | # Validate git and project 70 | echo "Validating..." 71 | run_script "$SCRIPT_VALIDATE_GIT" "$BRANCH" 72 | run_script "$SCRIPT_VALIDATE_TARGET" "$TARGET" 73 | 74 | # Bump version 75 | echo "Bumping version..." 76 | run_script "$SCRIPT_VERSION_BUMP" 77 | 78 | # Complete successfully 79 | echo "" 80 | echo "Version created successfully!" 81 | echo "" 82 | -------------------------------------------------------------------------------- /scripts/version_bump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script bumps the project version number. 5 | # You can append --no-semver to disable semantic version validation. 6 | 7 | # Usage: 8 | # version_bump.sh [--no-semver] 9 | # e.g. `bash scripts/version_bump.sh` 10 | # e.g. `bash scripts/version_bump.sh --no-semver` 11 | 12 | # Exit immediately if a command exits with a non-zero status 13 | set -e 14 | 15 | # Use the script folder to refer to other scripts. 16 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 17 | SCRIPT_VERSION_NUMBER="$FOLDER/version_number.sh" 18 | 19 | 20 | # Parse --no-semver argument 21 | VALIDATE_SEMVER=true 22 | for arg in "$@"; do 23 | case $arg in 24 | --no-semver) 25 | VALIDATE_SEMVER=false 26 | shift # Remove --no-semver from processing 27 | ;; 28 | esac 29 | done 30 | 31 | # Start script 32 | echo "" 33 | echo "Bumping version number..." 34 | echo "" 35 | 36 | # Get the latest version 37 | VERSION=$($SCRIPT_VERSION_NUMBER) 38 | if [ $? -ne 0 ]; then 39 | echo "Failed to get the latest version" 40 | exit 1 41 | fi 42 | 43 | # Print the current version 44 | echo "The current version is: $VERSION" 45 | 46 | # Function to validate semver format, including optional -rc. suffix 47 | validate_semver() { 48 | if [ "$VALIDATE_SEMVER" = false ]; then 49 | return 0 50 | fi 51 | 52 | if [[ $1 =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then 53 | return 0 54 | else 55 | return 1 56 | fi 57 | } 58 | 59 | # Prompt user for new version 60 | while true; do 61 | read -p "Enter the new version number: " NEW_VERSION 62 | 63 | # Validate the version number to ensure that it's a semver version 64 | if validate_semver "$NEW_VERSION"; then 65 | break 66 | else 67 | echo "Invalid version format. Please use semver format (e.g., 1.2.3, v1.2.3, 1.2.3-rc.1, etc.)." 68 | exit 1 69 | fi 70 | done 71 | 72 | # Push the new tag 73 | git push -u origin HEAD 74 | git tag $NEW_VERSION 75 | git push --tags 76 | 77 | # Complete successfully 78 | echo "" 79 | echo "Version tag pushed successfully!" 80 | echo "" 81 | -------------------------------------------------------------------------------- /scripts/version_number.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script returns the latest project version. 5 | 6 | # Usage: 7 | # version_number.sh 8 | # e.g. `bash scripts/version_number.sh` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Check if the current directory is a Git repository 14 | if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then 15 | echo "Error: Not a Git repository" 16 | exit 1 17 | fi 18 | 19 | # Fetch all tags 20 | git fetch --tags > /dev/null 2>&1 21 | 22 | # Get the latest semver tag 23 | latest_version=$(git tag -l --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) 24 | 25 | # Check if we found a version tag 26 | if [ -z "$latest_version" ]; then 27 | echo "Error: No semver tags found in this repository" >&2 28 | exit 1 29 | fi 30 | 31 | # Print the latest version 32 | echo "$latest_version" 33 | -------------------------------------------------------------------------------- /scripts/version_validate_git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script validates the Git repository for release. 5 | # You can pass in a to validate any non-main branch. 6 | 7 | # Usage: 8 | # version_validate_git.sh " 9 | # e.g. `bash scripts/version_validate_git.sh master` 10 | 11 | # This script will: 12 | # * Validate that the script is run within a git repository. 13 | # * Validate that the git repository doesn't have any uncommitted changes. 14 | # * Validate that the current git branch matches the provided one. 15 | 16 | # Exit immediately if a command exits with a non-zero status 17 | set -e 18 | 19 | # Verify that all required arguments are provided 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires exactly one argument" 22 | echo "Usage: $0 " 23 | exit 1 24 | fi 25 | 26 | # Create local argument variables. 27 | BRANCH=$1 28 | 29 | # Start script 30 | echo "" 31 | echo "Validating git repository..." 32 | echo "" 33 | 34 | # Check if the current directory is a Git repository 35 | if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then 36 | echo "Error: Not a Git repository" 37 | exit 1 38 | fi 39 | 40 | # Check for uncommitted changes 41 | if [ -n "$(git status --porcelain)" ]; then 42 | echo "Error: Git repository is dirty. There are uncommitted changes." 43 | exit 1 44 | fi 45 | 46 | # Verify that we're on the correct branch 47 | current_branch=$(git rev-parse --abbrev-ref HEAD) 48 | if [ "$current_branch" != "$BRANCH" ]; then 49 | echo "Error: Not on the specified branch. Current branch is $current_branch, expected $1." 50 | exit 1 51 | fi 52 | 53 | # The Git repository validation succeeded. 54 | echo "" 55 | echo "Git repository validated successfully!" 56 | echo "" 57 | -------------------------------------------------------------------------------- /scripts/version_validate_target.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script validates a for release. 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # version_validate_target.sh [ default:iOS macOS tvOS watchOS xrOS]" 10 | # e.g. `bash scripts/version_validate_target.sh iOS macOS` 11 | 12 | # This script will: 13 | # * Validate that swiftlint passes. 14 | # * Validate that all unit tests passes for all . 15 | 16 | # Exit immediately if a command exits with a non-zero status 17 | set -e 18 | 19 | # Verify that all requires at least one argument" 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires at least one argument" 22 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 23 | exit 1 24 | fi 25 | 26 | # Create local argument variables. 27 | TARGET=$1 28 | 29 | # Remove TARGET from arguments list 30 | shift 31 | 32 | # Define platforms variable 33 | if [ $# -eq 0 ]; then 34 | set -- iOS macOS tvOS watchOS xrOS 35 | fi 36 | PLATFORMS=$@ 37 | 38 | # Use the script folder to refer to other scripts. 39 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 40 | SCRIPT_TEST="$FOLDER/test.sh" 41 | 42 | # A function that run a certain script and checks for errors 43 | run_script() { 44 | local script="$1" 45 | shift # Remove the first argument (script path) from the argument list 46 | 47 | if [ ! -f "$script" ]; then 48 | echo "Error: Script not found: $script" 49 | exit 1 50 | fi 51 | 52 | chmod +x "$script" 53 | if ! "$script" "$@"; then 54 | echo "Error: Script $script failed" 55 | exit 1 56 | fi 57 | } 58 | 59 | # Start script 60 | echo "" 61 | echo "Validating project..." 62 | echo "" 63 | 64 | # Run SwiftLint 65 | echo "Running SwiftLint" 66 | if ! swiftlint --strict; then 67 | echo "Error: SwiftLint failed" 68 | exit 1 69 | fi 70 | 71 | # Run unit tests 72 | echo "Testing..." 73 | run_script "$SCRIPT_TEST" "$TARGET" "$PLATFORMS" 74 | 75 | # Complete successfully 76 | echo "" 77 | echo "Project successfully validated!" 78 | echo "" 79 | --------------------------------------------------------------------------------