Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS Unit Testing - Special Cases

iOS Unit Testing - Special Cases

iOS Practice Leaders Community Meet-up in Dnipropetrovsk

Avatar for Maksym Koshtenko

Maksym Koshtenko

September 25, 2014

More Decks by Maksym Koshtenko

Other Decks in Programming

Transcript

  1. - (void)setUp { [super setUp]; sut = [ConverterViewController new]; }

    - (void)tearDown { sut = nil; [super tearDown]; } Is Outlet Connected?
  2. - (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?
  3. - (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?
  4. - (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?
  5. - (void)testButtonActionBinding { [sut view]; NSArray* acts = [sut.button actionsForTarget:sut

    forControlEvent:UIControlEventTouchUpInside]; XCTAssert([acts containsObject:@"onButton:"], @"should use correct action"); } Is Action Connected?
  6. viewDidLoad Unit testing of a view controller nearly always means

    writing the view controller methods differently
  7. 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)
  8. dealloc ❓hook dealloc method of SUT when setup! ❓record calling

    of the hook! ❓verify if hook is called after teardown
  9. /// 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
  10. dealloc ✅ hook dealloc method of SUT when setup !

    ✅ record calling of the hook! ✅ verify if hook is called after teardown Aspects instance var XCTAssert
  11. @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
  12. @interface } @end - ( [ } - ( [

    } dealloc [sut aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){ _sutDeallocated = YES; } error:nil];
  13. -(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
  14. Choosing not to test view controllers is the decision not

    to test most of your code. UIViewController
  15. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO;

    // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  16. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO;

    // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  17. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO;

    // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  18. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO;

    // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  19. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO;

    // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  20. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO;

    // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  21. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO;

    // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  22. - (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO;

    // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); } Block-based code
  23. - (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
  24. - (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
  25. 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
  26. CoreData As long as you don't put business logic in

    your models, you don't have to test them.
  27. 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
  28. - (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
  29. - (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”); }
  30. 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
  31. 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
  32. - (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