4

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.

abbood
  • 23,101
  • 16
  • 132
  • 246
  • This looks like it should work... I presume DetailViewController.h is being included in your test? Can you call "helloWorld" without using mocks? – Ben Flynn Oct 10 '13 at 19:27
  • yes `DetailViewController.h` is included in the test, and replaced the Class method stub with a regular stub (same hello world signature etc).. and it didn't work.. – abbood Oct 10 '13 at 22:44
  • @BenFlynn I just created a very simple [repo](https://github.com/abbood/very-simple-oc-mock-stub-example/blob/master/verySimpleOCMockStubExampleTests/otherTests.m) to demo the problem I'm getting.. if you can take a quick peek that would be great! – abbood Oct 12 '13 at 03:40

5 Answers5

4

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).

Carl Lindberg
  • 2,902
  • 18
  • 22
  • After such a long answer I must ask: did you try your solution on the repo i provided? I just double checked and ensured that `DetailViewController` is indeed part of the app target itself, and *not* part of the tests target.. I also checked if [DetailViewcontroller class] is equal to NSClassFromString(@"DetailViewController").. and sure enough they are (they both return strings anyways.. which don't say much) in short.. your answer didn't work – abbood Oct 13 '13 at 17:31
  • Yes, I downloaded your project. DetailViewController was part of both targets; once I removed it from the test target the test passed. I do have a newer OCMock installed though. As for equals, I meant that [DetailViewController class] (when called from the test case) will not return the same pointer as NSClassFromString(@"DetailViewController") -- if you compare them with "==" they are not the same in your project, whereas normally they should be. – Carl Lindberg Oct 13 '13 at 23:30
  • Even looking at your screenshot above, I see "Compile Sources (2 items)" when the Test target is selected -- one is your otherTests.m file; the second one is DetailViewController.m. There should only be one item in that screenshot; DetailViewController should only show up in the "Compile Sources" area when the application target is selected. – Carl Lindberg Oct 13 '13 at 23:47
  • `DetailViewController.m` [isn't part](http://s21.postimg.org/fpf1ww34n/Screen_Shot_2013_10_14_at_5_36_35_AM.png) of the 2 items under my test target.. but I do agree with you that the library i'm using is not up to date.. i simply cloned the example on the ocmock github account and *assumed* it was up to date.. I was reviewing it and the lib [turned out to be](https://github.com/erikdoe/ocmock/tree/master/Examples/iOS5Example/usr/lib) **2 years old**!! – abbood Oct 14 '13 at 02:42
  • But here is a question for you: I've searched on how to determine the version of the OCMock lib, and I couldn't find any solution.. **how did you know it was older than version 2.1**? Also, now that we agree it's a lib version problem.. will I have to go through the [hassle](http://www.blog.montgomerie.net/easy-xcode-static-library-subprojects-and-submodules) of including the ocmock project as a subproject of my original project and compiling them together etc etc? I can't I just copy the up to date lib from somewhere (or achieve the same thing in an easier way?) – abbood Oct 14 '13 at 02:44
  • **Finally!** problem solved.. it was all in the [build.rb](https://github.com/erikdoe/ocmock/blob/master/Tools/build.rb?source=c) build script.. just ran `ruby build.rb` from shell, and it gave me a nice and clean `libOCMock.a`.. removed the old one from my `usr/lib` directory and it worked perfect! credit will go to you though (if you can answer the question of how to know if what version libOCMock.a is though that would be great).. I'll summarize the gotchas i went through in my answer. – abbood Oct 14 '13 at 03:02
  • OK on your project -- I guess that did turn out to be an out-of-date library (which would not have implemented class method mocking at all). The test project you put on github did have the class-in-both-targets problem. As for which version, you generally need to go to the [OCMock downloads page](http://www.ocmock.org/download/), and get the latest version that way. It should come with libOCMock.a, which you can just drop into your project, and should show up in the "Link Binary with Libraries" section in the build phases. (Or you can build from source, of course.) – Carl Lindberg Oct 14 '13 at 03:26
  • 1
    As for determining which version you have... I don't think there is a version string embedded in there. But you can do `otool -av /path/to/libOCMock.a` to see the compile date of all of the archive members, which gives a pretty good clue. – Carl Lindberg Oct 14 '13 at 03:35
  • Carl, you just saved my sanity. I literally wasted hours trying to figure out why a simple class method mock wasn't working. Just wish I saw your answer sooner. :-P – stuckj Nov 28 '13 at 06:08
  • Also, one should note that if both your main application and your test library are linking the same static libraries the same duplicate symbol issue will occur. – Sandy Chapman Apr 15 '14 at 17:46
2

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);
}
e1985
  • 6,239
  • 1
  • 24
  • 39
  • I just created a very simple [repo](https://github.com/abbood/very-simple-oc-mock-stub-example/blob/master/verySimpleOCMockStubExampleTests/otherTests.m) to demo the problem I'm getting.. if you can take a quick look that would be great! – abbood Oct 12 '13 at 03:40
1

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!

Community
  • 1
  • 1
abbood
  • 23,101
  • 16
  • 132
  • 246
1

Looking at your sample project:

  1. You should not be compiling DetailViewController.m in your test target.

  2. 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.

Ben Flynn
  • 18,524
  • 20
  • 97
  • 142
0

just use

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