cannot stub class method with OCMock 2.1+ in Xcode 5.0 - ios

I know that OCMock version 2.1+ supports stubbing class methods out of the box. But for some reason it's not working with me. To make sure I isolated the problem, I simply cloned the example OCMock project (which is clearly marked as version 2.2.1) and simply added this inside testMasterViewControllerDeletesItemsFromTableView:
id detailViewMock = [OCMockObject mockForClass:[DetailViewController class]];
[[[detailViewMock stub] andReturn:#"hello"] helloWorld];
in DetailViewController.h I added:
+ (NSString *)helloWorld;
and DetailViewController.m:
+ (NSString *)helloWorld {
return #"hello world";
}
But I keep on getting the error:
*** -[NSProxy doesNotRecognize Selector:helloWorld] called!
to see a demo of the problem please clone this repo to see what's going on.

That should work just fine. I just tried in a project of mine which uses XCTest on Xcode5, and that code passed.
I would 1) make sure you are using the latest version of OCMock (which is 2.2.1 right now; I think there are some fixes for both class methods and Xcode5 in the newer versions), and 2) make sure your DetailViewController class is linked in the runtime (i.e. part of the correct target) correctly.
In looking at your project, your DetailViewController class is part of both the main application, and the test target. With Xcode5, it appears this means that two copies of the class get compiled and are present in the runtime, with code in the app calling one copy, and code in the test case calling the other. This used to be a linker error (duplicate symbols), but for better or worse, the linker now appears to silently allow two copies of the same class (with the same name) to exist in the ObjC runtime. OCMock, using dynamic lookup, finds the first one (the one compiled into the app), but the test case is directly linked to the second copy (the one compiled into the test bundle). So... OCMock is not actually mocking the class you think it is.
You can see this, just for grins, by verifying as part of the test case that [DetailViewController class] will not equal NSClassFromString(#"DetailViewController") (the first is directly linked, the second is dynamic).
To fix this properly, in the "Target Memberships" for DetailViewController.m, just uncheck the test target. This way there is only one copy of the class in the runtime, and things work like you'd expect. The test bundle gets loaded into the main application, so all of the main application's classes should be available to the bundle without having to directly compile them into the bundle. Classes should only be part of one of the two targets, not both (this has always been the case).

Could you show the code you are testing?
This works:
#interface DetailViewController : UIViewController
+ (NSString *) helloWorld;
#end
#implementation DetailViewController
+ (NSString *)helloWorld
{
return #"hello world";
}
#end
The test:
- (void) test__stubbing_a_class_method
{
id mockDetailViewController = [OCMockObject mockForClass:[DetailViewController class]];
[[[mockDetailViewController stub] andReturn:#"hello"] helloWorld];
STAssertEqualObjects([DetailViewController helloWorld], #"hello", nil);
}

Looking at your sample project:
You should not be compiling DetailViewController.m in your test target.
You should not have any references to OCMock in your primary target.
I removed all reference to OCMock from both projects, then just included OCMock from source and the test passes just fine. I think you probably just have some environmental conflicts that are causing your problem.

Although Carl Lindberg's answer is the correct one, I figured i'd summarize what we discussed in his answer's comments here:
The problem was simply that I was using an out dated version of
OCMock. The reason I got there was b/c the instructions on the
ocmock page simply referred me to grab the
iOS example off their github account and copy over the OCMock
library (They even instructed to use the same directory structure).
It turns out that the library in their example is over 2 years
old!!.
To remedy that, simply run the build.rb script on shell like so:
ruby build.rb. This will give you an up to date libOCMock.a library, which you can simply plug back in to your project, and Voila! it's all done!

just use
id detailViewMock = [OCMockObject niceMockForClass:[DetailViewController class]];

Related

iOS Xcode: Compiler error accessing Ivar in Objective-C

I am working in a large existing objective-c codebase, writing unit tests at the moment. The project uses instance variables widely.
I wrote a little method to grab the ivar for something. It worked in another unit test in the same project, but isn't working in this case.
Code:
-(id)getObjectForIvarNamed:(NSString *)ivarNameString
{
const char *ivarName = [ivarNameString UTF8String];
Ivar ivarValue = class_getInstanceVariable([textFieldOverlay class], ivarName);
id objectAtIvar = object_getIvar(textFieldOverlay, ivarValue);
return objectAtIvar;
}
The compiler complains at the line starting Ivar ivarValue with the following error:
"Declaration of 'Ivar' must be imported from module 'ObjectiveC.runtime' before it is required"
A web search for this error code comes up with zero hits. Not sure why it works in another file, the headers all look the same between the two XCUnitTest classes.
Never fails, found the answer as soon as I had typed the question. But, I hadn't posted yet. Because this error was impossible to find in a web search, I figured I should post it, so there is an instance out there. The project's class I was testing in the working test class imports the objective-c runtime header, I thought to check the tested class after I typed the question up. I thought it was a header issue but didn't see it in the imported class. Learning moment.
To import the objective-c runtime header and eradicate this warning, add #import <objc/runtime.h> to the header. Done, and done.

iOS Code Coverage: #implementation Not Hit?

I've recently added test coverage analysis to our codebase, and thankfully the classes that I expected to be well covered are clocking in above 95% coverage. I love the ones that hit 100%, since that's obviously the goal, but a handful of my classes are stuck at 96-98%, because one line isn't being hit. That one line is the #implementation ClassName line, which I find really confusing.
Logically if all of the class's executable lines were exercised, the class was obviously instantiated.
I'm using the standard Xcode methods (__gcov_flush()) for generating .gcda and .gcno usage data, and I'm using CoverStory for the analysis and HTML generation.
It's not a huge deal; obviously the class is well covered, but it's annoying to keep mental notes of the classes that aren't at 100% because the dang #implementation line is missed for whatever reason.
I can't find a pattern between the files that have the #implementation line hit and those that have it missed. Has anyone else experienced this and/or know what I might try to alleviate it? Could it even be a bug with CoverStory perhaps?
I've had the same problem with my current project and you can fix it using:
Edit: For XCText, it should be XCTAssertEqualObjects since NSString * are objects
Kiwi:
[NSStringFromClass([[YourClass new] class]) should] equal:NSStringFromClass[YourClass class])]
or
XCTAssertEqualObjects(NSStringFromClass([[YourClass new] class]), NSStringFromClass([YourClass class]));
#implementation YourClass will hit while an instance of that class going to be dealloc'ed.
You can confirm that by create a empty demo class, put a checkpoint on #implementation DemoClass, and call [DemoClass new], the debugger will stop while [DemoClass .cxx_destruct] is being called, which means the instance has zero retainCount and going to be dealloc'ed.
You can also confirm that by view the code coverage html file created by XcodeCoverage; select a file, click "function" which will list all functions coverage report for that file, and you will see [DemoClass .cxx_destruct] list as a function in either green or red.
In other word, If #implementation YourClass is not being called, there is a good chance that you had retain cycles/static variables in your class implementation that prevent your class from deallocating. (e.g. contains NSURLSession object but forget to call finishTaskAndInvalidate, or contains repeat NSTimer but didn't call invalidate , or it is singleton class which contains static variables etc.)

MR_SHORTHAND does not work

I use MagicalRecords but by some reason MR_SHORTHAND does not work for me.
I have added these lines below to Prefix.pch
#define MR_SHORTHAND
#import "CoreData+MagicalRecord.h"
In case when app calls + createEntity method the app terminates due unrecognized selector. But if I write MR_createEntity everything works fine.
Team *team = [Team MR_createEntity]; // This line works
but
Team *team = [Team createEntity]; // This line does not work
with reason: '+[Team createEntity]: unrecognized selector sent to class 0x74b8c'
I know we haven't announced it publicly prior to this answer, but we've decided to deprecate shorthand support, and remove it in the upcoming MR3 release.
Shorthand support is an interesting idea and feature, but it is not something we want to maintain. We would rather focus our efforts on getting the data centric part of MagicalRecord to be as solid and reusable as possible.
That said, you're certainly welcome to fix it yourself and submit a patch to the older branch of MagicalRecord. Some things to look at are:
Make sure the shorthand selectors are included, and add new ones if they are not present
Make sure you've used the MagicalRecord class once prior to this call. All the shorthand support is initialized in the class initializer method.
If you're using Cocoapods, you may need to update your pod spec reference to 'MagicalRecord/Shorthand'
According to the official guide https://github.com/magicalpanda/MagicalRecord/blob/master/Docs/Installing-MagicalRecord.md
You have to call
[MagicalRecord enableShorthandMethods];
the above error means the core data stack is not initialized yet.
you should add [MagicalRecord setupCoreDataStackWithStoreNamed:dbName]; in app delegate before [self.window makeKeyAndVisible];
because if you have called [Team createEntity]; in your root VC [self.window makeKeyAndVisible]; make the view be loaded and visible, but still app has not reached the next line to setup core data stack

using #implementation creates "instance method conflict" warnings in XCode

In my iOS application I have a 5 view controllers that all deal with the same feature (groups). These view controllers can be pushed on top of eachother in a few different configurations. I made a file called GroupViewHelper.h which uses #implementation to provide some functions for the groups feature. The functions look through the view controller stack and send a "refresh" message to a view controller of a specific type. The file looks like this:
#implementation UIViewController (GroupViewHelper)
- (void) refreshManageGroupsParent
{
// ...
}
- (void) refreshGroupDetailsParent
{
// ...
}
#end
My code works great and everything behaves as expected, but I get 14 warnings that are all very similar to this at build time:
ld: warning: instance method 'refreshGroupDetailsParent' in category from /Users/x/Library/Developer/Xcode/DerivedData/myapp-ayshzmsyeabbgqbbnbiixjhdmqgs/Build/Intermediates/myapp.build/Debug-iphonesimulator/myapp-dev.build/Objects-normal/i386/GroupMembersController.o conflicts with same method from another category
I think I'm getting this because I'm using a .H which is included in multiple places, but how do I correctly use #implementation in this situation?
I think I'm getting this because I'm using a .H which is included in multiple places
Well, sort of, but the real problem is that you've put the #implementation in the .h file in the first place. If you only included that .h file in one place, you would get away with it—but it would still not be the right way to do it.
but how do I correctly use #implementation in this situation?
Put it in a file called GroupViewHelper.m, and add that file to your project's sources, and put the #interface in GroupViewHelper.h.
Or, ideally, call them UIViewController+GroupViewHelper.m and UIViewController+GroupViewHelper.h, because that's the idiomatic way to name category files. (And if you use Xcode's "New File…" menu item to create a new Objective-C category file, that's what it will give you.)
In other words, interfaces and implementations for categories on existing classes work exactly the same as interfaces and implementations for new classes.
I have encountered exactly this issue. I had imported a reference to a header file, on a .m page. However, it also contained a reference to another header file, which contained a reference to another header file - that also referenced the conflicted header file. So indirectly the same header file was imported twice, causing the error.
In my case, the .m file did not need this reference. I was able to delete it, removing the error. My advice is check the files where you have included a reference to the offending header file, and verify that it actually is required.

Why are my NSManagedObject subclass methods not being recognized?

I've (heavily) subclassed NSManagedObject for a project. It worked in the original project seamlessly and without any effort.
I copied those files over to a new project, manually adding the appropriate CoreData classes to the new data model.
Unfortunately, I'm having 'issues'. For some reason, the methods of the subclass in question are being ignored. The exact same code between the two projects, but I'm suddenly getting an unrecognized selector issue.
NSFetchRequest *blockRequest=[[NSFetchRequest alloc] init];
NSEntityDescription *blockDesc=[NSEntityDescription entityForName:#"AdBlock"
inManagedObjectContext:context];
[blockRequest setEntity:blockDesc];
AdBlock *curBlock=[adBlocks objectAtIndex:blockIndex];
adBlocks=[context executeFetchRequest:blockRequest error:nil];
for (AdBlock *block in adBlocks) {
[block initAdBlock];//Crashes with unrecognized selector
}
I've checked, and the appropriate .m files got added to the compiler build phase. The code was quite literally copy&paste and is identical between the two projects -- source works, destination doesn't.
I've noticed that I don't explicitly tell the context that it should return the subclass type, but that wasn't an issue in the old project, so why should it be an issue in the new one?
When something like this happens to me, there's two things I try. The first thing I do is make sure I properly #imported the NSManagedObject subclass (I forget that way more often than I should), but as you said you copied and pasted, I don't think that is your problem. The second thing I try is to rebuild the NSManagedObjects by going to File>New>File then NSManagedObject Subclass then selecting the NSManagedObjects that I changed something in. I would recommend rebuilding all of them in your case. See if that works. It may not, but it's an easy place to start.
Make sure you check your model and ensure that you've change the class names in the inspector. Otherwise, they'll return as NSManagedObjects no matter what. And use mogenerator so you don't have to worry about regenerating your classes.
I know this question is stale, but maybe someone searching will find this helpful.
I've been using categories to add additional functions to NSManagedObject subclasses. This allows me to use the XCode command to generate the class definition without destroying any of the custom logic. Ron mentions this in a comment on the selected answer - just thought it would be worth calling attention to as I find it's a pretty slick solution.

Resources