问题
I'm having an issue with the OCMock
framework for iOS. I'm essentially trying to mock UIAlertView
's initWithTitle:message:delegate
... method. The example below doesn't work in the sense that the stubbed return value isn't returned when I call the initWithTitle
method.
UIAlertView *emptyAlert = [UIAlertView new];
id mockAlert = [OCMockObject partialMockForObject:[UIAlertView alloc]];
[[[mockAlert stub] andReturn:emptyAlert] initWithTitle:OCMOCK_ANY message:OCMOCK_ANY delegate:nil cancelButtonTitle:OCMOCK_ANY otherButtonTitles:nil];
UIAlertView *testAlertReturnValue = [[UIAlertView alloc] initWithTitle:@"title" message:@"message" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles:nil];
if(testAlertReturnValue == emptyAlert) {
NSLog(@"UIAlertView test worked");
}
However, it does work if I use the same idea for NSDictionary
.
NSDictionary *emptyDictionary = [NSDictionary new];
id mockDictionary = [OCMockObject partialMockForObject:[NSDictionary alloc]];
[[[mockDictionary stub] andReturn:emptyDictionary] initWithContentsOfFile:OCMOCK_ANY];
NSDictionary *testDictionaryReturnValue = [[NSDictionary alloc] initWithContentsOfFile:@"test"];
if(testDictionaryReturnValue == emptyDictionary) {
NSLog(@"NSDictionary test worked");
}
One thing I notice is that the method "forwardInvocationForRealObject:
" in "OCPartialMockObject.m
" is called during the NSDictionary
initWithContentsOfFile
call, but not during the UIAlertView
initWithTitle call.
Could this be an OCMock
bug?
回答1:
I had issues with mocking UIAlertView
as well, and my best guess is that it's the vararg that's throwing it off (can't 100% remember though). My solution was to create a factory method for UIAlertView
and add it as a category.
+ (instancetype)alertViewWithTitle:(NSString *)title message:(NSString *)message delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles;
Notice that I replace the varargs with an NSArray
. This method is definitely mockable, and the syntax is pretty similar now that we have array literals:
[UIAlertView alertViewWithTitle:@"Warning" message:@"Really delete your save file?" delegate:self cancelButtonTitle:@"No" otherButtonTitles:@[ @"Yes", @"Maybe" ]];
If you have the flexibility to change your source code, this'd be my suggestion.
EDIT
Looking more closely at your code, you are creating a partial mock, stubbing it's init method, then not doing anything with it. It's possible the way you are doing it might actually work if you replace the [UIAlertView alloc]
with the mock you create. Can't say for sure because I do remember having issues with it.
回答2:
Here's a more recent example, OCMock now supports class mocks.
id mockAlertView = [OCMockObject mockForClass:[UIAlertView class]];
[[[mockAlertView stub] andReturn:mockAlertView] alloc];
(void)[[[mockAlertView expect] andReturn:mockAlertView]
initWithTitle:@"Title"
message:@"Message"
delegate:OCMOCK_ANY
cancelButtonTitle:OCMOCK_ANY
otherButtonTitles:OCMOCK_ANY, nil];
[[mockAlertView expect] show];
// code that will display the alert here
[mockAlertView verify];
[mockAlertView stopMocking];
It's pretty common to have the alert being triggered from a callback on something. One way to wait for that is using a verifyWithDelay, see https://github.com/erikdoe/ocmock/pull/59.
回答3:
For some reason mocking +(id)alloc in UIAlertView doesn't seem to work, so rather than partially mock UIAlertView and stub the (for example) initWithTitle: method, I now use the following fix. Hopefully this will be useful to anyone else facing similar problems.
XCTest_UIAlertView+MyCustomCategory.m
/**
Tests alert displays on screen with correct message
Method: +(void)showAlertWithMessage:
*/
-(void)test_showAlertWithMessage
{
NSString *alertMessage = @"hello";
UIAlertView *alert = [UIAlertView new];
[UIAlertView setOCMock_UIAlertView:alert];
id alertToTest = [OCMockObject partialMockForObject:alert];
[[alertToTest expect] show];
[UIAlertView showAlertWithMessage:alertMessage];
[alertToTest verify];
XCTAssert([alert.message isEqualToString:alertMessage], @"alert message incorrect, expected [%@]", alertMessage);
}
UIAlertView+MyCustomCategory.m
/**
@warning variable for unit testing only
*/
static UIAlertView *__OCMock_UIAlertView;
@implementation UIAlertView (MyCustomCategory)
+(void)setOCMock_UIAlertView:(UIAlertView *)alert
{
__OCMock_UIAlertView = alert;
}
-(id)init
{
if(__OCMock_UIAlertView)
{
self = __OCMock_UIAlertView;
if(self) {
}
return self;
}
return [super init];
}
+(void)showAlertWithMessage:(NSString *)message
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:message
delegate:nil
cancelButtonTitle:@"ok"
otherButtonTitles:nil];
[alert show];
}
来源:https://stackoverflow.com/questions/20037834/ocmock-partial-mocking-uialertview-alloc