42
everything you (n)ever wanted to know about testing view controllers

Everything You (N)ever Wanted to Know about Testing View Controllers

Embed Size (px)

Citation preview

Page 1: Everything You (N)ever Wanted to Know about Testing View Controllers

everything you (n)ever wanted to know about testing view

controllers

Page 2: Everything You (N)ever Wanted to Know about Testing View Controllers

unit testing is great…

Page 3: Everything You (N)ever Wanted to Know about Testing View Controllers

…for your model layer

- Model layer is easy to test- Most examples of testing/TDD show model layer tests

Page 4: Everything You (N)ever Wanted to Know about Testing View Controllers

can you even test view controllers?

- But view controllers—ugh!- Can you even test them?

Page 5: Everything You (N)ever Wanted to Know about Testing View Controllers

- Yes!- This is unfortunate misconception among many iOS devs

Page 6: Everything You (N)ever Wanted to Know about Testing View Controllers

- Yes!- This is unfortunate misconception among many iOS devs

Page 7: Everything You (N)ever Wanted to Know about Testing View Controllers

1. App module 2. Manual lifecycle events 3. Storyboard accessibility

- Just a few simple tricks

Page 8: Everything You (N)ever Wanted to Know about Testing View Controllers

- We’ll be testing my stealth mode app, BananaApp, made up of one view controller, BananaViewController

Page 9: Everything You (N)ever Wanted to Know about Testing View Controllers

- We’ll be testing my stealth mode app, BananaApp, made up of one view controller, BananaViewController

Page 10: Everything You (N)ever Wanted to Know about Testing View Controllers

1. App module 2. Manual lifecycle events 3. Storyboard accessibility

- Public class- App defines a Swift module

Page 11: Everything You (N)ever Wanted to Know about Testing View Controllers

- Defines module = YES -> classes in app exported as module- “Product module name” is the name of what you import in test file

Page 12: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewController.swift

public class BananaViewController: UIViewController { // ... }

- To test classes, they must be exported in module, so they must be public- This goes for all classes, not just view controllers

Page 13: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

import BananaApp import XCTest

class BananaViewControllerTests: XCTestCase {

var viewController: BananaViewController!

// ... }

- We import our defined module- Since class is public, we can reference it from imported module

Page 14: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

import BananaApp import XCTest

class BananaViewControllerTests: XCTestCase {

var viewController: BananaViewController!

// ... }

- We import our defined module- Since class is public, we can reference it from imported module

Page 15: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

import BananaApp import XCTest

class BananaViewControllerTests: XCTestCase {

var viewController: BananaViewController!

// ... }

- We import our defined module- Since class is public, we can reference it from imported module

Page 16: Everything You (N)ever Wanted to Know about Testing View Controllers

1. App module 2. Manual lifecycle events 3. Storyboard accessibility

- When app runs, view controller lifecycle methods are triggered automatically.- In tests, you’ll need to trigger methods like viewDidLoad: yourself.

Page 17: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewController.swift

import UIKit

public class BananaViewController: UIViewController { // ... public override func viewDidLoad() { super.viewDidLoad() updateButtons() }

private func updateButtons() { moreButton.enabled = bananaCount < 10 lessButton.enabled = bananaCount > 0 } }

- BananaViewController overrides viewDidLoad to update its buttons- Let’s test this behavior

Page 18: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewController.swift

import UIKit

public class BananaViewController: UIViewController { // ... public override func viewDidLoad() { super.viewDidLoad() updateButtons() }

private func updateButtons() { moreButton.enabled = bananaCount < 10 lessButton.enabled = bananaCount > 0 } }

- BananaViewController overrides viewDidLoad to update its buttons- Let’s test this behavior

Page 19: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewController.swift

import UIKit

public class BananaViewController: UIViewController { // ... public override func viewDidLoad() { super.viewDidLoad() updateButtons() }

private func updateButtons() { moreButton.enabled = bananaCount < 10 lessButton.enabled = bananaCount > 0 } }

- BananaViewController overrides viewDidLoad to update its buttons- Let’s test this behavior

Page 20: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewController.swift

import UIKit

public class BananaViewController: UIViewController { // ... public override func viewDidLoad() { super.viewDidLoad() updateButtons() }

private func updateButtons() { moreButton.enabled = bananaCount < 10 lessButton.enabled = bananaCount > 0 } }

- BananaViewController overrides viewDidLoad to update its buttons- Let’s test this behavior

Page 21: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

class BananaViewControllerTests: XCTestCase {

var viewController: BananaViewController!

override func setUp() { viewController = BananaViewController() let _ = viewController.view }

func testLessButtonIsDisabled() { XCTAssertFalse(viewController.lessButton.enabled) } }

- Here we test the less button is disabled- But when we run the tests, they fail- Need to access view to trigger viewDidLoad

Page 22: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

class BananaViewControllerTests: XCTestCase {

var viewController: BananaViewController!

override func setUp() { viewController = BananaViewController() let _ = viewController.view }

func testLessButtonIsDisabled() { XCTAssertFalse(viewController.lessButton.enabled) } }

- Here we test the less button is disabled- But when we run the tests, they fail- Need to access view to trigger viewDidLoad

Page 23: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

class BananaViewControllerTests: XCTestCase {

var viewController: BananaViewController!

override func setUp() { viewController = BananaViewController() let _ = viewController.view }

func testLessButtonIsDisabled() { XCTAssertFalse(viewController.lessButton.enabled) } }

- Here we test the less button is disabled- But when we run the tests, they fail- Need to access view to trigger viewDidLoad

Page 24: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

class BananaViewControllerTests: XCTestCase {

var viewController: BananaViewController!

override func setUp() { viewController = BananaViewController() let _ = viewController.view }

func testLessButtonIsDisabled() { XCTAssertFalse(viewController.lessButton.enabled) } }

XCTAssertFalse failed

- Here we test the less button is disabled- But when we run the tests, they fail- Need to access view to trigger viewDidLoad

Page 25: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

class BananaViewControllerTests: XCTestCase {

var viewController: BananaViewController!

override func setUp() { viewController = BananaViewController() let _ = viewController.view }

func testLessButtonIsDisabled() { XCTAssertFalse(viewController.lessButton.enabled) } }

- Here we test the less button is disabled- But when we run the tests, they fail- Need to access view to trigger viewDidLoad

Page 26: Everything You (N)ever Wanted to Know about Testing View Controllers

1. App module 2. Manual lifecycle events 3. Storyboard accessibility

- If your view controller’s interface is buried within a storyboard file, you’ll need to provide a way to access it

Page 27: Everything You (N)ever Wanted to Know about Testing View Controllers

- You’ll need to give your view controller an ID

Page 28: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

let storyboard = UIStoryboard(name: "Main", bundle: nil) viewController = storyboard.instantiateViewControllerWithIdentifier( "BananaViewControllerID") as BananaViewController

- You can instantiate any view controller with an ID in your tests- If it’s the initial view controller in your storyboard, you don’t need an identifier

Page 29: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

let storyboard = UIStoryboard(name: "Main", bundle: nil) viewController = storyboard.instantiateViewControllerWithIdentifier( "BananaViewControllerID") as BananaViewController

viewController = storyboard.instantiateInitialViewController() as BananaViewController

- You can instantiate any view controller with an ID in your tests- If it’s the initial view controller in your storyboard, you don’t need an identifier

Page 30: Everything You (N)ever Wanted to Know about Testing View Controllers

public class BananaViewController: UIViewController {

@IBOutlet public weak var countLabel: UILabel! @IBOutlet public weak var moreButton: UIButton! @IBOutlet public weak var lessButton: UIButton!

// ... }

- And remember, XIB and storyboard files set IBOutlet properties during -viewDidLoad

Page 31: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

let storyboard = UIStoryboard(name: "Main", bundle: nil) viewController = storyboard.instantiateViewControllerWithIdentifier( "BananaViewControllerID") as BananaViewController

let _ = viewController.view XCTAssertFalse(viewController.lessButton.enabled)

- So if you access an IBOutlet prior to triggering -viewDidLoad, you’ll hit an assert

Page 32: Everything You (N)ever Wanted to Know about Testing View Controllers

Thread 1: EXC_BAD_INSTRUCTION

// BananaViewControllerTests.swift

let storyboard = UIStoryboard(name: "Main", bundle: nil) viewController = storyboard.instantiateViewControllerWithIdentifier( "BananaViewControllerID") as BananaViewController

let _ = viewController.view XCTAssertFalse(viewController.lessButton.enabled)

- So if you access an IBOutlet prior to triggering -viewDidLoad, you’ll hit an assert

Page 33: Everything You (N)ever Wanted to Know about Testing View Controllers

// BananaViewControllerTests.swift

let storyboard = UIStoryboard(name: "Main", bundle: nil) viewController = storyboard.instantiateViewControllerWithIdentifier( "BananaViewControllerID") as BananaViewController

let _ = viewController.view XCTAssertFalse(viewController.lessButton.enabled)

- So if you access an IBOutlet prior to triggering -viewDidLoad, you’ll hit an assert

Page 34: Everything You (N)ever Wanted to Know about Testing View Controllers

1. App module 2. Manual lifecycle events 3. Storyboard accessibility

- So remember

Page 35: Everything You (N)ever Wanted to Know about Testing View Controllers

func testLessButtonAfterAddingBananaIsEnabled() { viewController.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) XCTAssert(viewController.lessButton.enabled) }

- With these in mind, we can write tests like this- Tap the button to add banana, then less button (to remove banana) is enabled

Page 36: Everything You (N)ever Wanted to Know about Testing View Controllers

func testLessButtonAfterAddingBananaIsEnabled() { viewController.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) XCTAssert(viewController.lessButton.enabled) }

- With these in mind, we can write tests like this- Tap the button to add banana, then less button (to remove banana) is enabled

Page 37: Everything You (N)ever Wanted to Know about Testing View Controllers

func testLessButtonAfterAddingBananaIsEnabled() { viewController.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) XCTAssert(viewController.lessButton.enabled) }

- With these in mind, we can write tests like this- Tap the button to add banana, then less button (to remove banana) is enabled

Page 38: Everything You (N)ever Wanted to Know about Testing View Controllers
Page 39: Everything You (N)ever Wanted to Know about Testing View Controllers
Page 40: Everything You (N)ever Wanted to Know about Testing View Controllers

- But in the end, it’s not as easy as testing regular objects- So push as much logic into model layer as possible

Page 41: Everything You (N)ever Wanted to Know about Testing View Controllers

- But in the end, it’s not as easy as testing regular objects- So push as much logic into model layer as possible

Page 42: Everything You (N)ever Wanted to Know about Testing View Controllers

thin view controllers!

- Remember, thin view controllers!