1

I want to verify with unit tests that all the IBoutlets in my controller class are correctly hooked up in the NIB file. I'd like to do this with OCMock - even though I know I could simply assert the controllers variables are not nil after loading the NIB. This is more a matter of general understanding of how the process works - as far as I understand it, this should be working, too.

The NIB OnOffSwitchCell has as its File's Owner OnOffSwitchCellController. This is my test method:

- (void) testIBOutletCellIsWiredToXib {
    id mockController = [OCMockObject mockForClass:[OnOffSwitchCellController class]];
    [[mockController expect] awakeAfterUsingCoder:OCMOCK_ANY];
    [[mockController expect] setValue:OCMOCK_ANY forKey:@"cell"];
    [[mockController expect] setValue:OCMOCK_ANY forKey:@"thelabel"];
    [[mockController expect] setValue:OCMOCK_ANY forKey:@"theswitch"];

    NSArray* nibContents = [guiBundle loadNibNamed:@"OnOffSwitchCell"
                                             owner:mockController
                                           options:nil];
    assertThat(nibContents, isNot(nil));
    assertThatInt([nibContents count], is(equalToInt(1)));
    assertThat([nibContents objectAtIndex:0], is(instanceOf([OnOffSwitchCell class])));

    [mockController verify];
}

guiBundle exists and is verified to be a valid NSBundle object.

From what I understand loadNibNamed:owner:options: will deserialize the objects in the NIB, call awakeAfterUsingCoder: and then set the outlets by calling setValue:forKey: for each one.

I put in three more asserts to make sure the loaded NIB actually contains the correct objects - these pass OK when I put in an instance of the real controller. But when I use the mock as shown above, it does not even get this far. Instead, the test crashes with this:

 Test Case '-[OnOffSwitchCellControllerTestCase testIBOutletCellIsWiredToXib]' started.
 2011-01-14 10:48:35.364 GTMTest[67797:903] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
     reason: 'OCMockObject[OnOffSwitchCellController]:
              unexpected method invoked: awakeAfterUsingCoder:<UINibDecoder: 0x500e800> 
    expected:   setValue:<OCMAnyConstraint: 0x4c718e0> forKey:@"cell"
    expected:   setValue:<OCMAnyConstraint: 0x4c71ce0> forKey:@"thelabel"
    expected:   setValue:<OCMAnyConstraint: 0x4c71ed0> forKey:@"theswitch"'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x00e3dbe9 __exceptionPreprocess + 185
    1   libobjc.A.dylib                     0x00f925c2 objc_exception_throw + 47
    2   CoreFoundation                      0x00e3db21 -[NSException raise] + 17
    3   GTMTest                             0x0001a049 -[OCMockObject handleUnRecordedInvocation:] + 322
    4   GTMTest                             0x00019aca -[OCMockObject forwardInvocation:] + 77
    5   CoreFoundation                      0x00daf404 ___forwarding___ + 1124
    6   CoreFoundation                      0x00daef22 _CF_forwarding_prep_0 + 50
    7   UIKit                               0x0062394a UINibDecoderDecodeObjectForValue + 2438
    8   UIKit                               0x00624693 -[UINibDecoder decodeObjectForKey:] + 398
    9   UIKit                               0x0053cf43 -[UIRuntimeConnection initWithCoder:] + 212
    10  UIKit                               0x0053d4b1 -[UIRuntimeEventConnection initWithCoder:] + 64
    11  UIKit                               0x006239e4 UINibDecoderDecodeObjectForValue + 2592
    12  UIKit                               0x006232dc UINibDecoderDecodeObjectForValue + 792
    13  UIKit                               0x00624693 -[UINibDecoder decodeObjectForKey:] + 398
    14  UIKit                               0x0053c200 -[UINib instantiateWithOwner:options:] + 804
    15  UIKit                               0x0053e081 -[NSBundle(UINSBundleAdditions) loadNibNamed:owner:options:] + 168
    16  GTMTest                             0x000140dc -[OnOffSwitchCellControllerTestCase testIBOutletCellIsWiredToXib] + 503
    17  GTMTest                             0x000041f3 -[SenTestCase invokeTest] + 163
    18  GTMTest                             0x0000479a -[GTMTestCase invokeTest] + 146
    19  GTMTest                             0x00003e90 -[SenTestCase performTest] + 37
    20  GTMTest                             0x00002f3d -[GTMIPhoneUnitTestDelegate runTests] + 1413
    21  GTMTest                             0x000028fb -[GTMIPhoneUnitTestDelegate applicationDidFinishLaunching:] + 197
    22  UIKit                               0x00347253 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1252
    23  UIKit                               0x0034955e -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 439
    24  UIKit                               0x00348ef0 -[UIApplication _run] + 452
    25  UIKit                               0x0035542e UIApplicationMain + 1160
    26  GTMTest                             0x00003500 main + 104
    27  GTMTest                             0x0000273d start + 53
    28  ???                                 0x00000002 0x0 + 2
)
terminate called after throwing an instance of 'NSException'

So it is complaining the call to awakeAfterUsingCoder: as being unexpected, even though I clearly expected it.

I also tried removing that expectation and replacing the mock with a nice mock that will not report superfluous method calls, but then it still aborts and reports the setValue:forKey: not being called.

What am I missing here?

Daniel Schneller
  • 13,728
  • 5
  • 43
  • 72

2 Answers2

0

Have you tried running this on the main system thread? You cannot instance UIKit classes off the main thread. Not sure how GTM does it, but with GHUnit you can put the following into your test case class:

- (BOOL)shouldRunOnMainThread {
    /* Tell GHUnit to run on a separate thread */
    return YES;
}
matyjas
  • 578
  • 5
  • 4
-1

you cant actually do any visual stuff in unit tests. The views are never actually loaded. Also, you dont need to test that awakeAfterUsingCoder is invoked. Thats Apple's stuff. Typically your unit tests must only test your logic. You can safely assume that awakeAfterUsingCoder is invoked from init, because it's Apple's code. You just need to make sure your methods are invoked

Zayin Krige
  • 3,229
  • 1
  • 35
  • 34