From cebf50125673c24dc1b24310331e60cf85c6db9f Mon Sep 17 00:00:00 2001 From: Nitkalya Wiriyanuparb Date: Tue, 8 Dec 2020 21:57:41 +1300 Subject: [PATCH] Support calling methods and closures with nil --- Cuckoo.xcodeproj/project.pbxproj | 4 -- .../ObjCHelpers/NSInvocation+OCMockWrapper.m | 2 +- OCMock/ObjectiveArgumentClosure.swift | 13 +++++++ Tests/OCMock/ObjectiveClassTest.swift | 38 +++++++++++++++++++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/Cuckoo.xcodeproj/project.pbxproj b/Cuckoo.xcodeproj/project.pbxproj index 06ceef11..d4a75acc 100644 --- a/Cuckoo.xcodeproj/project.pbxproj +++ b/Cuckoo.xcodeproj/project.pbxproj @@ -188,7 +188,6 @@ 6767946AF36C2A279F53D3FC /* ToBeStubbedProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE705433C346CA3DF4E2AB96 /* ToBeStubbedProperty.swift */; }; 67BC45751E3361D5B2EE6C47 /* Array+matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1D814F035D5D39FC84D26C /* Array+matchers.swift */; }; 68472F9A24D3ECA700E096C6 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68472F9924D3ECA600E096C6 /* GeneratedMocks.swift */; }; - 68472F9C24D3EE1100E096C6 /* ObjectiveExamplesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68472F9B24D3EE1100E096C6 /* ObjectiveExamplesTest.swift */; }; 68666AF31756DE755BED16A0 /* GenericClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01C5ABA73DC1B0CCE088669 /* GenericClass.swift */; }; 68696CFDFD67F6D77057F64D /* CallMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C854CC76B478ED72B6D3A65 /* CallMatcher.swift */; }; 689E3852D2B5C6C81837A08F /* StubFunctionThenTrait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C749F49256DDA51A9C96C9 /* StubFunctionThenTrait.swift */; }; @@ -773,7 +772,6 @@ 654DD2C28B20B62C30F20699 /* ObjectiveCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjectiveCatcher.m; sourceTree = ""; }; 6777A3E4D2701ADC5254EDB5 /* Pods-Cuckoo_OCMock-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Cuckoo_OCMock-macOS.debug.xcconfig"; path = "Target Support Files/Pods-Cuckoo_OCMock-macOS/Pods-Cuckoo_OCMock-macOS.debug.xcconfig"; sourceTree = ""; }; 68472F9924D3ECA600E096C6 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GeneratedMocks.swift; path = Generated/GeneratedMocks.swift; sourceTree = ""; }; - 68472F9B24D3EE1100E096C6 /* ObjectiveExamplesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectiveExamplesTest.swift; sourceTree = ""; }; 6873C8013002AFEA7565BDAC /* ClassTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassTest.swift; sourceTree = ""; }; 68BD8B62F4589B263FA137F0 /* Pods-Cuckoo_OCMock-macOS-Cuckoo_OCMock-macOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Cuckoo_OCMock-macOS-Cuckoo_OCMock-macOSTests.debug.xcconfig"; path = "Target Support Files/Pods-Cuckoo_OCMock-macOS-Cuckoo_OCMock-macOSTests/Pods-Cuckoo_OCMock-macOS-Cuckoo_OCMock-macOSTests.debug.xcconfig"; sourceTree = ""; }; 6975D97C79395805A3BB3B04 /* Dictionary+matchers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+matchers.swift"; sourceTree = ""; }; @@ -1194,7 +1192,6 @@ children = ( EAFD10D2F5491D90D226ED78 /* ObjectiveClassTest.swift */, C3D36A00ADFA42CC4C411F3E /* ObjectiveProtocolTest.swift */, - 68472F9B24D3EE1100E096C6 /* ObjectiveExamplesTest.swift */, ); path = OCMock; sourceTree = ""; @@ -2772,7 +2769,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 68472F9C24D3EE1100E096C6 /* ObjectiveExamplesTest.swift in Sources */, 25328F4C5AD244EB5D8C1868 /* TestError.swift in Sources */, 4EB79BE67C370C4BD74B4604 /* ObjectiveClassTest.swift in Sources */, 749D92CA8AAF73824D8EED26 /* ObjectiveProtocolTest.swift in Sources */, diff --git a/OCMock/ObjCHelpers/NSInvocation+OCMockWrapper.m b/OCMock/ObjCHelpers/NSInvocation+OCMockWrapper.m index bee6bba2..fe1bd192 100644 --- a/OCMock/ObjCHelpers/NSInvocation+OCMockWrapper.m +++ b/OCMock/ObjCHelpers/NSInvocation+OCMockWrapper.m @@ -32,7 +32,7 @@ - (NSArray*)arguments { [self getArgument:&arg atIndex: i]; NSValue* _Nonnull n = [NSValue value:&arg withObjCType: argType]; - if (OCMIsObjectType(argType)) { + if (OCMIsObjectType(argType) && arg) { [arguments addObject:(__bridge id _Nonnull)(arg)]; } else if (n) { [arguments addObject:n]; diff --git a/OCMock/ObjectiveArgumentClosure.swift b/OCMock/ObjectiveArgumentClosure.swift index c32ca7ee..7fd1e2ce 100644 --- a/OCMock/ObjectiveArgumentClosure.swift +++ b/OCMock/ObjectiveArgumentClosure.swift @@ -31,6 +31,19 @@ public func objectiveArgumentClosure(from: Any) -> (IN1, IN2) -> Void } } +// TODO add more variants +public func objectiveOptionalArgumentClosure(from: Any) -> (IN1?, IN2?) -> Void { + return { in1, in2 in + var arg = from + let block = UnsafeRawPointer(&arg).assumingMemoryBound(to: (@convention(block) (NSObject?, NSObject?) -> Void).self).pointee + + let nsIn1 = in1 == nil ? nil : TrustMe.onThis(in1 as Any) + let nsIn2 = in2 == nil ? nil : TrustMe.onThis(in2 as Any) + + block(nsIn1, nsIn2) + } +} + public func objectiveArgumentClosure(from: Any) -> (IN1, IN2, IN3) -> Void { return { in1, in2, in3 in var arg = from diff --git a/Tests/OCMock/ObjectiveClassTest.swift b/Tests/OCMock/ObjectiveClassTest.swift index 8c2a5be5..2691d9c0 100644 --- a/Tests/OCMock/ObjectiveClassTest.swift +++ b/Tests/OCMock/ObjectiveClassTest.swift @@ -160,6 +160,39 @@ class ObjectiveClassTest: XCTestCase { objcVerify(mock.dudka(lelo: objcAny())) } + + func testSwiftClassWithNullableArgs() { + let expectCompleteWithNil = expectation(description: "Should call completion with nil") + let expectNilArg = expectation(description: "Should call with nil") + + let mock = objcStub(for: SwiftClass.self) { stubber, mock in + stubber.when(mock.say("cheeze", completion: objcAnyClosure())).then { args in + let completionHandler = objectiveOptionalArgumentClosure(from: args[1]) as (String, [String]?) -> Void + completionHandler("meh", nil) + } + stubber.when(mock.say(nil, completion: objcAnyClosure())).then { args in + let completionHandler = objectiveArgumentClosure(from: args[1]) as (String, [String]?) -> Void + completionHandler("nil now behaves", []) + } + } + + mock.say("cheeze") { result, messages in + XCTAssertEqual("meh", result) + XCTAssertNil(messages) + expectCompleteWithNil.fulfill() + } + + mock.say(nil) { result, messages in + XCTAssertEqual("nil now behaves", result) + XCTAssertEqual([], messages) + expectNilArg.fulfill() + } + + wait(for: [expectCompleteWithNil, expectNilArg], timeout: 1) + + objcVerify(mock.say("cheeze", completion: objcAnyClosure())) + objcVerify(mock.say(nil, completion: objcAnyClosure())) + } } class SwiftClass: NSObject { @@ -168,6 +201,11 @@ class SwiftClass: NSObject { dynamic func dudka(lelo: String) -> Bool { return false } + + @objc + dynamic func say(_ message: String?, completion: @escaping (String, [String]?) -> Void) -> Void { + // mock me + } } #else #warning("macOS tests are missing.")