Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Unit Testing
Special Cases
How to Test
UIViewControllers

How to TestUnit Testing
What Is Testing For? 
UIViewControllers

Block-based code

!
How to TestUnit Testing
What Is Testing For? 
UIViewControllers

Block-based code

CoreData	

How to TestUnit Testing
What Is Testing For? 
UIViewController
IBOutlets & IBActions

viewDidLoad

dealloc

UINavigationController
UIViewController
IBOutlets & IBActions
Outlet is connected
- (void)setUp {
[super setUp];
sut = [ConverterViewController new];
}
!
- (void)tearDown {
sut = nil;
[super tearDown];
}
...
- (void)setUp {
[super setUp];
sut = [ConverterViewController new];
}
!
- (void)tearDown {
sut = nil;
[super tearDown];
}
...
- (void)setUp {
[super setUp];
sut = [ConverterViewController new];
}
!
- (void)tearDown {
sut = nil;
[super tearDown];
}
...
- (void)setUp {
[super setUp];
sut = [ConverterViewController new];
}
!
- (void)tearDown {
sut = nil;
[super tearDown];
}
...
IBOutlets & IBActions
Outlet has a right action.
- (void)testButtonActionBinding {
[sut view];
NSArray* acts =
[sut.button actionsForTarget:sut
forControlEvent:UIControlEv...
IBOutlets & IBActions
The action does the right things.
viewDidLoad
Unit testing of a view controller
nearly always means writing the
view controller methods differently
viewDidLoad
- should call helper methods
viewDidLoad
- should call helper methods

- each of the helper methods should
do just one thing (SOLID principles)
viewDidLoad
- should call helper methods

- each of the helper methods should
do just one thing (SOLID principles)

- writ...
viewDidLoad
- should call helper methods

- each of the helper methods should
do just one thing (SOLID principles)

- writ...
dealloc
setUp tearDown zombie
dealloc
❓hook dealloc method of SUT when
setup
dealloc
❓hook dealloc method of SUT when
setup

❓record calling of the hook
dealloc
❓hook dealloc method of SUT when
setup

❓record calling of the hook

❓verify if hook is called after teardown
Aspects
/// Adds a block of code before/instead/after the current
`selector` for a specific instance.
- (id<AspectToken>)aspect_ho...
dealloc
✅ hook dealloc method of SUT when
setup 

❓ record calling of the hook

❓ verify if hook is called after teardown
...
dealloc
✅ hook dealloc method of SUT when
setup 

✅ record calling of the hook

❓ verify if hook is called after teardown
...
dealloc
✅ hook dealloc method of SUT when
setup 

✅ record calling of the hook

✅ verify if hook is called after teardown
...
@interface ConverterViewControllerTests : XCTestCase {
ConverterViewController* sut;
BOOL _sutDeallocated;
}
@end
!
- (voi...
@interface
}
@end
!
- (
[
!
!
!
!
!
}
!
- (
[
}
dealloc
!
!
!
!
!
!
!
!
!
!
[sut aspect_hookSelector:NSSelectorFromString(...
UINavigationController
- test push view controller

- test pop view controller
UINavigationController
UINavigationController
But
UINavigationController
But
@interface UIViewController (UINavigationControllerItem)
!
@property(nonatomic,readonly,retain)...
-(void)testTappingDetailsShouldDisplayDetails {
UINavigationController *nav = [[UINavigationController alloc]
initWithRoot...
Choosing not to test view controllers
is the decision not to test most of
your code.
UIViewController
Testing simple things is simple, and
testing complex things is complex
UIViewController
Block-based code
Why does test fail?
Block-based code
!
typedef void(^CompletionHandler)(NSArray * result);
!
- (void)runAsyncCode:(CompletionHandler)completio...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
Block-based code
What can we do?
Block-based code
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
has...
Block-based code
Also we can use:
- (void)verifyWithDelay:(NSTimeInterval)delay
{
NSTimeInterval step = 0.01;
while(delay ...
CoreData
CoreData
As long as you don't put business
logic in your models, you don't have
to test them.
CoreData
creates setters & getters in run-time
CoreData
creates setters & getters in run-time
we can’t mock CoreData models
CoreData
What can we do?
CoreData
- create protocol that has all model’s
properties defined
CoreData
- create protocol that has all model’s
properties defined

- conform NSManagedObject to the
protocol
CoreData
- create protocol that has all model’s
properties defined

- conform NSManagedObject to the
protocol

- create NSO...
CoreData
Protocol
Model<Protocol> TestModel<Protocol>
CoreData
Protocol
Model<Protocol> TestModel<Protocol>
CoreData
Have a better solution?
CoreData
Create own CoreData stack for each
test-case
- (void)setUp
{
[super setUp];
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"SimpleInvoice"
withExtension:@“mo...
- (void
{
[
!
initWithContentsOfURL
!
initWithManagedObjectModel
!
!
!
!
!
!
!
!
}
CoreData
- (void)setUp
{
!
!
!
!
!
!
!
...
CoreData
Advantages?
CoreData
Advantages?
- no additional classes
CoreData
Advantages?
- no additional classes

- no dependence on external state
CoreData
Advantages?
- no additional classes

- no dependence on external state

- close approximation to the
application ...
CoreData
Advantages?
- no additional classes

- no dependence on external state

- close approximation to the
application ...
CoreData
Advantages?
- no additional classes

- no dependence on external state

- close approximation to the
application ...
- (void)testFullNameReturnsСorrectString {
Person* ps;
ps = [NSEntityDescription insertNewObjectForEntityForName:@"Person"...
CoreData
- test model’s additional business-logic

- test ManagedObjectModel for an
entity

- create and use models as a m...
✅ UIViewControllers

✅ Block-based code

✅ CoreData	

How to Test
Video is coming!
https://github.com/maksum
!
maksum.ko
contact me:
Sources
http://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/
0321774183
!
http://www.objc.io/issue-1/t...
Unit Testing: Special Cases
Upcoming SlideShare
Loading in …5
×

Unit Testing: Special Cases

975 views

Published on

The 4th Dnepropetrovsk iOS Practice Leaders Community Meet-Up, which took place onThursday, September 25.

Maxim Koshtenko, an iOS developer with 4+ years of experience in the area, held a presentation in which he told:

- about the most widespread problems which appear while writing tests and how to solve them;
- how to cover controllers with tests correctly and what should be visible in interface;
- why tests do not work for block-based and asynchronous code and how we can fix this;
- how to write tests for Core Data models;
- many other useful and interesting tips and tricks.

The presentation will be interesting for all iOS developers.

Published in: Technology
  • Be the first to comment

Unit Testing: Special Cases

  1. 1. Unit Testing Special Cases
  2. 2. How to Test
  3. 3. UIViewControllers How to TestUnit Testing What Is Testing For? 
  4. 4. UIViewControllers Block-based code ! How to TestUnit Testing What Is Testing For? 
  5. 5. UIViewControllers Block-based code CoreData How to TestUnit Testing What Is Testing For? 
  6. 6. UIViewController
  7. 7. IBOutlets & IBActions viewDidLoad dealloc UINavigationController UIViewController
  8. 8. IBOutlets & IBActions Outlet is connected
  9. 9. - (void)setUp { [super setUp]; sut = [ConverterViewController new]; } ! - (void)tearDown { sut = nil; [super tearDown]; } Is Outlet Connected?
  10. 10. - (void)setUp { [super setUp]; sut = [ConverterViewController new]; } ! - (void)tearDown { sut = nil; [super tearDown]; } ! - (void)testThatTextFieldOutletIsConnected { XCTAssertNotNil(sut.textField, @"outlet should be connected"); } Is Outlet Connected?
  11. 11. - (void)setUp { [super setUp]; sut = [ConverterViewController new]; } ! - (void)tearDown { sut = nil; [super tearDown]; } ! - (void)testThatTextFieldOutletIsConnected { XCTAssertNotNil(sut.textField, @"outlet should be connected"); } Is Outlet Connected?
  12. 12. - (void)setUp { [super setUp]; sut = [ConverterViewController new]; } ! - (void)tearDown { sut = nil; [super tearDown]; } ! - (void)testThatTextFieldOutletIsConnected { [sut view]; XCTAssertNotNil(sut.textField, @"outlet should be connected"); } Is Outlet Connected?
  13. 13. IBOutlets & IBActions Outlet has a right action.
  14. 14. - (void)testButtonActionBinding { [sut view]; NSArray* acts = [sut.button actionsForTarget:sut forControlEvent:UIControlEventTouchUpInside]; XCTAssert([acts containsObject:@"onButton:"], @"should use correct action"); } Is Action Connected?
  15. 15. IBOutlets & IBActions The action does the right things.
  16. 16. viewDidLoad Unit testing of a view controller nearly always means writing the view controller methods differently
  17. 17. viewDidLoad - should call helper methods
  18. 18. viewDidLoad - should call helper methods - each of the helper methods should do just one thing (SOLID principles)
  19. 19. viewDidLoad - should call helper methods - each of the helper methods should do just one thing (SOLID principles) - write tests for each of the helper methods
  20. 20. viewDidLoad - should call helper methods - each of the helper methods should do just one thing (SOLID principles) - write tests for each of the helper methods - test viewDidLoad calls helper methods (partial mock)
  21. 21. dealloc setUp tearDown zombie
  22. 22. dealloc ❓hook dealloc method of SUT when setup
  23. 23. dealloc ❓hook dealloc method of SUT when setup ❓record calling of the hook
  24. 24. dealloc ❓hook dealloc method of SUT when setup ❓record calling of the hook ❓verify if hook is called after teardown
  25. 25. Aspects
  26. 26. /// Adds a block of code before/instead/after the current `selector` for a specific instance. - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; ! /// Called after the original implementation (default) AspectPositionAfter, ! /// Will replace the original implementation. AspectPositionInstead, ! /// Called before the original implementation. AspectPositionBefore, Aspects
  27. 27. dealloc ✅ hook dealloc method of SUT when setup ❓ record calling of the hook ❓ verify if hook is called after teardown Aspects
  28. 28. dealloc ✅ hook dealloc method of SUT when setup ✅ record calling of the hook ❓ verify if hook is called after teardown Aspects instance var
  29. 29. dealloc ✅ hook dealloc method of SUT when setup ✅ record calling of the hook ✅ verify if hook is called after teardown Aspects instance var XCTAssert
  30. 30. @interface ConverterViewControllerTests : XCTestCase { ConverterViewController* sut; BOOL _sutDeallocated; } @end ! - (void)setUp { [super setUp]; sut = [ConverterViewController new]; _sutDeallocated = NO; [sut aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){ _sutDeallocated = YES; } error:nil]; } ! - (void)tearDown { sut = nil; XCTAssertTrue(_sutDeallocated, @"SUT is not deallocated"); [super tearDown]; } dealloc
  31. 31. @interface } @end ! - ( [ ! ! ! ! ! } ! - ( [ } dealloc ! ! ! ! ! ! ! ! ! ! [sut aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){ _sutDeallocated = YES; } error:nil];
  32. 32. UINavigationController - test push view controller - test pop view controller
  33. 33. UINavigationController
  34. 34. UINavigationController But
  35. 35. UINavigationController But @interface UIViewController (UINavigationControllerItem) ! @property(nonatomic,readonly,retain) UINavigationController* navigationController; ! @end
  36. 36. -(void)testTappingDetailsShouldDisplayDetails { UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:sut]; id mockNav = [OCMockObject partialMockForObject:nav]; [[mockNav expect] pushViewController:[OCMArg any] animated:YES]; [sut onShowDetailsButton:nil]; [mockNav verify]; } UINavigationController
  37. 37. Choosing not to test view controllers is the decision not to test most of your code. UIViewController
  38. 38. Testing simple things is simple, and testing complex things is complex UIViewController
  39. 39. Block-based code Why does test fail?
  40. 40. Block-based code ! typedef void(^CompletionHandler)(NSArray * result); ! - (void)runAsyncCode:(CompletionHandler)completion { dispatch_async(dispatch_get_main_queue(), ^{ completion(nil); }); }
  41. 41. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  42. 42. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  43. 43. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  44. 44. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  45. 45. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  46. 46. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  47. 47. Block-based code What can we do?
  48. 48. Block-based code
  49. 49. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  50. 50. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert ! ! ! ! ! XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  51. 51. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; while([timeout timeIntervalSinceNow] > 0 && hasCalledBack == NO) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; } XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  52. 52. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; while([timeout timeIntervalSinceNow] > 0 && hasCalledBack == NO) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; } XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  53. 53. Block-based code Also we can use: - (void)verifyWithDelay:(NSTimeInterval)delay { NSTimeInterval step = 0.01; while(delay > 0) { if([expectations count] == 0) break; NSDate* until = [NSDate dateWithTimeIntervalSinceNow:step]; [[NSRunLoop currentRunLoop] runUntilDate:until]; delay -= step; step *= 2; } [self verify]; } OCMock
  54. 54. CoreData
  55. 55. CoreData As long as you don't put business logic in your models, you don't have to test them.
  56. 56. CoreData creates setters & getters in run-time
  57. 57. CoreData creates setters & getters in run-time we can’t mock CoreData models
  58. 58. CoreData What can we do?
  59. 59. CoreData - create protocol that has all model’s properties defined
  60. 60. CoreData - create protocol that has all model’s properties defined - conform NSManagedObject to the protocol
  61. 61. CoreData - create protocol that has all model’s properties defined - conform NSManagedObject to the protocol - create NSObject model just for testing, conforms to the protocol and @synthesize properties
  62. 62. CoreData Protocol Model<Protocol> TestModel<Protocol>
  63. 63. CoreData Protocol Model<Protocol> TestModel<Protocol>
  64. 64. CoreData Have a better solution?
  65. 65. CoreData Create own CoreData stack for each test-case
  66. 66. - (void)setUp { [super setUp]; NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"SimpleInvoice" withExtension:@“momd"]; ! NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; ! NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; ! XCTAssertNotNil([psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL], @"Should be able to add in-memory store”); ! _moc = [[NSManagedObjectContext alloc] init]; _moc.persistentStoreCoordinator = psc; } CoreData
  67. 67. - (void { [ ! initWithContentsOfURL ! initWithManagedObjectModel ! ! ! ! ! ! ! ! } CoreData - (void)setUp { ! ! ! ! ! ! ! ! ! ! XCTAssertNotNil([psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL], @"Should be able to add in-memory store”); ! ! ! }
  68. 68. CoreData Advantages?
  69. 69. CoreData Advantages? - no additional classes
  70. 70. CoreData Advantages? - no additional classes - no dependence on external state
  71. 71. CoreData Advantages? - no additional classes - no dependence on external state - close approximation to the application environment
  72. 72. CoreData Advantages? - no additional classes - no dependence on external state - close approximation to the application environment - we are able to create base test class with a stack and subclass it where we need
  73. 73. CoreData Advantages? - no additional classes - no dependence on external state - close approximation to the application environment - we are able to create base test class with a stack and subclass it where we need
  74. 74. - (void)testFullNameReturnsСorrectString { Person* ps; ps = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:_moc]; ps.firstName = @"Tom"; ps.lastName = @“Lol"; ! STAssertTrue([[ps fullName] isEqualToString:@"Lol Tom"], @"should have matched full name"); } CoreData
  75. 75. CoreData - test model’s additional business-logic - test ManagedObjectModel for an entity - create and use models as a mocks
  76. 76. ✅ UIViewControllers ✅ Block-based code ✅ CoreData How to Test
  77. 77. Video is coming! https://github.com/maksum ! maksum.ko contact me:
  78. 78. Sources http://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/ 0321774183 ! http://www.objc.io/issue-1/testing-view-controllers.html http://www.silverbaytech.com/2013/02/25/ios-testing-part-3-testing-view-controller/ http://iosunittesting.com/unit-testing-view-controllers/ http://blog.carbonfive.com/2010/03/10/testing-view-controllers/ ! http://iosunittesting.com/how-to-unit-test-completion-blocks/ ! http://iosunittesting.com/unit-testing-core-data/ http://ashfurrow.com/blog/unit-testing-with-core-data-models http://www.sicpers.info/2010/06/template-class-for-unit-testing-core-data-entities/ http://iamleeg.blogspot.com/2010/01/unit-testing-core-data-driven-apps-fit.html

×