Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support calling objective-c methods and closures with nil #374

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions Cuckoo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -773,7 +772,6 @@
654DD2C28B20B62C30F20699 /* ObjectiveCatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjectiveCatcher.m; sourceTree = "<group>"; };
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 = "<group>"; };
68472F9924D3ECA600E096C6 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GeneratedMocks.swift; path = Generated/GeneratedMocks.swift; sourceTree = "<group>"; };
68472F9B24D3EE1100E096C6 /* ObjectiveExamplesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectiveExamplesTest.swift; sourceTree = "<group>"; };
6873C8013002AFEA7565BDAC /* ClassTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassTest.swift; sourceTree = "<group>"; };
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 = "<group>"; };
6975D97C79395805A3BB3B04 /* Dictionary+matchers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+matchers.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1194,7 +1192,6 @@
children = (
EAFD10D2F5491D90D226ED78 /* ObjectiveClassTest.swift */,
C3D36A00ADFA42CC4C411F3E /* ObjectiveProtocolTest.swift */,
68472F9B24D3EE1100E096C6 /* ObjectiveExamplesTest.swift */,
);
path = OCMock;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down
2 changes: 1 addition & 1 deletion OCMock/ObjCHelpers/NSInvocation+OCMockWrapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
13 changes: 13 additions & 0 deletions OCMock/ObjectiveArgumentClosure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ public func objectiveArgumentClosure<IN1, IN2>(from: Any) -> (IN1, IN2) -> Void
}
}

// TODO add more variants
public func objectiveOptionalArgumentClosure<IN1, IN2>(from: Any) -> (IN1?, IN2?) -> Void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll have to try coming up with a more complex solution to this, because as the number of arguments go up, the combinations of optional and non-optional arguments will skyrocket.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree. I ended up defining this in my test project just for the specific test cases that I need. Should I split this into 2 PRs so the 1st problem can be fixed? I really need that fix.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting this up sounds like the better call at the moment. Would you please add the remaining combinations for 2-parameter closures, i.e. (optional, non-optional), (non-optional, optional) as well as an optional counterpart to the 1-parameter closure. We'll need to come up with a more generic solution, but for now this can help make the closures more accessible by showing users how to implement them (only using the right amount of arguments). Thanks!

Also, would you mind creating a test or two for the closures with optional parameters? Just to make sure they work and when replacing with the generic solution to know nothing has been broken.

Afterwards, we can probably merge this and release.

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<NSObject>.onThis(in1 as Any)
let nsIn2 = in2 == nil ? nil : TrustMe<NSObject>.onThis(in2 as Any)

block(nsIn1, nsIn2)
}
}

public func objectiveArgumentClosure<IN1, IN2, IN3>(from: Any) -> (IN1, IN2, IN3) -> Void {
return { in1, in2, in3 in
var arg = from
Expand Down
38 changes: 38 additions & 0 deletions Tests/OCMock/ObjectiveClassTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good nil.

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 {
Expand All @@ -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.")
Expand Down