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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 | 
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 |
--------------------------------------------------------------------------------