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

OCMock stubbing with String return type #378

Open
Tyler-Keith-Thompson opened this issue Jan 15, 2021 · 4 comments
Open

OCMock stubbing with String return type #378

Tyler-Keith-Thompson opened this issue Jan 15, 2021 · 4 comments
Labels
help wanted This issue is asking for a way to solve a problem.

Comments

@Tyler-Keith-Thompson
Copy link
Contributor

So I was trying to mock UserDefaults string(forKey:) method using OCMock. My attempt:

        let mock = objcStub(for: UserDefaults.self) { (stubber, mock) in
            stubber.when(mock.string(forKey: "key")).thenReturn("value") //SEG FAULT 11
        }

This blows up spectacularly with a segfault. After digging into a bit I have a couple of theories. It seems to me this is some issue with NSString vs String types. The method signature for UserDefaults (despite being an objective-c type) as exposed to Swift returns a String?. It seems probable that it honestly returns an NSString and there is some language construct that just converts that.

Note that other UserDefaults kinds of things work as expected (bool, for example).

Any advice for how to get around this?

@MatyasKriz MatyasKriz added the help wanted This issue is asking for a way to solve a problem. label Feb 2, 2021
@MatyasKriz
Copy link
Collaborator

@TadeasKriz might know, he's the brain behind integrating OCMock into Cuckoo.

@Tyler-Keith-Thompson
Copy link
Contributor Author

Update on this: Turns out this is really a problem with anything that's a swift value type that the language sort of auto-converts from Objective-C. For example when mocking FileManager we ran into this same problem because contents(atPath:) returns a Data? struct instead of an NSData object. So fundamentally it can't handle a thenReturn method.

Seems there are a couple workarounds:

  • Create a protocol in your production code and mock that 🤮. Unfortunately this means changing production code purely for the sake of tests
  • Create a subclass of UserDefaults or FileManager in your test target only, point Cuckoo at that file in your test target. Downside: You have to override any method you want it to mock, also naming things becomes hard. You could do class FileManager: Foundation.FileManager if you can stand the namespacing thing or you can try to come up with a different naming convention

@rolandkakonyi
Copy link
Contributor

I managed to make this work by utilizing _ObjectiveCBridgeable protocol by adding extensions to Stubber and StubRecorder. (see below)

By adding this to my project I managed to make it work for String along with other bridgeable types.

import Cuckoo

extension Stubber {
    func when<OUT: _ObjectiveCBridgeable>(_ invocation: @autoclosure () -> OUT) -> StubRecorder<OUT._ObjectiveCType> {
        when(invocation()._bridgeToObjectiveC())
    }

    func when<OUT: _ObjectiveCBridgeable>(_ invocation: @autoclosure () -> OUT?) -> StubRecorder<OUT._ObjectiveCType?> {
        when(invocation()?._bridgeToObjectiveC())
    }
}

extension StubRecorder {
    func thenReturn<T: _ObjectiveCBridgeable>(
        _ value: T
    ) where T._ObjectiveCType == OUT, T._ObjectiveCType: NSObject {
        thenReturn(value._bridgeToObjectiveC())
    }

    func thenReturn<T: _ObjectiveCBridgeable>(
        _ value: T?
    ) where T._ObjectiveCType? == OUT, T._ObjectiveCType: NSObject {
        thenReturn(value?._bridgeToObjectiveC())
    }
}

@drwjf
Copy link

drwjf commented Apr 14, 2023

The solution works perfectly for bridgeable classes, such as String.

But how do we create stubbing with Enumeration type?

    func testObjectiveC() {
        let mock = objcStub(for: UIDevice.self) { stubber, mock in
            stubber.when(mock.batteryState).thenReturn(UIDevice.BatteryState.charging)
        }
        XCTAssertEqual(mock.batteryState, .charging)
    }

Can't be built now. The compiler reports Instance method 'thenReturn' requires that 'UIDevice.BatteryState' conform to '_ObjectiveCBridgeable'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted This issue is asking for a way to solve a problem.
Projects
None yet
Development

No branches or pull requests

4 participants