18

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.
MarkPowell
  • 16,482
  • 7
  • 61
  • 77
  • Have you overridden `- detailItem` in such a way that it might be returning different results when you call it a second time? – Jesse Rusak Aug 28 '12 at 13:15
  • Could you show the code where you assign `self.detailItem`? It's important that it's an instance, and not the class object. – uvesten Aug 28 '12 at 13:15
  • What is the superclass of "MovieInfo"? – Moshe Aug 28 '12 at 13:17
  • MovieInfo is an NSObject – MarkPowell Aug 28 '12 at 13:25
  • how do you know the `isKindOfClass` is returning `NO`? it seems you didn't check the return value anywhere... you just double and triple check the `class` and you log them many times but you haven't checked anywhere the return value of the `isKindOfClass`... can you log the return value, perhaps? – holex Aug 28 '12 at 13:54
  • it's in an if statement that doesn't execute and it's in an NSParameterAssert that throws an exception. That means it is returning NO. – MarkPowell Aug 28 '12 at 13:56
  • @holex just to verify I put: if (![newDetailItem isKindOfClass:[MovieInfo class]]) { NSLog(@"%@ is not a MovieInfo", newDetailItem); } this is printing out: is not a MovieInfo – MarkPowell Aug 28 '12 at 14:04
  • how is it possible a `MovieInfo` class (the `newDetailedItem`) is not kind of a `MovieInfo` class (as the string representation of the class says it is)? have you override the `isKindOfClass` method? have your tried to rename your `MovieInfo` class i.e. to `MPMovieInfo`? why not? :) is there any chance for two different `MovieInfo` classes are exists in the scope? – holex Aug 28 '12 at 14:41
  • I answered the question below (can't accept for 2 days). Was an issue with targets. – MarkPowell Aug 28 '12 at 15:20

10 Answers10

16

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.

MarkPowell
  • 16,482
  • 7
  • 61
  • 77
2

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I have a NSLog(@"%@", self.detailItem) just before the isKindOfClass call and it display MovieInfo as well. – MarkPowell Aug 28 '12 at 13:26
  • I'm not explicitly using threads in the unit test, so not sure where any race conditions would come from. – MarkPowell Aug 28 '12 at 13:34
  • Switched to printf: printf("%s", [[newDetailItem description] UTF8String]); And that is printing out MovieInfo as well. – MarkPowell Aug 28 '12 at 13:57
2

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!

Taz
  • 1,203
  • 1
  • 11
  • 21
  • At last, a solid answer. This answer should be more highly rated since it explains all the discrepancies on this page: **Q1:** Why does `[self.detailItem class] == [ETTWallpaper class]` work when `[self.detailItem] isKindOfClass:[ETTWallpaper class]]` doesn't? **A**: because the comparison of class relies on the .m or .h signatures which are different to a comparison of class name **Q2:** Why does removing the .m file for the class under test work, as well as adding the .m file? **A**: Probably because the .m file includes a reference to the required .h file, or some linkage provides it. – head in the codes Jul 26 '18 at 23:10
1

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

Barbara R
  • 1,057
  • 5
  • 18
  • 5
    `isKindOfClass:` compares an object to a `Class`, not `Class` to `Class`. This should work for you: `[self.detailItem isKindOfClass:[ETTWallpaper class]]` – Aviel Gross May 07 '14 at 13:41
1

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

stefreak
  • 1,460
  • 12
  • 30
1

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.

ThomasW
  • 16,981
  • 4
  • 79
  • 106
0

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

Sixten Otto
  • 14,816
  • 3
  • 48
  • 60
0

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.

igorsales
  • 612
  • 6
  • 8
0

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

Ryan
  • 815
  • 8
  • 14
0

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!

Community
  • 1
  • 1
david72
  • 7,151
  • 3
  • 37
  • 59