{"id":205,"date":"2009-07-05T16:22:16","date_gmt":"2009-07-06T00:22:16","guid":{"rendered":"http:\/\/eschatologist.net\/blog\/?p=205"},"modified":"2009-07-05T16:24:55","modified_gmt":"2009-07-06T00:24:55","slug":"unit-testing-cocoa-user-interfaces-tips-tricks","status":"publish","type":"post","link":"https:\/\/eschatologist.net\/blog\/?p=205","title":{"rendered":"Unit testing Cocoa user interfaces: Use Check Methods"},"content":{"rendered":"<p>In the past, I&#8217;ve talked about ways to easily write unit tests for Cocoa applications, including [tests for user interfaces using target-action][1] and [tests for interfaces using Cocoa bindings][2].  There are some strategies you can apply to make writing tests for Cocoa code even easier, though.  They&#8217;re just straightforward object-oriented programming, but sometimes we can forget that all the techniques you might use in your main code base can also be applied to our test code.<\/p>\n<p>So here&#8217;s one trick that you can use in writing tests for Cocoa user interfaces, especially in ways that will make test-driven development easier.<\/p>\n<p>### Use a Common Base Class with Check Methods<\/p>\n<p>The first, probably most important thing to do is *use your own common base class* for your tests.  Don&#8217;t derive your tests directly from `SenTestCase`; instead, derive them from your own `MyTestCase` class that in turn derives from `SenTestCase`.  This gives you a place to put all of the customization that&#8217;s appropriate for your project.<\/p>\n<p>Sometimes you might need a series of assertions to verify some particular state.  However, that series of assertions will be the same every time you need to verify that state.  Or the assertions themselves aren&#8217;t very *intention revealing* so you always wind up putting a comment above them describing what they&#8217;re really doing.<\/p>\n<p>### Checking Target-Action<\/p>\n<p>A simple example of this is checking a target-action connection in a user interface.  Say you have a view controller that presents a collection of objects managed by an array controller.  Its view has an Add button that should send `-addObject:` to the array controller.  You might write a test for it like this:<\/p>\n<p>    &#8211; (void)testAddButtonSendsAddObjectToArrayController {<br \/>\n        STAssertEquals([viewController.addButton target], viewController.arrayController,<br \/>\n            @&#8221;The Add button should target the array controller.&#8221;);<br \/>\n        STAssertEquals([viewController.addButton action], @selector(addObject:),<br \/>\n            @&#8221;The Add button should send the -addObject: action.&#8221;);<br \/>\n    }<\/p>\n<p>That&#8217;s not too difficult to understand, but it could be made simpler \u00e2\u20ac\u201d it could be done in a single assertion.  You&#8217;d just write a method to check both the target and action at once and then use that method from your test, like this:<\/p>\n<p>    \/\/ in your test base class&#8230;<\/p>\n<p>    \/*! Tells whether the control sends the action to the target. *\/<br \/>\n    &#8211; (BOOL)checkControl:(NSControl *)control<br \/>\n             sendsAction:(SEL)action<br \/>\n                toTarget:(id)target<br \/>\n    {<br \/>\n        return ([control action] == action)<br \/>\n            &#038;&#038; ([control target] == target);<br \/>\n    }<\/p>\n<p>    \/\/ in the tests specifying the view controller&#8217;s behavior&#8230;<\/p>\n<p>    &#8211; (void)testAddButtonSendsAddObjectToArrayController {<br \/>\n        STAssertTrue([self checkControl:viewController.addButton<br \/>\n                            sendsAction:@selector(addObject:)<br \/>\n                               toTarget:viewController.arrayController],<br \/>\n            @&#8221;The Add button&#8217;s action should send -addObject: to the array controller.&#8221;);<br \/>\n    }<\/p>\n<p>That makes the intention behind the entire test a lot clearer, and it makes writing the test easier  &#038; safer since you can&#8217;t (say) forget to check either the target or the action.<\/p>\n<p>It *does* lose a tiny bit of information:  If the test fails, you&#8217;ll have to look at your xib file instead of the failure message to determine whether it&#8217;s because the target or the action isn&#8217;t set as you&#8217;ve specified.  However, the trade-off in making the test easier to read and write is worth it here.<\/p>\n<p>### Checking Outlets<\/p>\n<p>This is even worthwhile for single assertions, such as those you&#8217;d use to test that your outlets are connected in the first place.  For example, you might initially write a test that your view controller is your table view&#8217;s delegate like this:<\/p>\n<p>    &#8211; (void)testViewControllerIsTableViewDelegate {<br \/>\n        STAssertEquals([viewController.tableView delegate], viewController,<br \/>\n            @&#8221;The table view&#8217;s delegate should be the view controller.&#8221;);<br \/>\n    }<\/p>\n<p>Rewriting it to be more intention-revealing with a simple check method would make it look like this:<\/p>\n<p>    \/\/ in your test base class&#8230;<\/p>\n<p>    \/*! Tells whether the outlet is connected to the given destination. *\/<br \/>\n    &#8211; (BOOL)checkOutlet:(id)outlet connectsTo:(id)destination {<br \/>\n        return outlet == destination;<br \/>\n    }<\/p>\n<p>    \/\/ in the tests specifying the view controller&#8217;s behavior&#8230;<\/p>\n<p>    &#8211; (void)testViewControllerIsTableViewDelegate {<br \/>\n        STAssertTrue([self checkOutlet:[viewController.tableView delegate]<br \/>\n                            connectsTo:viewController],<br \/>\n            @&#8221;The table view&#8217;s delegate should be the view controller.&#8221;);<br \/>\n    }<\/p>\n<p>You&#8217;re not saving any code by writing your test this way \u00e2\u20ac\u201d you&#8217;re actually writing more \u00e2\u20ac\u201d but its *complexity* has gone down because it requires less effort to see what it&#8217;s actually trying to do.<\/p>\n<p>### Checking Bindings<\/p>\n<p>This is even worthwhile in situations where you may still need a few extra assertions.  For example, Cocoa bindings are specified using a lot more information than just outlets and target-acton connections; you won&#8217;t always want to check (and specify the value of) all of it, but you can easily make the common parts clearer.<\/p>\n<p>Going back to our Add button example, as is typical its enabled state should be bound to the array controller&#8217;s `canAdd` property.  Writing a test to specify this involves using `-infoForBinding:` and interpreting the results, which takes a couple lines of code and a couple of assertions:<\/p>\n<p>    &#8211; (void)testAddButtonEnabledStateIsBoundToArrayControllerCanAdd {<br \/>\n        NSDictionary *bindingInfo = [viewController.addButton infoForBinding:NSEnabledBinding];<br \/>\n        STAssertNotNil(bindingInfo,<br \/>\n            @&#8221;The Add button&#8217;s enabled state should be bound.&#8221;);<\/p>\n<p>        id observedObject = [bindingInfo objectForKey:NSObservedObjectKey];<br \/>\n        STAssertEquals(observedObject, viewController.arrayController,<br \/>\n            @&#8221;The Add button&#8217;s enabled state should be bound to the array controller.&#8221;);<\/p>\n<p>        NSString *observedKeyPath = [bindingInfo objectForKey:NSObservedKeyPathKey];<br \/>\n        STAssertEqualObjects(observedKeyPath, @&#8221;canAdd&#8221;,<br \/>\n            @&#8221;The Add button&#8217;s enabled state should be bound through the &#8216;canAdd&#8217; key path.&#8221;);<br \/>\n    }<\/p>\n<p>This isn&#8217;t too complicated, but it does start to get tedious, especially given that you have to remember to distinguish between `STAssertEquals` (pointer equality) and `STAssertEqualObjects` (object equivalence).  Let&#8217;s put the tedium in one place:<\/p>\n<p>    \/*! Tells whether the object&#8217;s binding is connected through the given key path. *\/<br \/>\n    &#8211; (BOOL)checkObject:(id)source<br \/>\n             hasBinding:(NSString *)binding<br \/>\n               toObject:(id)destination<br \/>\n                through:(NSString *)keyPath<br \/>\n    {<br \/>\n        NSDictionary *bindingInfo = ;<br \/>\n        id observedObject = [bindingInfo objectForKey:NSObservedObjectKey];<br \/>\n        NSString *observedKeyPath = [bindingInfo objectForKey:NSObservedKeyPathKey];<\/p>\n<p>        return (bindingInfo != nil)<br \/>\n            &#038;&#038; (observedObject == destination)<br \/>\n            &#038;&#038; [keyPath isEqualToString:observedKeyPath];<br \/>\n    }<\/p>\n<p>    \/\/ in the tests specifying the view controller&#8217;s behavior&#8230;<\/p>\n<p>    &#8211; (void)testAddButtonEnabledStateIsBoundToArrayControllerCanAdd {<br \/>\n        STAssertTrue([self checkObject:viewController.addButton<br \/>\n                            hasBinding:NSEnabledBinding<br \/>\n                              toObject:viewController.arrayController<br \/>\n                               through:@&#8221;canAdd&#8221;],<br \/>\n            @&#8221;The Add button&#8217;s enabled state should be bound to the array controller&#8217;s &#8216;canAdd&#8217; property.&#8221;);<br \/>\n    }<\/p>\n<p>Much clearer!<\/p>\n<p>[1]: https:\/\/eschatologist.net\/blog\/?p=10 &#8220;Unit testing Cocoa user interfaces: Target-Action&#8221;<br \/>\n[2]: https:\/\/eschatologist.net\/blog\/?p=12 &#8220;Unit testing Cocoa user interfaces: Cocoa Bindings&#8221;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the past, I&#8217;ve talked about ways to easily write unit tests for Cocoa applications, including [tests for user interfaces using target-action][1] and [tests for interfaces using Cocoa bindings][2]. There are some strategies you can apply to make writing tests for Cocoa code even easier, though. They&#8217;re just straightforward object-oriented programming, but sometimes we can&hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[3],"tags":[12,9,13,10,11,18,15,16,7,14,17],"class_list":["post-205","post","type-post","status-publish","format-standard","hentry","category-technology","tag-agile-development","tag-cocoa","tag-cocoa-bindings","tag-human-interface-design","tag-interface-builder","tag-objective-c","tag-patterns","tag-test-driven-development","tag-unit-testing","tag-user-interface-testing","tag-xcode"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p74loH-3j","_links":{"self":[{"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=\/wp\/v2\/posts\/205","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=205"}],"version-history":[{"count":14,"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=\/wp\/v2\/posts\/205\/revisions"}],"predecessor-version":[{"id":219,"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=\/wp\/v2\/posts\/205\/revisions\/219"}],"wp:attachment":[{"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/eschatologist.net\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}