├── codecov.yml ├── Makefile ├── .gitignore ├── scripts ├── gencode └── configure_xcodeproj.rb ├── Sources └── Stubber │ ├── Execution.swift │ ├── Escaping.Arguments.erb │ ├── Stubber.swift │ └── Escaping.Arguments.swift ├── Package.swift ├── Stubber.podspec ├── LICENSE ├── .github └── workflows │ └── ci.yml ├── README.md └── Tests └── StubberTests ├── Stubs.swift └── StubberTests.swift /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Tests/" 3 | 4 | coverage: 5 | status: 6 | project: no 7 | patch: no 8 | changes: no 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | generate: 2 | ./scripts/gencode 3 | 4 | project: generate 5 | swift package generate-xcodeproj 6 | ruby ./scripts/configure_xcodeproj.rb 7 | 8 | test: generate 9 | swift test 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | sourcekitten-output.json 3 | docs/ 4 | /.build 5 | /Packages 6 | /*.xcodeproj 7 | **/xcuserdata 8 | **/xcshareddata 9 | Pods/ 10 | Carthage/ 11 | Examples/**/Podfile.lock 12 | -------------------------------------------------------------------------------- /scripts/gencode: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (c) Yoichi Tagaya 3 | # https://github.com/Swinject/Swinject 4 | 5 | files="Sources/Stubber/Escaping.Arguments" 6 | 7 | for file in $files; do 8 | echo "Generating code to $file.swift" 9 | erb -v -T 1 $file.erb > $file.swift 10 | done 11 | -------------------------------------------------------------------------------- /Sources/Stubber/Execution.swift: -------------------------------------------------------------------------------- 1 | public protocol AnyExecution { 2 | } 3 | 4 | public struct Execution: AnyExecution { 5 | private let _arguments: A 6 | public var arguments: A { 7 | return self._arguments 8 | } 9 | 10 | public let result: R 11 | 12 | init(arguments: A, result: R) { 13 | self._arguments = arguments 14 | self.result = result 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Stubber", 7 | platforms: [ 8 | .macOS(.v10_10), .iOS(.v8), .tvOS(.v9), .watchOS(.v2) 9 | ], 10 | products: [ 11 | .library(name: "Stubber", targets: ["Stubber"]), 12 | ], 13 | targets: [ 14 | .target(name: "Stubber"), 15 | .testTarget(name: "StubberTests", dependencies: ["Stubber"]), 16 | ], 17 | swiftLanguageVersions: [.v5] 18 | ) 19 | -------------------------------------------------------------------------------- /Sources/Stubber/Escaping.Arguments.erb: -------------------------------------------------------------------------------- 1 | <% puts "// WARNING: This swift file is auto generated. Don't modify this file directly.\n\n" %> 2 | <% arguments = 1..20 %> 3 | <% 4 | for arg in arguments 5 | argument_range = 1..arg 6 | 7 | generic_arr = argument_range.map { |i| "Arg#{i}" } 8 | generic_str = generic_arr.join(", ") 9 | 10 | params_arr = argument_range.map { |i| "_ arg#{i}: Arg#{i}" } 11 | params_str = params_arr.join(", ") 12 | 13 | return_arr = argument_range.map { |i| "arg#{i}" } 14 | return_str = return_arr.join(", ") 15 | %> 16 | public func escaping<<%= generic_str %>>(<%= params_str %>) -> (<%= generic_str %>)! { 17 | return (<%= return_str %>) 18 | } 19 | 20 | <% end %> 21 | -------------------------------------------------------------------------------- /Stubber.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Stubber" 3 | s.version = "1.5.3" 4 | s.summary = "A minimal method stub for Swift" 5 | s.homepage = "https://github.com/devxoul/Stubber" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "Suyeol Jeon" => "devxoul@gmail.com" } 8 | s.source = { :git => "https://github.com/devxoul/Stubber.git", 9 | :tag => s.version.to_s } 10 | s.source_files = "Sources/**/*.{swift,h,m}" 11 | s.frameworks = "Foundation" 12 | s.swift_version = "5.0" 13 | 14 | s.ios.deployment_target = "8.0" 15 | s.osx.deployment_target = "10.11" 16 | s.tvos.deployment_target = "9.0" 17 | s.watchos.deployment_target = "2.0" 18 | end 19 | -------------------------------------------------------------------------------- /scripts/configure_xcodeproj.rb: -------------------------------------------------------------------------------- 1 | require "xcodeproj" 2 | 3 | template_files = Dir.glob("./Sources/Stubber/*.erb") 4 | project = Xcodeproj::Project.open("Stubber.xcodeproj") 5 | 6 | # Add template file 7 | source_group = project.groups 8 | .find { |group| group.name == "Sources" }.children 9 | .find { |group| group.name == "Stubber" } 10 | 11 | for file in template_files 12 | filename = file.split("/")[-1] 13 | ref = source_group.new_reference(filename) 14 | end 15 | 16 | # Add run script 17 | target = project.targets.find { |target| target.name == "Stubber" } 18 | # phase = target.new_shell_script_build_phase("Generate Swift Code") 19 | phase = project.new(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) 20 | phase.name = "Generate Swift Code" 21 | phase.shell_script = "./scripts/gencode" 22 | target.build_phases.insert(0, phase) 23 | 24 | project.save 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Suyeol Jeon (xoul.kr) 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 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: macOS-latest 10 | env: 11 | PROJECT: Stubber.xcodeproj 12 | SCHEME: Stubber-Package 13 | CODECOV_PACKAGE_NAME: Stubber 14 | strategy: 15 | matrix: 16 | env: 17 | - sdk: iphonesimulator 18 | destination: platform=iOS Simulator,name=iPhone 11 Pro,OS=latest 19 | 20 | - sdk: macosx 21 | destination: arch=x86_64 22 | 23 | - sdk: appletvsimulator 24 | destination: platform=tvOS Simulator,name=Apple TV,OS=latest 25 | 26 | steps: 27 | - uses: actions/checkout@v1 28 | 29 | - name: List SDKs and Devices 30 | run: xcodebuild -showsdks; instruments -s 31 | 32 | - name: Generate Xcode Project 33 | run: make project 34 | 35 | - name: Build and Test 36 | run: | 37 | set -o pipefail && xcodebuild clean build test \ 38 | -project "$PROJECT" \ 39 | -scheme "$SCHEME" \ 40 | -sdk "$SDK" \ 41 | -destination "$DESTINATION" \ 42 | -configuration Debug \ 43 | -enableCodeCoverage YES \ 44 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c; 45 | env: 46 | SDK: ${{ matrix.env.sdk }} 47 | DESTINATION: ${{ matrix.env.destination }} 48 | 49 | - name: Upload Code Coverage 50 | run: | 51 | bash <(curl -s https://codecov.io/bash) \ 52 | -X xcodeplist \ 53 | -J "$CODECOV_PACKAGE_NAME" 54 | env: 55 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stubber 2 | 3 | ![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg) 4 | [![CocoaPods](http://img.shields.io/cocoapods/v/Stubber.svg)](https://cocoapods.org/pods/Stubber) 5 | [![Build Status](https://travis-ci.org/devxoul/Stubber.svg?branch=master)](https://travis-ci.org/devxoul/Stubber) 6 | [![Codecov](https://img.shields.io/codecov/c/github/devxoul/Stubber.svg)](https://codecov.io/gh/devxoul/Stubber) 7 | 8 | A minimal method stub for Swift. 9 | 10 | ## At a Glance 11 | 12 | ```swift 13 | import Stubber 14 | 15 | final class StubUserService: UserServiceProtocol { 16 | func follow(userID: Int) -> String { 17 | return Stubber.invoke(follow, args: userID) 18 | } 19 | 20 | func edit(userID: Int, name: String) -> Bool { 21 | return Stubber.invoke(edit, args: (userID, name)) 22 | } 23 | } 24 | 25 | func testMethodCall() { 26 |  // given 27 | let userService = StubUserService() 28 | Stubber.register(userService.follow) { userID in "stub-\(userID)" } // stub 29 | 30 | // when 31 | userService.follow(userID: 123) // call 32 | 33 | // then 34 | XCTAssertEqual(Stubber.executions(userService.follow).count, 1) 35 | XCTAssertEqual(Stubber.executions(userService.follow)[0].arguments, 123) 36 | XCTAssertEqual(Stubber.executions(userService.follow)[0].result, "stub-123") 37 | } 38 | ``` 39 | 40 | ## Escaping Parameters 41 | 42 | When a function contains an escaped parameter, use `escaping()` on arguments. 43 | 44 | ```diff 45 | func request(path: String, completion: @escaping (Result) -> Void) { 46 | - Stubber.invoke(request, args: (path, completion)) 47 | + Stubber.invoke(request, args: escaping(path, completion)) 48 | } 49 | ``` 50 | 51 | ## Installation 52 | 53 | ```ruby 54 | pod 'Stubber' 55 | ``` 56 | 57 | ## License 58 | 59 | Stubber is under MIT license. See the [LICENSE](LICENSE) for more info. 60 | -------------------------------------------------------------------------------- /Tests/StubberTests/Stubs.swift: -------------------------------------------------------------------------------- 1 | import Stubber 2 | import Foundation 3 | 4 | final class StubClass { 5 | func argument0_returnVoid_defaultNo() { 6 | return Stubber.invoke(argument0_returnVoid_defaultNo, args: ()) 7 | } 8 | func argument0_returnVoid_defaultVoid() { 9 | return Stubber.invoke(argument0_returnVoid_defaultVoid, args: (), default: Void()) 10 | } 11 | 12 | func argument1_returnInt_defaultNo(_ value: String) -> Int { 13 | return Stubber.invoke(argument1_returnInt_defaultNo, args: value) 14 | } 15 | func argument1_returnInt_defaultInt(_ value: String) -> Int { 16 | return Stubber.invoke(argument1_returnInt_defaultInt, args: value, default: 0) 17 | } 18 | 19 | func argument1_parameterOptional_returnBool_defaultNo(_ value: String?) -> Bool { 20 | return Stubber.invoke(argument1_parameterOptional_returnBool_defaultNo, args: value) 21 | } 22 | func argument1_parameterOptional_returnBool_defaultBool(_ value: String?) -> Bool { 23 | return Stubber.invoke(argument1_parameterOptional_returnBool_defaultBool, args: value, default: false) 24 | } 25 | 26 | func argument2_parameterOptional_returnBool_defaultNo(_ value1: String?, _ value2: String) -> Bool { 27 | return Stubber.invoke(argument2_parameterOptional_returnBool_defaultNo, args: (value1, value2)) 28 | } 29 | func argument2_parameterOptional_returnBool_defaultBool(_ value1: String?, _ value2: String) -> Bool { 30 | return Stubber.invoke(argument2_parameterOptional_returnBool_defaultBool, args: (value1, value2), default: false) 31 | } 32 | 33 | func argument3_returnOptionalString_defaultNo(_ value1: String, _ value2: Int, _ value3: Float) -> String? { 34 | return Stubber.invoke(argument3_returnOptionalString_defaultNo, args: (value1, value2, value3)) 35 | } 36 | func argument3_returnOptionalString_defaultString(_ value1: String, _ value2: Int, _ value3: Float) -> String? { 37 | return Stubber.invoke(argument3_returnOptionalString_defaultString, args: (value1, value2, value3), default: "default") 38 | } 39 | 40 | func argument0_returnOptionalInt_defaultNil() -> Int? { 41 | return Stubber.invoke(argument0_returnOptionalInt_defaultNil, args: (), default: nil) 42 | } 43 | 44 | func argument2_returnString_defaultNo_throws(_ value1: String, _ value2: Int) throws -> String { 45 | return try Stubber.invoke(argument2_returnString_defaultNo_throws, args: (value1, value2)) 46 | } 47 | 48 | func argument_escapeClosure(_ value1: String, _ value2: @escaping (Bool) -> String) -> Void { 49 | return Stubber.invoke(argument_escapeClosure, args: escaping(value1, value2), default: Void()) 50 | } 51 | 52 | func argument_1_generic(_ value: T) -> T { 53 | return Stubber.invoke(argument_1_generic, args: value, default: value) 54 | } 55 | 56 | func sleep() { 57 | Stubber.invoke(self.sleep, args: (), default: { Foundation.sleep(1) }()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Stubber/Stubber.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private typealias FunctionAddress = Int 4 | private typealias Function = Any 5 | 6 | private final class Store { 7 | var stubs: [FunctionAddress: Function] = [:] 8 | var executions: [FunctionAddress: [AnyExecution]] = [:] 9 | } 10 | 11 | private let store = Store() 12 | private let lock = NSLock() 13 | 14 | 15 | // MARK: Stub 16 | 17 | public func register(_ f: @escaping (A) throws -> R, with closure: @escaping (A) -> R) { 18 | let address = functionAddress(of: f) 19 | lock.lock() 20 | store.stubs[address] = closure 21 | store.executions[address]?.removeAll() 22 | lock.unlock() 23 | } 24 | 25 | @available(*, deprecated, renamed: "register(_:with:)") 26 | public func stub(_ f: @escaping (A) throws -> R, with closure: @escaping (A) -> R) { 27 | register(f, with: closure) 28 | } 29 | 30 | 31 | // MARK: Stubbed 32 | 33 | public func invoke(_ f: @escaping (A) throws -> R, args: A, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) rethrows -> R { 34 | let address = functionAddress(of: f) 35 | let closure = store.stubs[address] as? (A) -> R 36 | guard let result = closure?(args) else { 37 | preconditionFailure("⚠️ '\(function)' is not stubbed.", file: file, line: line) 38 | } 39 | 40 | lock.lock() 41 | store.executions[address] = (store.executions[address] ?? []) + [Execution(arguments: args, result: result)] 42 | lock.unlock() 43 | 44 | return result 45 | } 46 | 47 | public func invoke(_ f: @escaping (A) throws -> R, args: A, default: @autoclosure () -> R, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) rethrows -> R { 48 | let address = functionAddress(of: f) 49 | let closure = store.stubs[address] as? (A) -> R 50 | let result = closure?(args) ?? `default`() 51 | 52 | lock.lock() 53 | store.executions[address] = (store.executions[address] ?? []) + [Execution(arguments: args, result: result)] 54 | lock.unlock() 55 | 56 | return result 57 | } 58 | 59 | @available(*, deprecated, renamed: "invoke(_:args:)") 60 | public func stubbed(_ f: @escaping (A) throws -> R, args: A, default: @autoclosure () -> R = (nil as R?)!, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) rethrows -> R { 61 | return try invoke(f, args: args) 62 | } 63 | 64 | 65 | // MARK: Escaping support 66 | 67 | public func invoke(_ f: @escaping (A) throws -> R, args: A!, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) rethrows -> R { 68 | return try invoke(f, args: args as A, file: file, line: line, function: function) 69 | } 70 | 71 | public func invoke(_ f: @escaping (A) throws -> R, args: A!, default: @autoclosure () -> R, file: StaticString = #file, line: UInt = #line, function: StaticString = #function) rethrows -> R { 72 | return try invoke(f, args: args as A, default: `default`(), file: file, line: line, function: function) 73 | } 74 | 75 | 76 | // MARK: Executions 77 | 78 | public func executions(_ f: @escaping (A) throws -> R) -> [Execution] { 79 | return _executions(f) 80 | } 81 | 82 | private func _executions(_ f: @escaping (A) throws -> R) -> [Execution] { 83 | let address = functionAddress(of: f) 84 | return (store.executions[address] as? [Execution]) ?? [] 85 | } 86 | 87 | 88 | // MARK: Clear 89 | 90 | public func clear() { 91 | lock.lock() 92 | store.stubs.removeAll() 93 | store.executions.removeAll() 94 | lock.unlock() 95 | } 96 | 97 | 98 | // MARK: Utils 99 | 100 | private func functionAddress(of f: @escaping (A) throws -> R) -> Int { 101 | let (_, lo) = unsafeBitCast(f, to: (Int, Int).self) 102 | let offset = MemoryLayout.size == 8 ? 16 : 12 103 | let pointer = UnsafePointer(bitPattern: lo + offset)! 104 | return pointer.pointee 105 | } 106 | -------------------------------------------------------------------------------- /Sources/Stubber/Escaping.Arguments.swift: -------------------------------------------------------------------------------- 1 | // WARNING: This swift file is auto generated. Don't modify this file directly. 2 | 3 | public func escaping(_ arg1: Arg1) -> (Arg1)! { 4 | return (arg1) 5 | } 6 | 7 | public func escaping(_ arg1: Arg1, _ arg2: Arg2) -> (Arg1, Arg2)! { 8 | return (arg1, arg2) 9 | } 10 | 11 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> (Arg1, Arg2, Arg3)! { 12 | return (arg1, arg2, arg3) 13 | } 14 | 15 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> (Arg1, Arg2, Arg3, Arg4)! { 16 | return (arg1, arg2, arg3, arg4) 17 | } 18 | 19 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> (Arg1, Arg2, Arg3, Arg4, Arg5)! { 20 | return (arg1, arg2, arg3, arg4, arg5) 21 | } 22 | 23 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6)! { 24 | return (arg1, arg2, arg3, arg4, arg5, arg6) 25 | } 26 | 27 | public func escaping(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) -> (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7)! { 28 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7) 29 | } 30 | 31 | 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)! { 32 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) 33 | } 34 | 35 | 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)! { 36 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) 37 | } 38 | 39 | 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)! { 40 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) 41 | } 42 | 43 | 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)! { 44 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) 45 | } 46 | 47 | 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)! { 48 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) 49 | } 50 | 51 | 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)! { 52 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13) 53 | } 54 | 55 | 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)! { 56 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) 57 | } 58 | 59 | 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)! { 60 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) 61 | } 62 | 63 | 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)! { 64 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) 65 | } 66 | 67 | 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)! { 68 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17) 69 | } 70 | 71 | 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)! { 72 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18) 73 | } 74 | 75 | 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)! { 76 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19) 77 | } 78 | 79 | 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)! { 80 | return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20) 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Tests/StubberTests/StubberTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Stubber 3 | 4 | class StubberTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | Stubber.clear() 8 | } 9 | 10 | func test_argument0_defaultNo_returnVoid() { 11 | // given 12 | let f = StubClass().argument0_returnVoid_defaultNo 13 | Stubber.register(f) { _ in } 14 | 15 | // when 16 | f() 17 | 18 | // then 19 | XCTAssertEqual(Stubber.executions(f).count, 1) 20 | } 21 | 22 | func test_argument0_defaultVoid_returnVoid() { 23 | // given 24 | let f = StubClass().argument0_returnVoid_defaultVoid 25 | 26 | // when 27 | f() 28 | f() 29 | 30 | // then 31 | XCTAssertEqual(Stubber.executions(f).count, 2) 32 | } 33 | 34 | func test_argument1_defaultNo_returnInt() { 35 | // given 36 | let f = StubClass().argument1_returnInt_defaultNo 37 | Stubber.register(f) { _ in 10 } 38 | 39 | // when 40 | let result = f("Hello") 41 | 42 | // then 43 | XCTAssertEqual(Stubber.executions(f).count, 1) 44 | XCTAssertEqual(Stubber.executions(f)[0].arguments, "Hello") 45 | XCTAssertEqual(Stubber.executions(f)[0].result, 10) 46 | XCTAssertEqual(result, 10) 47 | } 48 | 49 | func test_argument1_defaultInt_returnInt() { 50 | // given 51 | let f = StubClass().argument1_returnInt_defaultInt 52 | 53 | // when 54 | let result = f("Hello, default!") 55 | 56 | // then 57 | XCTAssertEqual(Stubber.executions(f).count, 1) 58 | XCTAssertEqual(Stubber.executions(f)[0].arguments, "Hello, default!") 59 | XCTAssertEqual(Stubber.executions(f)[0].result, 0) 60 | XCTAssertEqual(result, 0) 61 | } 62 | 63 | func test_argument1_parameterOptional_returnBool_defaultNo() { 64 | // given 65 | let f = StubClass().argument1_parameterOptional_returnBool_defaultNo 66 | Stubber.register(f) { key in 67 | return true 68 | } 69 | 70 | // when 71 | let result = f("String Parameter") 72 | 73 | // then 74 | XCTAssertEqual(Stubber.executions(f).count, 1) 75 | XCTAssertEqual(Stubber.executions(f)[0].arguments, "String Parameter") 76 | XCTAssertEqual(Stubber.executions(f)[0].result, true) 77 | XCTAssertEqual(result, true) 78 | } 79 | 80 | func test_argument1_parameterOptional_returnBool_defaultBool() { 81 | // given 82 | let f = StubClass().argument1_parameterOptional_returnBool_defaultBool 83 | 84 | // when 85 | let result = f("String Value") 86 | 87 | // then 88 | XCTAssertEqual(Stubber.executions(f).count, 1) 89 | XCTAssertEqual(Stubber.executions(f)[0].arguments, "String Value") 90 | XCTAssertEqual(Stubber.executions(f)[0].result, false) 91 | XCTAssertEqual(result, false) 92 | } 93 | 94 | func test_argument2_parameterOptional_returnBool_defaultNo() { 95 | // given 96 | let f = StubClass().argument2_parameterOptional_returnBool_defaultNo 97 | Stubber.register(f) { key in 98 | return true 99 | } 100 | 101 | // when 102 | let result = f("String1", "String2") 103 | 104 | // then 105 | XCTAssertEqual(Stubber.executions(f).count, 1) 106 | XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "String1") 107 | XCTAssertEqual(Stubber.executions(f)[0].arguments.1, "String2") 108 | XCTAssertEqual(Stubber.executions(f)[0].result, true) 109 | XCTAssertEqual(result, true) 110 | } 111 | 112 | func test_argument2_parameterOptional_returnBool_defaultBool() { 113 | // given 114 | let f = StubClass().argument2_parameterOptional_returnBool_defaultBool 115 | 116 | // when 117 | let result = f("String Value1", "String Value2") 118 | 119 | // then 120 | XCTAssertEqual(Stubber.executions(f).count, 1) 121 | XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "String Value1") 122 | XCTAssertEqual(Stubber.executions(f)[0].arguments.1, "String Value2") 123 | XCTAssertEqual(Stubber.executions(f)[0].result, false) 124 | XCTAssertEqual(result, false) 125 | } 126 | 127 | func test_argument3_returnOptionalString_defaultNo() { 128 | // given 129 | let f = StubClass().argument3_returnOptionalString_defaultNo 130 | Stubber.register(f) { _ in "Test" } 131 | 132 | // when 133 | let result = f("Hello", 10, 20) 134 | 135 | // then 136 | XCTAssertEqual(Stubber.executions(f).count, 1) 137 | XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "Hello") 138 | XCTAssertEqual(Stubber.executions(f)[0].arguments.1, 10) 139 | XCTAssertEqual(Stubber.executions(f)[0].arguments.2, 20) 140 | XCTAssertEqual(Stubber.executions(f)[0].result, "Test") 141 | XCTAssertEqual(result, "Test") 142 | } 143 | 144 | func test_argument3_returnOptionalString_defaultString() { 145 | // given 146 | let f = StubClass().argument3_returnOptionalString_defaultString 147 | 148 | // when 149 | let result = f("Hello", 10, 20) 150 | 151 | // then 152 | XCTAssertEqual(Stubber.executions(f).count, 1) 153 | XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "Hello") 154 | XCTAssertEqual(Stubber.executions(f)[0].arguments.1, 10) 155 | XCTAssertEqual(Stubber.executions(f)[0].arguments.2, 20) 156 | XCTAssertEqual(Stubber.executions(f)[0].result, "default") 157 | XCTAssertEqual(result, "default") 158 | } 159 | 160 | func test_argument0_returnOptionalInt_defaultNil() { 161 | // given 162 | let f = StubClass().argument0_returnOptionalInt_defaultNil 163 | 164 | // when 165 | let result = f() 166 | 167 | XCTAssertEqual(Stubber.executions(f).count, 1) 168 | XCTAssertNil(Stubber.executions(f)[0].result) 169 | XCTAssertNil(result) 170 | } 171 | 172 | func test_argument2_returnString_defaultNo_throws() { 173 | // given 174 | let f = StubClass().argument2_returnString_defaultNo_throws 175 | Stubber.register(f) { _ in "Stubbed" } 176 | 177 | // when 178 | let result = try? f("Hello", 30) 179 | 180 | // then 181 | XCTAssertEqual(Stubber.executions(f).count, 1) 182 | XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "Hello") 183 | XCTAssertEqual(Stubber.executions(f)[0].arguments.1, 30) 184 | XCTAssertEqual(Stubber.executions(f)[0].result, "Stubbed") 185 | XCTAssertEqual(result, "Stubbed") 186 | } 187 | 188 | /// ⚠️ Generic is not supported yet. 189 | func _test_argument_1_generic() { 190 | // given 191 | let f: (String) -> String = StubClass().argument_1_generic 192 | Stubber.register(f) { _ in "Stubbed" } 193 | 194 | // when 195 | let result = f("Hi") 196 | 197 | // then 198 | XCTAssertEqual(Stubber.executions(f).count, 1) 199 | XCTAssertEqual(Stubber.executions(f)[0].arguments, "Hello") 200 | XCTAssertEqual(Stubber.executions(f)[0].result, "Stubbed") 201 | XCTAssertEqual(result, "Stubbed") 202 | } 203 | 204 | func testThreadSafety() { 205 | let f = StubClass().sleep 206 | for _ in 0..<10 { 207 | DispatchQueue.global().async(execute: f) 208 | } 209 | XCTWaiter().wait(for: [XCTestExpectation()], timeout: 1) 210 | } 211 | 212 | func testClosureCrash() { 213 | // given 214 | final class ImageManager { 215 | func requestImage(for asset: AnyObject, resultHandler: (() -> Void)? = nil) { 216 | return Stubber.invoke(requestImage, args: (asset, resultHandler), default: Void()) 217 | } 218 | } 219 | 220 | let imageManager = ImageManager() 221 | imageManager.requestImage(for: NSObject()) 222 | 223 | let executions = Stubber.executions(imageManager.requestImage) 224 | 225 | // crash 226 | _ = executions.first?.arguments.0 227 | Stubber.clear() 228 | } 229 | 230 | func testMatcherArgumentNil() { 231 | // given 232 | final class Foo { 233 | func doSomething(with arg: String?) { 234 | Stubber.invoke(doSomething(with:), args: arg, default: Void()) 235 | } 236 | } 237 | 238 | func expect(_ value: T, toHaveCount count: Int, file: StaticString = #file, line: UInt = #line, where: @escaping (T.Iterator.Element) -> Bool) where T: Collection { 239 | XCTAssertEqual(value.filter(`where`).count, count, file: file, line: line) 240 | } 241 | 242 | let foo = Foo() 243 | foo.doSomething(with: nil) 244 | 245 | let executions = Stubber.executions(foo.doSomething(with:)) 246 | expect(executions, toHaveCount: 1) { 247 | $0.arguments == nil 248 | } 249 | } 250 | } 251 | --------------------------------------------------------------------------------