everything you (n)ever wanted to know about testing view controllers
TRANSCRIPT
![Page 1: Everything You (N)ever Wanted to Know about Testing View Controllers](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/1.jpg)
everything you (n)ever wanted to know about testing view
controllers
![Page 2: Everything You (N)ever Wanted to Know about Testing View Controllers](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/2.jpg)
unit testing is great…
![Page 3: Everything You (N)ever Wanted to Know about Testing View Controllers](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/3.jpg)
…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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/4.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/5.jpg)
- Yes!- This is unfortunate misconception among many iOS devs
![Page 6: Everything You (N)ever Wanted to Know about Testing View Controllers](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/6.jpg)
- Yes!- This is unfortunate misconception among many iOS devs
![Page 7: Everything You (N)ever Wanted to Know about Testing View Controllers](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/7.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/8.jpg)
- 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/9.jpg)
- 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/10.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/11.jpg)
- 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/12.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/13.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/14.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/15.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/16.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/17.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/18.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/19.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/20.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/21.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/22.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/23.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/24.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/25.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/26.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/27.jpg)
- You’ll need to give your view controller an ID
![Page 28: Everything You (N)ever Wanted to Know about Testing View Controllers](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/28.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/29.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/30.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/31.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/32.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/33.jpg)
// 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/34.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/35.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/36.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/37.jpg)
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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/38.jpg)
![Page 39: Everything You (N)ever Wanted to Know about Testing View Controllers](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/39.jpg)
![Page 40: Everything You (N)ever Wanted to Know about Testing View Controllers](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/40.jpg)
- 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/41.jpg)
- 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](https://reader033.vdocuments.net/reader033/viewer/2022051414/55a20aae1a28aba0368b46c3/html5/thumbnails/42.jpg)
thin view controllers!
- Remember, thin view controllers!