Trikalabs
  • Home
  • Best online TDD videos
  • Book Suggestions
  • About Me
  • Contact
Trikalabs
No Result
View All Result

Unit testing UIViewController LifeCycle

by fragi
May 13, 2021
in Unit Testing
Share on FacebookShare on Twitter

Unit testing UIViewController life cycle events are not a straightforward process. Let’s have a look on the following view controller:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import UIKit
 
final class ViewController: UIViewController {
 
    var aCollaborator: ACollaboratorProtocol = ACollaborator()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        aCollaborator.methodA()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        aCollaborator.methodB()
    }
 
    @IBAction func didTapButton(_ sender: Any) {
        aCollaborator.methodC()
        showAnotherViewController()
    }
    
    private func showAnotherViewController() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let anotherViewController = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        present(anotherViewController, animated: true, completion: nil)
    }
    
}

All we want to test here are the calls to the collaborator and the presentation of a new ViewController as a result of the button action.

In order to test the interactions with the Collaborator we have to replace it with a test double, a spy specifically.

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import XCTest
@testable import TestingViewControllersLifeCycle
 
class ViewControllerTests: XCTestCase {
 
    private final class ACollaboratorMock: ACollaboratorProtocol {
        
        private(set) var didCallMethodA = 0
        func methodA() {
            didCallMethodA += 1
        }
        
        private(set) var didCallMethodB = 0
        func methodB() {
            didCallMethodB += 1
        }
        
        private(set) var didCallMethodC = 0
        func methodC() {
            didCallMethodC += 1
        }
    }
}

Now let’s start by unit testing the first interaction, the call to methodA when on the viewDidLoad.

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ViewControllerTests: XCTestCase {
 
    func test_viewDidLoad_callsMethodAOnCollaborator() {
        let (sut, mockACollaborator) = makeSUT()
        
        _ = sut.view
        
        XCTAssertEqual(1, mockACollaborator.didCallMethodA)
    }
    
    private func makeSUT() -> (ViewController, ACollaboratorMock) {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let sut = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
        let mockACollaborator = ACollaboratorMock()
        sut.aCollaborator = mockACollaborator
        
        return (sut, mockACollaborator)
    }

Here the trick is that we need to call sut.view, that is enough to trigger the viewDidLoad method.

Let’s now write the test for the methodB on the viewWillAppear.

Swift
1
2
3
4
5
6
7
8
    func test_viewWillAppear_callsMethodBOnCollaborator() {
        let (sut, mockACollaborator) = makeSUT()
        
        sut.beginAppearanceTransition(true, animated: true)
        sut.endAppearanceTransition()
        
        XCTAssertEqual(1, mockACollaborator.didCallMethodB)
    }

This time the calls beginAppearanceTransition and endAppearanceTransition enable us to test the code on the viewWillAppear method.

Last method to test is the didTapButton. We need two tests here one to test the interaction with the Collaborator and one to test that the view is presenting a new view.

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    func test_didTapButton_callsMethodCOnCollaborator() {
        let (sut, mockACollaborator) = makeSUT()
        
        sut.didTapButton(UIButton())
 
        XCTAssertEqual(1, mockACollaborator.didCallMethodC)
    }
 
    func test_didTapButton_presentsAnotherViewController() {
        let (sut, _) = makeSUT()
        createWindowWith(rootViewController: sut)
 
        sut.didTapButton(UIButton())
 
        XCTAssertTrue(sut.presentedViewController is AnotherViewController)
    }
    
    private  func createWindowWith(rootViewController viewController: UIViewController) {
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.makeKeyAndVisible()
        window.rootViewController = viewController
    }

Right, the first test is easy enough, straightforward case. But checking that the new viewController has been presented requires to add the view controller in a window.

The above tricks will help you to test the majority code in a UIViewController. 

fragi

fragi

Related Posts

What is TDD?
Unit Testing

Write better Unit tests with XCTUnwrap

January 27, 2022

Testing optionals can require boilerplate code to unwrap it. It can also affect the readability of the test. Let's look...

Test static method on collaborator
Unit Testing

Test static method on collaborator

May 13, 2021

One of the main issues we are facing when we try to make an old piece of code testable is...

Dependency injection – UIViewController in Storyboard (iOS 13)
Unit Testing

Dependency injection – UIViewController in Storyboard (iOS 13)

May 13, 2021

Finally Apple (iOS 13 +) has added constructor dependency injection to Storyboards! No excuses anymore for not using DI properly...

Unit testing code with DispatchQueue
Unit Testing

Unit testing code with DispatchQueue

May 13, 2021

One way of testing code with DispatchQueue is by using expectations (https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations/testing_asynchronous_operations_with_expectations) but in most of the cases it's more...

Dependency Injection – UIViewController with a Nib file
Unit Testing

Dependency Injection – UIViewController with a Nib file

May 13, 2021

UIViewControllers are among the most used UIKit objects. As the name implies UIViewControllers are controller objects and their purpose should...

Unit Test Post Notification
Unit Testing

Unit Test Post Notification

July 19, 2019

In this article, we will see how we can test code that post notifications. Let's see our example code: ...

Next Post
Unit testing code with DispatchQueue

Unit testing code with DispatchQueue

Git rebase with SourceTree

Git rebase with SourceTree

Dependency injection – UIViewController in Storyboard (iOS 13)

Dependency injection - UIViewController in Storyboard (iOS 13)

  • Advertise
  • Privacy & Policy
  • Contact

© 2019 Trikalabs Ltd. All rights reserved.

No Result
View All Result
  • Home
  • About Me
  • A curated list with the best free online TDD videos
  • Book Suggestions
  • Pinner Code Club

© 2019 Trikalabs Ltd. All rights reserved.