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];
}
Is Outlet Connected?
- (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?
- (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?
- (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?
IBOutlets & IBActions
Outlet has a right action.
- (void)testButtonActionBinding {
[sut view];
NSArray* acts =
[sut.button actionsForTarget:sut
forControlEvent:UIControlEventTouchUpInside];
XCTAssert([acts containsObject:@"onButton:"], @"should use
correct action");
}
Is Action Connected?
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)

- write tests for each of the helper
methods
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)
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_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
dealloc
✅ hook dealloc method of SUT when
setup 

❓ record calling of the hook

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

✅ record calling of the hook

❓ verify if hook is called after teardown
Aspects
instance var
dealloc
✅ hook dealloc method of SUT when
setup 

✅ record calling of the hook

✅ verify if hook is called after teardown
Aspects
instance var
XCTAssert
@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
@interface
}
@end
!
- (
[
!
!
!
!
!
}
!
- (
[
}
dealloc
!
!
!
!
!
!
!
!
!
!
[sut aspect_hookSelector:NSSelectorFromString(@"dealloc")
withOptions:AspectPositionBefore
usingBlock:^(id<AspectInfo> aspectInfo){
_sutDeallocated = YES;
} error:nil];
UINavigationController
- test push view controller

- test pop view controller
UINavigationController
UINavigationController
But
UINavigationController
But
@interface UIViewController (UINavigationControllerItem)
!
@property(nonatomic,readonly,retain) UINavigationController*
navigationController;
!
@end
-(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
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)completion {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil);
});
}
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
hasCalledBack = YES;
}];
// assert
XCTAssert(hasCalledBack, @"Test timed out");
}
Block-based code
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
hasCalledBack = YES;
}];
// assert
XCTAssert(hasCalledBack, @"Test timed out");
}
Block-based code
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
hasCalledBack = YES;
}];
// assert
XCTAssert(hasCalledBack, @"Test timed out");
}
Block-based code
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
hasCalledBack = YES;
}];
// assert
XCTAssert(hasCalledBack, @"Test timed out");
}
Block-based code
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
hasCalledBack = YES;
}];
// assert
XCTAssert(hasCalledBack, @"Test timed out");
}
Block-based code
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
hasCalledBack = YES;
}];
// assert
XCTAssert(hasCalledBack, @"Test timed out");
}
Block-based code
Block-based code
What can we do?
Block-based code
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
hasCalledBack = YES;
}];
// assert
XCTAssert(hasCalledBack, @"Test timed out");
}
Block-based code
- (void)testRunAsyncCode {
// arrange
__block BOOL hasCalledBack = NO;
!
// act
[sut runAsyncCode:^(NSArray *result) {
hasCalledBack = YES;
}];
// assert
!
!
!
!
!
XCTAssert(hasCalledBack, @"Test timed out");
}
Block-based code
- (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
- (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
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
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 NSObject model just for
testing, conforms to the protocol
and @synthesize properties
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:@“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
- (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”);
!
!
!
}
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 environment
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
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
- (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
CoreData
- test model’s additional business-logic

- test ManagedObjectModel for an
entity

- create and use models as a mocks
✅ 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/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
Unit Testing: Special Cases

Unit Testing: Special Cases