diff --git a/Mockingbird.xcodeproj/project.pbxproj b/Mockingbird.xcodeproj/project.pbxproj index e230b5c7..6847571a 100644 --- a/Mockingbird.xcodeproj/project.pbxproj +++ b/Mockingbird.xcodeproj/project.pbxproj @@ -120,6 +120,8 @@ 28843B2726AE710400AFB8DF /* MKBTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 28843B2526AE710400AFB8DF /* MKBTestUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28843B2826AE710400AFB8DF /* MKBTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 28843B2626AE710400AFB8DF /* MKBTestUtils.m */; }; 28843B2A26AE77E800AFB8DF /* InlinePropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28843B2926AE77E800AFB8DF /* InlinePropertyTests.swift */; }; + 288678E6279B9C25004FFB3D /* ObjCProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288678E5279B9C25004FFB3D /* ObjCProtocol.swift */; }; + 288678E8279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288678E7279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift */; }; 28874F8B26BF12DD00097529 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 28874F8A26BF12DD00097529 /* ArgumentParser */; }; 28874F8D26BF7AB000097529 /* Configure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28874F8C26BF7AB000097529 /* Configure.swift */; }; 28874F8F26BF7C3C00097529 /* XcodeProjPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28874F8E26BF7C3C00097529 /* XcodeProjPath.swift */; }; @@ -645,6 +647,8 @@ 28843B2526AE710400AFB8DF /* MKBTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKBTestUtils.h; sourceTree = ""; }; 28843B2626AE710400AFB8DF /* MKBTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MKBTestUtils.m; sourceTree = ""; }; 28843B2926AE77E800AFB8DF /* InlinePropertyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinePropertyTests.swift; sourceTree = ""; }; + 288678E5279B9C25004FFB3D /* ObjCProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjCProtocol.swift; sourceTree = ""; }; + 288678E7279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectiveCProtocolTests.swift; sourceTree = ""; }; 28874F8C26BF7AB000097529 /* Configure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configure.swift; sourceTree = ""; }; 28874F8E26BF7C3C00097529 /* XcodeProjPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeProjPath.swift; sourceTree = ""; }; 28874F9026BF7FA400097529 /* ValidatableArgument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatableArgument.swift; sourceTree = ""; }; @@ -1562,6 +1566,7 @@ 28A1F3C326ADD57C002F282D /* MinimalTestTypes.swift */, OBJ_196 /* ModuleImportCases.swift */, 28D08CCF277477F700AE7C39 /* ObjCParameters.swift */, + 288678E5279B9C25004FFB3D /* ObjCProtocol.swift */, OBJ_198 /* OpaquelyInheritedTypes.swift */, OBJ_199 /* Optionals.swift */, OBJ_200 /* OverloadedMethods.swift */, @@ -1745,6 +1750,7 @@ OBJ_261 /* LastSetValueStubTests.swift */, 28719AF426B23AB200C38C2C /* ObjectiveCTests.swift */, 28D08CD1277479B600AE7C39 /* ObjectiveCParameterTests.swift */, + 288678E7279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift */, 28D08CCD2774247C00AE7C39 /* OptionalsTests.swift */, OBJ_262 /* OrderedVerificationTests.swift */, OBJ_263 /* OverloadedMethodTests.swift */, @@ -2555,6 +2561,7 @@ OBJ_1066 /* InitializerTests.swift in Sources */, OBJ_1067 /* LastSetValueStubTests.swift in Sources */, OBJ_1068 /* OrderedVerificationTests.swift in Sources */, + 288678E8279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift in Sources */, OBJ_1069 /* OverloadedMethodTests.swift in Sources */, OBJ_1070 /* SequentialValueStubbingTests.swift in Sources */, OBJ_1071 /* StubbingTests.swift in Sources */, @@ -2597,6 +2604,7 @@ OBJ_1136 /* Extensions.swift in Sources */, OBJ_1137 /* ExternalModuleClassScopedTypes.swift in Sources */, OBJ_1138 /* ExternalModuleImplicitlyImportedTypes.swift in Sources */, + 288678E6279B9C25004FFB3D /* ObjCProtocol.swift in Sources */, OBJ_1139 /* ExternalModuleTypealiasing.swift in Sources */, 5E45ADF7279DB9A9004AB972 /* AsyncMethods.swift in Sources */, OBJ_1140 /* ExternalModuleTypes.swift in Sources */, diff --git a/Mockingbird.xcodeproj/xcconfigs/MockingbirdFramework.xcconfig b/Mockingbird.xcodeproj/xcconfigs/MockingbirdFramework.xcconfig index c6940d30..edfc9bab 100644 --- a/Mockingbird.xcodeproj/xcconfigs/MockingbirdFramework.xcconfig +++ b/Mockingbird.xcodeproj/xcconfigs/MockingbirdFramework.xcconfig @@ -9,6 +9,8 @@ TARGET_NAME = Mockingbird // Build Flags BUILD_LIBRARY_FOR_DISTRIBUTION = YES +GCC_ENABLE_OBJC_EXCEPTIONS = YES +GCC_WARN_SHADOW = YES // Linking OTHER_LDFLAGS = $(inherited) -framework XCTest diff --git a/Mockingbird.xcodeproj/xcconfigs/MockingbirdGenerator.xcconfig b/Mockingbird.xcodeproj/xcconfigs/MockingbirdGenerator.xcconfig index 60f8f85e..afdc0336 100644 --- a/Mockingbird.xcodeproj/xcconfigs/MockingbirdGenerator.xcconfig +++ b/Mockingbird.xcodeproj/xcconfigs/MockingbirdGenerator.xcconfig @@ -1,7 +1,11 @@ #include "FrameworkBase.xcconfig" +// Identifiers INFOPLIST_FILE = $(SRCROOT)/Sources/MockingbirdGenerator/Info.plist SUPPORTED_PLATFORMS = macosx PRODUCT_MODULE_NAME = MockingbirdGenerator PRODUCT_NAME = MockingbirdGenerator TARGET_NAME = MockingbirdGenerator + +// Compatibility +MACOSX_DEPLOYMENT_TARGET = 10.15 diff --git a/Sources/MockingbirdFramework/Mocking/Mocking.swift b/Sources/MockingbirdFramework/Mocking/Mocking.swift index 6da6846e..f951e831 100644 --- a/Sources/MockingbirdFramework/Mocking/Mocking.swift +++ b/Sources/MockingbirdFramework/Mocking/Mocking.swift @@ -46,6 +46,7 @@ public func mock(_ type: T.Type) -> T { /// ``` /// /// - Parameter type: The type to mock. +@_disfavoredOverload public func mock(_ type: T.Type) -> T { return MKBTypeFacade.create(from: MKBMock(type)) } @@ -121,6 +122,7 @@ public func reset(_ staticMocks: Mock.Type...) { /// ``` /// /// - Parameter mocks: A set of Objective-C mocks to reset. +@_disfavoredOverload public func reset(_ mocks: NSObjectProtocol...) { mocks.forEach({ mock in clearInvocations(on: mock) @@ -183,6 +185,7 @@ public func clearInvocations(on mocks: Mock.Type...) { /// ``` /// /// - Parameter mocks: A set of Objective-C mocks to reset. +@_disfavoredOverload public func clearInvocations(on mocks: NSObjectProtocol...) { mocks.forEach({ mock in guard let context = mock.mockingbirdContext else { return } @@ -239,6 +242,7 @@ public func clearStubs(on mocks: Mock.Type...) { /// ``` /// /// - Parameter mocks: A set of mocks to reset. +@_disfavoredOverload public func clearStubs(on mocks: NSObjectProtocol...) { mocks.forEach({ mock in guard let context = mock.mockingbirdContext else { return } diff --git a/Sources/MockingbirdFramework/Objective-C/MKBConcreteMock.m b/Sources/MockingbirdFramework/Objective-C/MKBConcreteMock.m index 5ed92fa6..edcb0e18 100644 --- a/Sources/MockingbirdFramework/Objective-C/MKBConcreteMock.m +++ b/Sources/MockingbirdFramework/Objective-C/MKBConcreteMock.m @@ -72,8 +72,8 @@ - (void)forwardInvocation:(NSInvocation *)invocation case MKBInvocationRecorderModeNone: { id _Nullable returnValue = [self.mockingbirdContext.mocking - objcDidInvoke:objcInvocation evaluating:^id _Nullable(MKBObjCInvocation *invocation) { - return [self.mockingbirdContext.stubbing evaluateReturnValueFor:invocation]; + objcDidInvoke:objcInvocation evaluating:^id _Nullable(MKBObjCInvocation *invo) { + return [self.mockingbirdContext.stubbing evaluateReturnValueFor:invo]; }]; if (returnValue == [MKBStubbingContext noImplementation]) { diff --git a/Sources/MockingbirdFramework/Stubbing/InvocationForwarding.swift b/Sources/MockingbirdFramework/Stubbing/InvocationForwarding.swift index 05f73ec4..573355e7 100644 --- a/Sources/MockingbirdFramework/Stubbing/InvocationForwarding.swift +++ b/Sources/MockingbirdFramework/Stubbing/InvocationForwarding.swift @@ -337,6 +337,7 @@ public extension NSObjectProtocol { /// ``` /// /// - Returns: A partial mock using the superclass to handle invocations. + @_disfavoredOverload @discardableResult func forwardCallsToSuper() -> Self { mockingbirdContext?.proxy.addTarget(.super) @@ -388,6 +389,7 @@ public extension NSObjectProtocol { /// /// - Parameter object: An object that should handle forwarded invocations. /// - Returns: A partial mock using `object` to handle invocations. + @_disfavoredOverload @discardableResult func forwardCalls(to target: T) -> Self { mockingbirdContext?.proxy.addTarget(.object(target)) diff --git a/Sources/MockingbirdGenerator/Generator/Templates/MethodTemplate.swift b/Sources/MockingbirdGenerator/Generator/Templates/MethodTemplate.swift index 1152cec6..173fa9ec 100644 --- a/Sources/MockingbirdGenerator/Generator/Templates/MethodTemplate.swift +++ b/Sources/MockingbirdGenerator/Generator/Templates/MethodTemplate.swift @@ -60,8 +60,11 @@ class MethodTemplate: Template { isAsync: method.isAsync, isThrowing: method.isThrowing, isStatic: method.kind.typeScope.isStatic, + isOptional: method.attributes.contains(.optional), callMember: { scope in - let scopedName = "\(scope).\(backticked: self.method.shortName)" + let optionalPostfix = self.method.attributes.contains(.optional) ? "?" : "" + let scopedName = + "\(scope).\(backticked: self.method.shortName)\(optionalPostfix)" guard self.method.isVariadic else { return FunctionCallTemplate( name: scopedName, diff --git a/Sources/MockingbirdGenerator/Generator/Templates/SubscriptMethodTemplate.swift b/Sources/MockingbirdGenerator/Generator/Templates/SubscriptMethodTemplate.swift index 2aa0ad4e..8800c298 100644 --- a/Sources/MockingbirdGenerator/Generator/Templates/SubscriptMethodTemplate.swift +++ b/Sources/MockingbirdGenerator/Generator/Templates/SubscriptMethodTemplate.swift @@ -46,6 +46,7 @@ class SubscriptMethodTemplate: MethodTemplate { isAsync: method.isAsync, isThrowing: method.isThrowing, isStatic: method.kind.typeScope.isStatic, + isOptional: method.attributes.contains(.optional), callMember: { scope in return "\(scope)[\(separated: callArguments)]" }, @@ -71,7 +72,13 @@ class SubscriptMethodTemplate: MethodTemplate { isAsync: method.isAsync, isThrowing: method.isThrowing, isStatic: method.kind.typeScope.isStatic, + isOptional: method.attributes.contains(.optional), callMember: { scope in + guard !self.method.attributes.contains(.optional) else { + // Optional readwrite subscripts cannot be assigned to: + // Cannot assign through subscript: 'object' is immutable + return "" + } return "\(scope)[\(separated: callArguments)] = newValue" }, invocationArguments: setterInvocationArguments).render()) diff --git a/Sources/MockingbirdGenerator/Generator/Templates/ThunkTemplate.swift b/Sources/MockingbirdGenerator/Generator/Templates/ThunkTemplate.swift index 92540136..ee5321c8 100644 --- a/Sources/MockingbirdGenerator/Generator/Templates/ThunkTemplate.swift +++ b/Sources/MockingbirdGenerator/Generator/Templates/ThunkTemplate.swift @@ -10,6 +10,7 @@ class ThunkTemplate: Template { let isAsync: Bool let isThrowing: Bool let isStatic: Bool + let isOptional: Bool let callMember: (_ scope: Scope) -> String let invocationArguments: [(argumentLabel: String?, parameterName: String)] @@ -33,6 +34,7 @@ class ThunkTemplate: Template { isAsync: Bool, isThrowing: Bool, isStatic: Bool, + isOptional: Bool, callMember: @escaping (_ scope: Scope) -> String, invocationArguments: [(argumentLabel: String?, parameterName: String)]) { self.mockableType = mockableType @@ -44,6 +46,7 @@ class ThunkTemplate: Template { self.isAsync = isAsync self.isThrowing = isThrowing self.isStatic = isStatic + self.isOptional = isOptional self.callMember = callMember self.invocationArguments = invocationArguments } @@ -100,6 +103,11 @@ class ThunkTemplate: Template { ])) as \(returnType) """).render() }() + let callProxyObject: String = { + let objectInvocation = callMember(.object) + guard !objectInvocation.isEmpty else { return "" } + return "let mkbValue: \(returnType) = \(objectInvocation)" + }() let supertype = isStatic ? "MockingbirdSupertype.Type" : "MockingbirdSupertype" let didInvoke = FunctionCallTemplate(name: "self.mockingbirdContext.mocking.didInvoke", @@ -131,15 +139,17 @@ class ThunkTemplate: Template { controlExpression: "mkbTargetBox.target", cases: [ (".super", isSubclass ? "break" : "return \(callMember(.super))"), - (".object" + (isProxyable ? "(let mkbObject)" : ""), !isProxyable ? "break" : """ - \(GuardStatementTemplate( - condition: "var mkbObject = mkbObject as? \(supertype)", body: "break")) - let mkbValue: \(returnType) = \(callMember(.object)) - \(FunctionCallTemplate( + (".object" + (isProxyable ? "(let mkbObject)" : ""), !isProxyable ? "break" : + String(lines: [ + GuardStatementTemplate( + condition: "var mkbObject = mkbObject as? \(supertype)", body: "break").render(), + !isOptional || callProxyObject.isEmpty ? callProxyObject : + GuardStatementTemplate(condition: callProxyObject, body: "break").render(), + FunctionCallTemplate( name: "self.mockingbirdContext.proxy.updateTarget", - arguments: [(nil, "&mkbObject"), ("in", "mkbTargetBox")])) - return mkbValue - """) + arguments: [(nil, "&mkbObject"), ("in", "mkbTargetBox")]).render(), + callProxyObject.isEmpty ? "" : "return mkbValue", + ])), ]).render()).render(), ])) \(IfStatementTemplate( diff --git a/Sources/MockingbirdGenerator/Generator/Templates/VariableTemplate.swift b/Sources/MockingbirdGenerator/Generator/Templates/VariableTemplate.swift index 7a2cc5d1..1988c3e8 100644 --- a/Sources/MockingbirdGenerator/Generator/Templates/VariableTemplate.swift +++ b/Sources/MockingbirdGenerator/Generator/Templates/VariableTemplate.swift @@ -74,6 +74,7 @@ class VariableTemplate: Template { isAsync: false, isThrowing: false, isStatic: variable.kind.typeScope.isStatic, + isOptional: variable.attributes.contains(.optional), callMember: { scope in return "\(scope).\(backticked: self.variable.name)" }, @@ -90,7 +91,13 @@ class VariableTemplate: Template { isAsync: false, isThrowing: false, isStatic: variable.kind.typeScope.isStatic, + isOptional: variable.attributes.contains(.optional), callMember: { scope in + guard !self.variable.attributes.contains(.optional) else { + // Optional readwrite properties cannot be assigned to: + // `Cannot assign to property: 'object' is immutable` + return "" + } return "\(scope).\(backticked: self.variable.name) = newValue" }, invocationArguments: [(nil, "newValue")]).render()) diff --git a/Sources/MockingbirdGenerator/Parser/Models/TypeAttributes.swift b/Sources/MockingbirdGenerator/Parser/Models/TypeAttributes.swift index 9c659e40..7eb76bad 100644 --- a/Sources/MockingbirdGenerator/Parser/Models/TypeAttributes.swift +++ b/Sources/MockingbirdGenerator/Parser/Models/TypeAttributes.swift @@ -89,22 +89,23 @@ struct Attributes: OptionSet, Hashable { static let convenience = Attributes(rawValue: 1 << 5) static let override = Attributes(rawValue: 1 << 6) static let objcName = Attributes(rawValue: 1 << 7) + static let `optional` = Attributes(rawValue: 1 << 8) // MARK: Inferred attributes - static let constant = Attributes(rawValue: 1 << 8) - static let readonly = Attributes(rawValue: 1 << 9) - static let async = Attributes(rawValue: 1 << 10) - static let `throws` = Attributes(rawValue: 1 << 11) - static let `inout` = Attributes(rawValue: 1 << 12) - static let variadic = Attributes(rawValue: 1 << 13) - static let failable = Attributes(rawValue: 1 << 14) - static let unwrappedFailable = Attributes(rawValue: 1 << 15) - static let closure = Attributes(rawValue: 1 << 16) - static let escaping = Attributes(rawValue: 1 << 17) - static let autoclosure = Attributes(rawValue: 1 << 18) + static let constant = Attributes(rawValue: 1 << 9) + static let readonly = Attributes(rawValue: 1 << 10) + static let async = Attributes(rawValue: 1 << 11) + static let `throws` = Attributes(rawValue: 1 << 12) + static let `inout` = Attributes(rawValue: 1 << 13) + static let variadic = Attributes(rawValue: 1 << 14) + static let failable = Attributes(rawValue: 1 << 15) + static let unwrappedFailable = Attributes(rawValue: 1 << 16) + static let closure = Attributes(rawValue: 1 << 17) + static let escaping = Attributes(rawValue: 1 << 18) + static let autoclosure = Attributes(rawValue: 1 << 19) // MARK: Custom attributes - static let implicit = Attributes(rawValue: 1 << 19) + static let implicit = Attributes(rawValue: 1 << 20) static let attributesKey = "key.attributes" static let attributeKey = "key.attribute" @@ -121,6 +122,7 @@ extension Attributes { case .convenience: self = .convenience case .override: self = .override case .objcName: self = .objcName + case .`optional`: self = .`optional` default: return nil } } diff --git a/Sources/MockingbirdTestsHost/ObjCProtocol.swift b/Sources/MockingbirdTestsHost/ObjCProtocol.swift new file mode 100644 index 00000000..8a5c4596 --- /dev/null +++ b/Sources/MockingbirdTestsHost/ObjCProtocol.swift @@ -0,0 +1,23 @@ +import Foundation + +@objc protocol ObjCProtocol: Foundation.NSObjectProtocol { + @objc func trivial() + @objc func parameterizedReturning(param: String) -> Bool + + @objc var property: Bool { get } + @objc var readwriteProperty: Bool { get set } + + // It’s possible to define Obj-C protocols with overloaded subscript requirements, but it can + // never be implemented in Swift as the compiler will complain about the conflicting selectors. + // @objc subscript(param: Int) -> Int { get set } + + // MARK: Optional + + @objc optional func optionalTrivial() + @objc optional func optionalParameterizedReturning(param: String) -> Bool + + @objc optional var optionalProperty: Bool { get } + @objc optional var optionalReadwriteProperty: Bool { get set } + + @objc optional subscript(param: Int) -> Bool { get set } +} diff --git a/Tests/MockingbirdTests/Framework/ObjectiveCProtocolTests.swift b/Tests/MockingbirdTests/Framework/ObjectiveCProtocolTests.swift new file mode 100644 index 00000000..3faf94e7 --- /dev/null +++ b/Tests/MockingbirdTests/Framework/ObjectiveCProtocolTests.swift @@ -0,0 +1,202 @@ +import Mockingbird +@testable import MockingbirdTestsHost +import XCTest + +class ObjectiveCProtocolTests: BaseTestCase { + + var protocolMock: ObjCProtocolMock! + var protocolInstance: ObjCProtocol { protocolMock } + + var protocolFake = ObjCProtocolFake() + + class ObjCProtocolFake: Foundation.NSObject, ObjCProtocol { + func trivial() {} + func parameterizedReturning(param: String) -> Bool { true } + + var property: Bool { true } + var readwriteProperty: Bool = true + + func optionalTrivial() {} + func optionalParameterizedReturning(param: String) -> Bool { true } + + var optionalProperty: Bool { true } + var optionalReadwriteProperty: Bool = true + + subscript(param: Int) -> Bool { + get { true } + set {} + } + } + + override func setUpWithError() throws { + self.protocolMock = mock(ObjCProtocol.self).initialize() + } + + + // MARK: - Resetting + + func testResetMock() { + protocolInstance.trivial() + reset(protocolMock) + verify(protocolMock.trivial()).wasNeverCalled() + } + + func testClearStubs() { + given(protocolInstance.parameterizedReturning(param: any())).willReturn(true) + clearStubs(on: protocolMock) + shouldFail { + _ = protocolInstance.parameterizedReturning(param: "foobar") + } + } + + func testClearInvocations() { + protocolInstance.trivial() + clearInvocations(on: protocolMock) + verify(protocolMock.trivial()).wasNeverCalled() + } + + + // MARK: - Concrete stubs + + func testTrivial() { + given(protocolMock.trivial()).willReturn() + protocolInstance.trivial() + verify(protocolMock.trivial()).wasCalled() + } + func testOptionalTrivial() { + given(protocolMock.optionalTrivial()).willReturn() + protocolInstance.optionalTrivial?() + verify(protocolMock.optionalTrivial()).wasCalled() + } + + func testParameterized() { + given(protocolMock.parameterizedReturning(param: any())).willReturn(true) + XCTAssertTrue(protocolInstance.parameterizedReturning(param: "foobar")) + verify(protocolMock.parameterizedReturning(param: any())).wasCalled() + } + func testOptionalParameterized() { + given(protocolMock.optionalParameterizedReturning(param: any())).willReturn(true) + XCTAssertTrue(protocolInstance.optionalParameterizedReturning?(param: "foobar") ?? false) + verify(protocolMock.optionalParameterizedReturning(param: any())).wasCalled() + } + + func testPropertyGetter() { + given(protocolMock.property).willReturn(true) + XCTAssertTrue(protocolInstance.property) + verify(protocolMock.property).wasCalled() + } + func testOptionalPropertyGetter() { + given(protocolMock.optionalProperty).willReturn(true) + XCTAssertTrue(protocolInstance.optionalProperty ?? false) + verify(protocolMock.optionalProperty).wasCalled() + } + + func testPropertySetter() { + let expectation = expectation(description: "setter was called") + given(protocolMock.readwriteProperty = any()).will { (_: Bool) in expectation.fulfill() } + protocolInstance.readwriteProperty = true + verify(protocolMock.readwriteProperty = any()).wasCalled() + waitForExpectations(timeout: 1) + } + + func testOptionalSubscriptGetter() { + given(protocolMock[any()]).willReturn(true) + XCTAssertTrue(protocolInstance[1] ?? false) + verify(protocolMock[any()]).wasCalled() + } + + + // MARK: - Object partial mock + + func testTrivialGlobalForwarding() { + protocolMock.forwardCalls(to: protocolFake) + protocolInstance.trivial() + verify(protocolMock.trivial()).wasCalled() + } + func testTrivialLocalForwarding() { + given(protocolMock.trivial()).willForward(to: protocolFake) + protocolInstance.trivial() + verify(protocolMock.trivial()).wasCalled() + } + + func testParameterizedGlobalForwarding() { + protocolMock.forwardCalls(to: protocolFake) + XCTAssertTrue(protocolInstance.parameterizedReturning(param: "foobar")) + verify(protocolMock.parameterizedReturning(param: any())).wasCalled() + } + func testParameterizedLocalForwarding() { + given(protocolMock.parameterizedReturning(param: any())).willForward(to: protocolFake) + XCTAssertTrue(protocolInstance.parameterizedReturning(param: "foobar")) + verify(protocolMock.parameterizedReturning(param: any())).wasCalled() + } + + func testPropertyGetterGlobalForwarding() { + protocolMock.forwardCalls(to: protocolFake) + XCTAssertTrue(protocolInstance.property) + verify(protocolMock.property).wasCalled() + } + func testPropertyGetterLocalForwarding() { + given(protocolMock.property).willForward(to: protocolFake) + XCTAssertTrue(protocolInstance.property) + verify(protocolMock.property).wasCalled() + } + + func testPropertySetterGlobalForwarding() { + protocolMock.forwardCalls(to: protocolFake) + let instance = protocolMock as ObjCProtocol + instance.readwriteProperty = true + verify(protocolMock.readwriteProperty = any()).wasCalled() + } + func testPropertySetterLocalForwarding() { + given(protocolMock.readwriteProperty = any()).willForward(to: protocolFake) + let instance = protocolMock as ObjCProtocol + instance.readwriteProperty = true + verify(protocolMock.readwriteProperty = any()).wasCalled() + } + + // MARK: Optionals + + func testOptionalTrivialGlobalForwarding() { + protocolMock.forwardCalls(to: protocolFake) + protocolInstance.optionalTrivial?() + verify(protocolMock.optionalTrivial()).wasCalled() + } + func testOptionalTrivialLocalForwarding() { + given(protocolMock.optionalTrivial()).willForward(to: protocolFake) + protocolInstance.optionalTrivial?() + verify(protocolMock.optionalTrivial()).wasCalled() + } + + func testOptionalParameterizedGlobalForwarding() { + protocolMock.forwardCalls(to: protocolFake) + XCTAssertTrue(protocolInstance.optionalParameterizedReturning?(param: "foobar") ?? false) + verify(protocolMock.optionalParameterizedReturning(param: any())).wasCalled() + } + func testOptionalParameterizedLocalForwarding() { + given(protocolMock.optionalParameterizedReturning(param: any())).willForward(to: protocolFake) + XCTAssertTrue(protocolInstance.optionalParameterizedReturning?(param: "foobar") ?? false) + verify(protocolMock.optionalParameterizedReturning(param: any())).wasCalled() + } + + func testOptionalPropertyGetterGlobalForwarding() { + protocolMock.forwardCalls(to: protocolFake) + XCTAssertTrue(protocolInstance.optionalProperty ?? false) + verify(protocolMock.optionalProperty).wasCalled() + } + func testOptionalPropertyGetterLocalForwarding() { + given(protocolMock.optionalProperty).willForward(to: protocolFake) + XCTAssertTrue(protocolInstance.optionalProperty ?? false) + verify(protocolMock.optionalProperty).wasCalled() + } + + func testOptionalSubscriptGetterGlobalForwarding() { + protocolMock.forwardCalls(to: protocolFake) + XCTAssertTrue(protocolInstance[1] ?? false) + verify(protocolMock[any()]).wasCalled() + } + func testOptionalSubscriptGetterLocalForwarding() { + given(protocolMock[any()]).willForward(to: protocolFake) + XCTAssertTrue(protocolInstance[1] ?? false) + verify(protocolMock[any()]).wasCalled() + } +} diff --git a/Tests/MockingbirdTests/Framework/Utilities/BaseTestCase.swift b/Tests/MockingbirdTests/Framework/Utilities/BaseTestCase.swift index 9b7cb3d3..e9747ba0 100644 --- a/Tests/MockingbirdTests/Framework/Utilities/BaseTestCase.swift +++ b/Tests/MockingbirdTests/Framework/Utilities/BaseTestCase.swift @@ -6,7 +6,7 @@ class BaseTestCase: XCTestCase { func shouldFail(_ times: Int = 1, file: String = #file, line: Int = #line, - _ context: @escaping () -> Void) { + @_implicitSelfCapture _ context: @escaping () -> Void) { let testFailer = XFailTestFailer(testCase: self, file: file, line: line) swizzleTestFailer(testFailer)