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

Mocking a delegate property with objcStub #358

Open
s-hocking opened this issue Jul 30, 2020 · 11 comments
Open

Mocking a delegate property with objcStub #358

s-hocking opened this issue Jul 30, 2020 · 11 comments
Labels
help wanted This issue is asking for a way to solve a problem.

Comments

@s-hocking
Copy link

s-hocking commented Jul 30, 2020

Hi there. I'm currently trying to mock out CBCentralManager using the experimental ObjC stubbing feature. I've run into an issue with the delegate property on this class... If I don't stub the delegate property, OCMock fails my test when my code accesses the delegate property, with "unexpected method invoked":

caught "NSInternalInconsistencyException", "OCMockObject(CBCentralManager): unexpected method invoked: setDelegate:<MySDKProject.BLECentral: 0x7fa1964178a0>

So I then try to stub the delegate property, but nothing seems to work.

  • Trying to stub using stubber.when(mock.delegate.set).then... complains with "Value of type 'CBCentralManagerDelegate?' has no member 'set'"
  • Trying to stub using stubber.when(mock.setDelegate(any())) complains that Value of type 'CBCentralManager' has no member 'setDelegate'

What am I doing wrong?

Edit: for anyone else trying to mock CoreBluetooth classes, this project from Nordic Semiconductor looks pretty good!

@MatyasKriz
Copy link
Collaborator

I'm afraid you're not using the correct methods for ObjC mocking. Please post the whole test, so I can verify how you create mocks, stub them, and verify them.

@MatyasKriz MatyasKriz added the help wanted This issue is asking for a way to solve a problem. label Jul 31, 2020
@s-hocking
Copy link
Author

s-hocking commented Jul 31, 2020

Thanks for the response. Here's a brief test class that illustrates my problem:

class CuckooBluetoothDemo: XCTestCase {
    
    var mockCentral: CBCentralManager!

    override func setUpWithError() throws {
        mockCentral = objcStub(for: CBCentralManager.self) { stubber, mock in
            // Neither of these stubs compile:
            stubber.when(mock.delegate.set).then { args in // Value of type 'CBCentralManagerDelegate?' has no member 'set'
                
            }
            
            stubber.when(mock.setDelegate).then { args in // Value of type 'CBCentralManager' has no member 'setDelegate'
                
            }
        }
    }

    func testSettingDelegate() throws {
        // This line triggers an error because the delegate is not stubbed:
        // caught "NSInternalInconsistencyException", "OCMockObject(CBCentralManager): unexpected method invoked: setDelegate:-[CuckooBluetoothDemo testSettingDelegate] "
        mockCentral.delegate = self
    }
}

extension CuckooBluetoothDemo: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        // This method is just here to satisfy the compiler
    }
}

@MatyasKriz
Copy link
Collaborator

MatyasKriz commented Jul 31, 2020

Hey, I just played around a bit with this. We were missing a NSObjectProtocol support.

Please try branch fix/objc-nsobjectprotocol, no need to rebuild/redownload the generator, this change affects only Cuckoo internals, so just install pods again after changing the Podfile.

Thank you so much for bringing this to light, if you come up with any other shortcomings, let us know and we'll hopefully find a way to fix them.

Oh and in ObjC mocking, you needn't specify anything like .set, just call it like you would, it's an autoclosure to make sure that OCMock can do its thing.

@MatyasKriz MatyasKriz added the awaiting response A Cuckoo maintainer is awaiting response from the issue/PR author/community. label Jul 31, 2020
@s-hocking
Copy link
Author

Thanks for looking in to this! I've tried out your bug fix branch, which makes it possible to mock out the delegate return value 👍

I've still got an issue though when the delegate property is set. The test fails because setDelegate: is not mocked. How can I mock this method?

class CuckooBluetoothDemo: XCTestCase {
    
    var mockCentral: CBCentralManager!

    override func setUpWithError() throws {
        mockCentral = objcStub(for: CBCentralManager.self) { stubber, mock in
            stubber.when(mock.delegate).thenReturn(self) // this now works with the updated branch!
        }
    }

    func testSettingDelegate() throws {
        // This line still fails the test because setDelegate: is not stubbed, with error:
        // caught "NSInternalInconsistencyException", "OCMockObject(CBCentralManager): unexpected method invoked: setDelegate:-[CuckooBluetoothDemo testSettingDelegate] "
        mockCentral.delegate = self
    }
}

extension CuckooBluetoothDemo: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        // This method is just here to satisfy the compiler
    }
}

@MatyasKriz MatyasKriz removed the awaiting response A Cuckoo maintainer is awaiting response from the issue/PR author/community. label Aug 3, 2020
@MatyasKriz
Copy link
Collaborator

Thanks for the feedback, I'll take a look at it, hopefully it's nothing too major.

@quetool
Copy link

quetool commented Aug 3, 2020

Hi! I just ran into this myself too, how do I need to change Podfile to add fix/objc-nsobjectprotocol branch?

@quetool
Copy link

quetool commented Aug 3, 2020

Like this?

pod 'Cuckoo', :git => 'https://github.com/Brightify/Cuckoo.git', :branch => 'fix/objc-nsobjectprotocol'

@quetool
Copy link

quetool commented Aug 3, 2020

I've changed the Podfile, still getting the same error but I am not sure it is related with the poster´s issue

Captura de Pantalla 2020-08-03 a la(s) 19 11 55

caught "NSInternalInconsistencyException", "OCMockObject(MiBancoAPIClient): unexpected method invoked: expireSessionWithCompletion:<__NSMallocBlock__: 0x600000054240> 
	stubbed:	hostAvailable
	stubbed:	isSessionStillValid"
(
	0   CoreFoundation                      0x00007fff23e3cf0e __exceptionPreprocess + 350
	1   libobjc.A.dylib                     0x00007fff50ba89b2 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff23e3cd4c +[NSException raise:format:] + 188
	3   OCMock                              0x0000000109ab76ce -[OCMockObject handleUnRecordedInvocation:] + 190
	4   OCMock                              0x0000000109ab6a2d -[OCMockObject forwardInvocation:] + 93
	5   CoreFoundation                      0x00007fff23e416b6 ___forwarding___ + 838
	6   CoreFoundation                      0x00007fff23e43bf8 _CF_forwarding_prep_0 + 120
	7   CoreFoundation                      0x00007fff23e43e8c __invoking___ + 140
	8   CoreFoundation                      0x00007fff23e41071 -[NSInvocation invoke] + 321
	9   CoreFoundation                      0x00007fff23e41344 -[NSInvocation invokeWithTarget:] + 68
	10  Cuckoo                              0x00000001099e191a -[CuckooMockObject forwardInvocation:] + 586
	11  CoreFoundation                      0x00007fff23e416b6 ___forwarding___ + 838
	12  CoreFoundation                      0x00007fff23e43bf8 _CF_forwarding_prep_0 + 120
	13  MiBancoSwiftTests                   0x00000001093affa6 $s17MiBancoSwiftTests06CuckooD0C7testApiyyF + 246
	14  MiBancoSwiftTests                   0x00000001093b035b $s17MiBancoSwiftTests06CuckooD0C7testApiyyFTo + 43
	15  CoreFoundation                      0x00007fff23e43e8c __invoking___ + 140
	16  CoreFoundation                      0x00007fff23e41071 -[NSInvocation invoke] + 321
	17  XCTest                              0x0000000109082037 __24-[XCTestCase invokeTest]_block_invoke_2 + 52
	18  XCTest                              0x0000000109081fe3 __24-[XCTestCase invokeTest]_block_invoke.206 + 320
	19  XCTest                              0x00000001090dcdc2 +[XCTestCase(Failures) performFailableBlock:testCase:testCaseRun:shouldInterruptTest:] + 69
	20  XCTest                              0x00000001090dccd4 -[XCTestCase(Failures) _performTurningExceptionsIntoFailuresInterruptAfterHandling:block:] + 115
	21  XCTest                              0x00000001090819f6 -[XCTestCase invokeTest] + 1183
	22  XCTest                              0x0000000109083329 __26-[XCTestCase performTest:]_block_invoke_2 + 43
	23  XCTest                              0x00000001090dcdc2 +[XCTestCase(Failures) performFailableBlock:testCase:testCaseRun:shouldInterruptTest:] + 69
	24  XCTest                              0x00000001090dccd4 -[XCTestCase(Failures) _performTurningExceptionsIntoFailuresInterruptAfterHandling:block:] + 115
	25  XCTest                              0x0000000109083260 __26-[XCTestCase performTest:]_block_invoke.359 + 86
	26  XCTest                              0x00000001090efa0d +[XCTContext runInContextForTestCase:block:] + 211
	27  XCTest                              0x0000000109082b14 -[XCTestCase performTest:] + 566
	28  XCTest                              0x00000001090c938e -[XCTest runTest] + 57
	29  XCTest                              0x000000010907cd50 __27-[XCTestSuite performTest:]_block_invoke + 354
	30  XCTest                              0x000000010907c4a2 __59-[XCTestSuite _performProtectedSectionForTest:testSection:]_block_invoke + 24
	31  XCTest                              0x00000001090efa0d +[XCTContext runInContextForTestCase:block:] + 211
	32  XCTest                              0x000000010907c459 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 148
	33  XCTest                              0x000000010907c7be -[XCTestSuite performTest:] + 348
	34  XCTest                              0x00000001090c938e -[XCTest runTest] + 57
	35  XCTest                              0x000000010907cd50 __27-[XCTestSuite performTest:]_block_invoke + 354
	36  XCTest                              0x000000010907c4a2 __59-[XCTestSuite _performProtectedSectionForTest:testSection:]_block_invoke + 24
	37  XCTest                              0x00000001090efa0d +[XCTContext runInContextForTestCase:block:] + 211
	38  XCTest                              0x000000010907c459 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 148
	39  XCTest                              0x000000010907c7be -[XCTestSuite performTest:] + 348
	40  XCTest                              0x00000001090c938e -[XCTest runTest] + 57
	41  XCTest                              0x000000010907cd50 __27-[XCTestSuite performTest:]_block_invoke + 354
	42  XCTest                              0x000000010907c4a2 __59-[XCTestSuite _performProtectedSectionForTest:testSection:]_block_invoke + 24
	43  XCTest                              0x00000001090efa0d +[XCTContext runInContextForTestCase:block:] + 211
	44  XCTest                              0x000000010907c459 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 148
	45  XCTest                              0x000000010907c7be -[XCTestSuite performTest:] + 348
	46  XCTest                              0x00000001090c938e -[XCTest runTest] + 57
	47  XCTest                              0x00000001090fef14 __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke + 171
	48  XCTest                              0x00000001090ff001 __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke.100 + 96
	49  XCTest                              0x0000000109097746 -[XCTestObservationCenter _observeTestExecutionForBlock:] + 682
	50  XCTest                              0x00000001090fec9f -[XCTTestRunSession runTestsAndReturnError:] + 615
	51  XCTest                              0x0000000109060744 -[XCTestDriver runTestsAndReturnError:] + 456
	52  XCTest                              0x00000001090eb64c _XCTestMain + 2496
	53  libXCTestBundleInject.dylib         0x0000000107c4cbfa __copy_helper_block_e8_32s + 0
	54  CoreFoundation                      0x00007fff23da0b5c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
	55  CoreFoundation                      0x00007fff23da0253 __CFRunLoopDoBlocks + 195
	56  CoreFoundation                      0x00007fff23d9b043 __CFRunLoopRun + 995
	57  CoreFoundation                      0x00007fff23d9a944 CFRunLoopRunSpecific + 404
	58  GraphicsServices                    0x00007fff38ba6c1a GSEventRunModal + 139
	59  UIKitCore                           0x00007fff48c8b9ec UIApplicationMain + 1605
	60  MiBanco Dev                         0x0000000106926bc0 main + 112
	61  libdyld.dylib                       0x00007fff51a231fd start + 1
	62  ???                                 0x0000000000000005 0x0 + 5
)

@MatyasKriz
Copy link
Collaborator

Hey, @quetool. Your issue is not quite relevant to @s-hocking's. Your mockApiClient doesn't stub the expireSession method, so OCMock tells you that in the error message.

As for calling the setter for resolving @s-hocking, I'm pretty stumped without the necessary experience in ObjC to include this functionality at the moment. I'll leave the issue open and see if either I or @TadeasKriz gets some bright idea.

@jandrewmoore
Copy link

jandrewmoore commented Jan 14, 2022

I am having almost the same issue as @s-hocking. Using objcStub(), the class I'm testing sets delegate on the mock, test fails.

Looking through the OCMock code and documentation, it seems like maybe the mock that objcStub() creates is a strict mock and will always fail if an unstubbed method is invoked (setDelegate in this case). If Cuckoo/OCMock exposes a way to make "nice" mocks, @s-hocking and I might be unstuck.

OCMock's method for making "nice" mocks: https://github.com/erikdoe/ocmock/blob/6358799e04cb93d8f126f1ea6a67e2e351b169f1/Examples/iOS5Example/usr/include/OCMock/OCMockObject.h#L22

@MatyasKriz
Copy link
Collaborator

MatyasKriz commented Jan 15, 2022

Hey @jandrewmoore, that might work, and even if it doesn't for some reason, it's still a good feature to have. Thanks for doing the research. 🙂

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