MarkPowell
MarkPowell

Reputation: 16540

isKindOfClass returning NO unexpectedly

One of my unit tests is failing and for a reason I didn't anticipate. It seems a call to isKindOfClass is returning NO, but when I debug and step through, there seems to be no reason it would.

The code is:

if ([self.detailItem isKindOfClass:[MovieInfo class]]) {
    [self configureViewForMovie];
}

I stepped through the code and did:

po self.detailItem

which displays:

(id) $1 = 0x0ea8f390 <MovieInfo: 0xea8f390>

So, what am I missing, why would the if statement return false in this case?

EDIT:

Here is the setter for the DetailItem:

- (void)setDetailItem:(id)newDetailItem
{
    if (_detailItem != newDetailItem) {
        NSLog(@"%@", [newDetailItem class]);
        _detailItem = newDetailItem;

        // Update the view.
        [self configureView];
    }

    if (self.masterPopoverController != nil) {
        [self.masterPopoverController dismissPopoverAnimated:YES];
    } 
}

It's template code from a Master Detail template.

The unit test creates a MovieInfo in setUp:

movie = [[MovieInfo alloc] initWithMovieName:@"Movie" movieID:1];

and sets it in the test

controller.detailItem = movie;

Additionally, I added a parameter assertion in setDetailItem:

NSParameterAssert([newDetailItem isKindOfClass:[MovieInfo class]] || [newDetailItem isKindOfClass:[PersonInfo class]] || newDetailItem == nil);

This assertion is failing as well.

I've put two log statements above the assertion call:

NSLog(@"%@", [newDetailItem class]);
NSLog(@"%@", newDetailItem);

which display:

2012-08-28 08:31:37.574 Popcorn[8006:c07] MovieInfo
2012-08-28 08:31:38.253 Popcorn[8006:c07] <MovieInfo: 0x6daac50>

ONE MORE EDIT:

I added the isKindOfClass check before setting it in the unit test, that one passes.

if ([movie isKindOfClass:[MovieInfo class]]) {
    NSLog(@"Yep"); //This passes and prints out
}
controller.detailItem = movie; //calls into the setter and fails.

Upvotes: 18

Views: 4975

Answers (10)

david72
david72

Reputation: 7297

Found a solution in this link :

My SomeEntity class was included in the test target. Building the test target also includes the main application as a dependency, which also includes SomeEntity. That apparently makes Xcode believe there are 2 different types.

Remove SomeEntity from the test target and everything passes!

Upvotes: 0

ThomasW
ThomasW

Reputation: 17317

As mentioned by @stefreak, in the standard unit testing configuration, the object under test should not be included in the unit test target. In this situation, if you do include the object under test in the test target the following type of message gets written to the log when building:

Class foo is implemented in both <.app path> and <.xctest path>. One of the two will be used. Which one is undefined.

As mentioned here you shouldn't include the object under test in both targets. Having the object in only one target will stop the unexpected behavior with isKindOfClass:.

Also, a co-worker of mine found that you need to ensure that unit tests are turned off for "Run" builds. Under the scheme for the application, select "Build", then look at the unit test target, "Run" should be deselected.

If you're developing in Swift, then you should have a @testable import MyModule at the top of your unit test file.

Upvotes: 1

Ryan
Ryan

Reputation: 815

I have the same problem when running unit test with CocoaPods 1.0 (where SomeClass is inside a Pod library)

I got the following warning message:

Class SomeClass is implemented in both </path/to/myapp> and </path/to/myapptest>. One of the two will be used. Which one is undefined.

My solution is to update the Podfile to:

target "MyApp" do
  pod 'xxx'
  pod 'yyy'

  target "MyApp-Tests" do
      inherit! :search_paths
  end
end

References: https://github.com/CocoaPods/CocoaPods/issues/4626

Upvotes: 0

igorsales
igorsales

Reputation: 622

I think there currently is a bug in Xcode 7.0. I have the same issue, and checked all CUTs are included in the test target, but I have one that in code returns NO to -[isKindOfClass:], but in the debugger returns YES. And this is happening in simulators and physical devices.

I ended up checking -[respondsToSelector:] to check my class' signature. Not perfect, but allows me to plow through.

Upvotes: 0

stefreak
stefreak

Reputation: 1450

MarkPowell's answer does help absolutely, if you want to always add all of your source files to all of your test targets.

However, if you have your Application as target dependency of your test target (if you are having only the test source files in your test target, not the project source files), then you have the same problem I had: One of your classes should be in the App target and NOT the test target (in my case it was a test helper class!)

Upvotes: 1

Taz
Taz

Reputation: 1223

I had this same issue in a library that I am writing. Funnily enough, it worked fine on my iOS test target, but failed on the Mac test target (interesting!).

I believe the issue is related to a mismatch in the class declarations. The library is using the .h declaration but the unit tests were looking at the internal declarations.

It's easier to explain with an example:

/*
 * ClassA.h
 */
@interface ClassA () : NSObject

@property (nonatomic, strong, readonly) NSString *someValue1;
@property (nonatomic, strong, readonly) NSString *someValue2;
@property (nonatomic, strong, readonly) NSString *someValue3;

@end

Adding to the property list in the .m

/*
 * ClassA.m
 */
@interface ClassA () : NSObject

@property (nonatomic, strong, readwrite) NSString *someValue2;
@property (nonatomic, strong, readwrite) NSDate *date;
@property (nonatomic, strong, readwrite) NSDateFormatter *dateFormatter;

@end

@implementation 
// implementation
@end

Now, since the unit test targets had the compile sources set to include ClassA.m, the isKindOfClass: would return no, but po command and NSStringFromClass([ClassA class]) when run in the debugger would return values that you expect.

I know this is an old post, but I hope this is useful and saves someone a lot of time. Took me almost an hour trying to figure this issue out!

Upvotes: 2

Barbara R
Barbara R

Reputation: 1057

Same situation is happening to me. I ended up doing the comparison as follow:

[self.detailItem class]  == [ETTWallpaper class]

Since

[[self.detailItem class] isKindOfClass:[ETTWallpaper class]]

Was not working, and always returned NO, despite being sure that it had to return YES

Upvotes: 1

MarkPowell
MarkPowell

Reputation: 16540

This was due to the fact that the class under test "DetailViewController" was not in the test's target. I would have expected this to manifest itself in a different way (linker error or something), but apparently, it just causes odd behavior. Adding DetailViewController to the test target fixed the issues.

Upvotes: 15

Sixten Otto
Sixten Otto

Reputation: 14816

Can self.detailItem ever be nil? The result of -isKindOfClass: would be NO in that case.

Upvotes: 0

Rob Napier
Rob Napier

Reputation: 299275

I would suspect a race condition, or a different between Debug and Release configurations. These will lead to differences between the debugger and regular runtime.

First, make sure that self.detailItem isn't nil. That's the most common cause of this kind of issue. Then try debugging this with logging rather than the debugger. If you really do have race conditions, then you may also consider logging with printf() rather than NSLog(). printf() is a much less performance-impacting way to do multi-threaded logging.

Upvotes: 2

Related Questions