質問

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:

  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.

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]];
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top