检查 IBOutlet 与 OCMock 的连接
我想通过单元测试验证控制器类中的所有 IBoutlet 是否已正确连接到 NIB 文件中。我想用 OCMock 来做到这一点 - 尽管我知道我可以在加载 NIB 后简单地断言控制器变量不是 nil
。这更多的是对这个过程如何工作的一般理解的问题——据我了解,这也应该有效。
NIB OnOffSwitchCell
的文件所有者为 OnOffSwitchCellController
。 这是我的测试方法:
- (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 存在并且被验证为有效的 NSBundle 对象。
据我了解 loadNibNamed:owner:options:
将反序列化 NIB 中的对象,调用 awakeAfterUsingCoder:
然后通过调用 setValue:forKey:< /code> 每一个。
我又添加了三个断言,以确保加载的 NIB 实际上包含正确的对象 - 当我放入真实控制器的实例时,这些断言会通过。但是当我使用上面所示的模拟时,它甚至没有达到这么远。相反,测试崩溃了:
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'
因此,它抱怨对 awakeAfterUsingCoder:
的调用是意外的,尽管我显然预料到了。
我还尝试消除这种期望,并用一个不会报告多余方法调用的漂亮模拟替换模拟,但它仍然会中止并报告 setValue:forKey:
未被调用。
我在这里缺少什么?
I want to verify with unit tests that all the IBoutlet
s 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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
你实际上不能在单元测试中做任何视觉上的事情。视图从未真正加载。此外,您不需要测试
awakeAfterUsingCoder
是否被调用。那是苹果的东西。通常,您的单元测试必须只测试您的逻辑。您可以放心地假设awakeAfterUsingCoder
是从 init 调用的,因为它是 Apple 的代码。您只需要确保您的方法被调用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 thatawakeAfterUsingCoder
is invoked from init, because it's Apple's code. You just need to make sure your methods are invoked您是否尝试过在主系统线程上运行它?您无法在主线程之外实例化 UIKit 类。不确定 GTM 是如何做到的,但是使用 GHUnit,您可以将以下内容放入测试用例类中:
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: