erikdoe / ocmock

Mock objects for Objective-C
http://ocmock.org
Apache License 2.0
2.16k stars 606 forks source link

OCMStub an instance mthod not work as expected #512

Closed liuxuan30 closed 2 years ago

liuxuan30 commented 2 years ago

Hi there,

I am trying to use OCMock to test my framework.

I have a simple class:

@interface PrivacyPolicy : NSObject <NSCoding>

- (BOOL)hasPermissionFor:(NSString *)biz scene:(nullable NSString *)scene;
@end
- (BOOL)hasPermissionFor:(NSString *)biz scene:(nullable NSString *)scene {
    return [self someVerifyMethod];
}

and my test code:

    id locationMock;
    locationMock = OCMClassMock([CLLocationManager class]);
    OCMStub(ClassMethod([locationMock authorizationStatus])).andReturn(kCLAuthorizationStatusAuthorizedWhenInUse);

    id policyMock = OCMClassMock([PrivacyPolicy class]);
    OCMStub([policyMock hasPermissionFor:OCMOCK_ANY scene:OCMOCK_ANY]).andReturn(YES);

    [privacyManager requestUserPermission];  // which will eventually call hasPermissionForBiz

    // hope hasPermissionForBiz:OCMOCK_ANY return YES

    OCMVerify([policyMock hasPermissionFor:OCMOCK_ANY scene:OCMOCK_ANY]);  // fail
    OCMVerify(ClassMethod([locationMock authorizationStatus]));  // pass

however, I figure hasPermissionFor: is never getting mocked, the real method is always code, and simply return NO, while I tried to mock as YES.

I have no idea what's wrong, because mocking authorizationStatus is successful.

Could someone help why or where I made some mistakes, and maybe give some places I could debug with?

liuxuan30 commented 2 years ago

UPDATE:

when I try a simplified version to test whether the mock is working:

- (void)testMock {
    id policyMock = OCMClassMock([PrivacyPolicy class]);
    OCMExpect([policyMock hasPermissionFor:OCMOCK_ANY scene:OCMOCK_ANY]).andReturn(YES);

    PrivacyConfig *config = [PrivacyConfig currentConfig];
    PrivacyPolicy *policy = [config getPolicyWithPermission:TestPermissionKey];

    BOOL val = [policy hasPermissionFor:KSPKTestPermissionKey scene:@"test"];
    XCTAssertTrue(val);
    OCMVerify([policyMock hasPermissionFor:OCMOCK_ANY scene:OCMOCK_ANY]);  // fail
}

When I tried to explicitly call hasPermissionFor:scene: it seems no matter with or without .andForwardToRealObject().andReturn(YES) the XCTAssertTrue and OCMVerify never pass, indicating the mock is not working at all.

I'm not sure if this is a limitation, or bug? because it seems PrivacyPolicy is a very simple class and nothing special.

erikdoe commented 2 years ago

This looks like a problem with using OCMock. What is the object returned by getPolicyWithPermission:? Assuming that this is not the mock object, how would the mock notice that a method is called on that object?

Please do not ask follow-up questions here. Questions relating to the use of OCMock should be asked on StackOverflow. Thank you.

liuxuan30 commented 2 years ago

@erikdoe I do hesitated where to ask before opening this thread, but eventually I thought I have read the doc and thinking it's worthy of confirming whether it's a bug or not.

Because in the doc

id mock = OCMClassMock([SomeClass class]);
OCMStub([mock someMethod]).andReturn(myValue);

/* run code under test */

OCMVerify([mock someMethod]);

it seems I can hook a instance method on a Class level mock?

getPolicyWithPermission returns a PrivacyPolicy instance, which I assume

id policyMock = OCMClassMock([PrivacyPolicy class]);
OCMExpect([policyMock hasPermissionFor:OCMOCK_ANY scene:OCMOCK_ANY]).andReturn(YES);

should work for this returned object? Could you confirm this is not a bug?

e.g. how could I mock a instance method for all instance for a specific class? Thanks.

dmaclach commented 2 years ago

It's not a bug. You have a basic misunderstanding of what the code is doing. OCMClassMock does not return a class level mock, it's returning an "mock" instance of the class. Please follow through with posting to stack overflow so that others can easily find the answer if they have similar questions.