isMemberOfClass returns no when ViewController is instantiated from UIStoryboard
Asked Answered
S

2

4

I have an OCUnit Test class: PatientTestViewControllerTests. Below is the interface:

@interface PatientTestViewControllerTests : SenTestCase

@property (nonatomic, strong) PatientTestViewController *testController;

@end

and setUp:

- (void) setUp
{    
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Testing" bundle:nil];
    self.testController = [storyboard instantiateInitialViewController];
}

The 'Testing' storyboard is the only storyboard in my app, and is set as the app's main storyboard. The PatientTestViewController is set as the storyboard's only view controller.

I have one test in my test class:

- (void) testInitialTestingStoryboardViewIsPatientTest
{
    STAssertTrue([self.testController isMemberOfClass:[PatientTestViewController class]], @"Instead of the %@, we have %@",[PatientTestViewController class], [self.testController class]);
}

This test fails with the following log message:

error: -[PatientTestViewControllerTests testInitialTestingStoryboardViewIsPatientTest] : "[self.testController isMemberOfClass:[PatientTestViewController class]]" should be true. Instead of the PatientTestViewController, we have PatientTestViewController

How can this be? Since

[self.testController isMemberOfClass:[PatientTestViewController class]]

is apparently false, how can the test log say that both

[self.testController class] and [PatientTestViewController class]

look the same?

Additional Info:

  • using [self.testController isKindOfClass:[PatientTestViewController class]] in the test also fails
  • using [self.testController class] == [PatientTestViewController class] fails also.

  • using [self.testController isKindOfClass:[UIViewController class]] PASSES.

  • using [self.testController isMemberOfClass:[UIViewController class]] FAILS.
Stormproof answered 26/7, 2012 at 17:59 Comment(6)
Out of curiousity, what does NSLog(@"Expected address: %p, actual: %p",[PatientTestViewController class], [self.testController class]); look like?Mellifluous
@JoshCaswell Aha. Interesting question! And an interesting answer. "Expected address: 0x679fa48, actual: 0x59bc" So... what does that mean? Obviously they're different memory addresses... but what does isMemberOfClass test against? Is it strict object equality, like '=='?Stormproof
@JoshCaswell Interestingly enough, if I instantiate the testController object in setUp using a standard alloc/init, the test passes with no problems, and the two addresses are identical. So why does [UIStoryboard instantiateInitialViewController] return something different.. and how is it different?Stormproof
I'm not sure how isMemberOfClass: checks equality internally. I kind of suspect UIStoryboard has created a dummy stand-in class for some reason. Possibly some sort of laziness. How about this: what happens if you send a message to the testController, then do isMemberOfClass:, isKindOfClass:, and print the address?Mellifluous
I'm thinking that you may be on to something here. Sending a message to the testController didn't change anything (still the same discrepancy in addresses). However, I created a new project to replicate the problem, and I can't replicate it. The only real difference between the two projects is that I was fiddling around with the Storyboards in my original. I must have done... something.Stormproof
@JoshCaswell Thanks for your help. The insight about the memory addresses was apparently key, it was because I had included the .m file of the view controller in both the app target and the test target. I'm used to GHUnit where you have to do that.Stormproof
S
9

The problem is likely that your view controller's .m file is included in both targets, the app and the test bundle. ocunit (and derivatives like Kiwi) uses a test harness that makes the classes included in the app available to tests without having to explicitly include their implementation.

Including both has given you two copies of the same class, which is why they have the same description but different memory addresses.

Seessel answered 26/7, 2012 at 20:6 Comment(4)
Dumbfounded. That's exactly it. Removed the .m file from the Test target, and blammo. Everything passes. I'm used to GHUnit, where you have to include the .m file in both targets.Stormproof
Thanks - you've just resolved HOURS of head-scratching and nasty creating-NSStrings-from-class-names workarounds!Bensky
Save my day!!! Thank you point this out. I add my model file in my test project, but it's don't needed.Flamsteed
To tack on to this answer, after you've removed the source files from the test target, you may need to set up the symbols to unhide themselves.Quarrelsome
C
3

You generally want isKindOfClass: and not isMemberOfClass:. The difference is that isKindOfClass: will return YES if the receiver is a member of a subclass of the class in question, whereas isMemberOfClass: will return NO in the same case.

You could also directly compare the classes using [self.testController class] == [PatientTestViewController class].

Craftsman answered 26/7, 2012 at 18:24 Comment(1)
Thanks for the reply. I wanted the test to be specific, that's why I used isMemberOfClass. The initial view of the storyboard should be PatientTestViewController exactly, not a subclass. However, I did actually try isKindOfClass as well, just to be sure. No dice. I like the idea of the '==' test, but it doesn't work either. :( I'll put some additional info in the question with what else I've tried.Stormproof

© 2022 - 2024 — McMap. All rights reserved.