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

TDD UIViewController

by fragi
July 17, 2019
in TDD, Unit Testing
Share on FacebookShare on Twitter

UIViewControllers are among the most frequent objects we use when we want to manage a view hierarchy for a UIkit app.

We can create UIViewControllers programmatically, using xib files or using storyboards. In this article will TDD a ViewController with a simple UI. Our ViewController will have two labels one with the name of a country and one with the flag of this country.

Let’s start our example:

STEP 1:

Create a new iOS project and name it TDDViewController. Since we want to start from scratch we have to delete all the files that Xcode has created for us.

Let’s delete the files ViewController.swift and TDDViewControllerTests.swift. Also, let’s open the Main.storyboard and delete the ViewController. 

Run the project. You should be able to see a blank screen. If the app crashes please make sure you have done all the above changes.

STEP 2: 

 It is time for starting TDD our new ViewController. Let’s name our ViewController as CountryDetailsViewController. So we have to create the 

CountryDetailsViewControllerTests TestCase:

Just make sure you added on the Tests target:

Then we get the following warning:

Unless we are planning to have Objective-C code we don’t need the bridging header, so let’s select “Don’t Create”.

 Finally, let’s remove all the methods that the template creates for us. The code should look like:

Swift
1
2
3
4
5
import XCTest
 
class CountryDetailsViewControllerTests: XCTestCase {
 
}

STEP 3:

 Our first test is:

Swift
1
2
3
4
5
6
7
8
    func test_shouldShowCountryName() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let sut = storyboard.instantiateViewController(withIdentifier: "CountryDetailsViewController") as! CountryDetailsViewController
        
        _ = sut.view
        
        XCTAssertEqual("Greece", sut.countryNameLabel.text)
    }

We notice that we trigger the view lifecycle by calling sut.view. ViewControllers are handling the view lifecycle and we don’t want to test their implementation on how they do this. All we need is to trigger the view lifecycle and access the view is the easiest and safest way. 

And now let’s make it pass:

First of all, let’s create the CountryDetailsViewController class. The code should look like:

Swift
1
2
3
4
5
import UIKit
 
class CountryDetailsViewController: UIViewController {
    
}

 Also, let’s go to the Main storyboard and add a new ViewController. Make sure you set the class to CountryDetailsViewController and also the storyboardID to CountryDetailsViewController:

Also, make sure you set it as the initial ViewController:Let’s import our main module to the test class:

Swift
1
2
import XCTest
@testable import TDDViewController

And let’s add an IBOutlet UIlabel with the name countryNameLabel to the ViewController. Set the constraints to be near the top of the view. Connect the IBOutlet to the CountryDetailsViewController:

Now the test compiles but it fails. Let’s add the correct text and make the test pass:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
import UIKit
 
class CountryDetailsViewController: UIViewController {
    
    @IBOutlet weak var countryNameLabel: UILabel!
 
     override func viewDidLoad() {
        super.viewDidLoad()
        
        countryNameLabel.text = "Greece"
    }
}

Now it’s time to refactor. Having a hardcoded string is not a good practice. Firstly, it makes the class not reusable for other countries and secondly because it represents the name of a country is better to create a country struct to encapsulate this logic there:

let’s create the Country struct:

Swift
1
2
3
struct Country {
    let name: String
}

and let’s use it on the CountryDetailsViewController

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
import UIKit
 
class CountryDetailsViewController: UIViewController {
    
    @IBOutlet weak var countryNameLabel: UILabel!
     var country: Country = Country(name: "Greece"
    
     override func viewDidLoad() {
        super.viewDidLoad()
        
        countryNameLabel.text = country.name
    }
}

 STEP 4:

Now let’s write to our next test.

Swift
1
2
3
4
5
6
7
8
9
func test_shouldShowFlag() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let sut = storyboard.instantiateViewController(withIdentifier: "CountryDetailsViewController") as! CountryDetailsViewController
        
        _ = sut.view
        
        let expectedFlag = try? CountryCodeStringToFlagConvert.flag(country: "gr")
        XCTAssertEqual(expectedFlag, sut.countryFlagLabel.text)
}

We will need another label for the flag so let’s add a countryFlagLabel UILabel in the CountryDetailsViewController and connect the IBOutlet as we did before:

CountryCodeStringToFlagConvert

And now we have to add the  CountryCodeStringToFlagConverter functionality. Let’s create a struct with the following code:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct CountryCodeStringToFlagConvert {
    
    enum CountryCodeStringToFlagConvertError: Error {
        case flagNotFound
    }
    
    static func flag(country:String) throws -> String {
        let base = 127397
        var usv = String.UnicodeScalarView()
        for utfc in country.uppercased().utf16 {
            guard let us = UnicodeScalar(base + Int(utfc)) else {
                throw CountryCodeStringToFlagConvertError.flagNotFound
            }
            usv.append(us)
        }
        return String(usv)
    }  
}

We should also unit test this struct, but to keep this article short in size we will not. If you want to check how to test a method that throws an error check this post: http://trikalabs.com/how-to-test-errors-in-swift/

Run the tests. The test compiles but fails. Let’s add the implementation to make it pass:

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
29
30
31
32
33
34
class CountryDetailsViewController: UIViewController {
    
    @IBOutlet weak var countryNameLabel: UILabel!
    @IBOutlet weak var countryFlagLabel: UILabel!
    
    var country: Country = Country(name: "Greece", code: "gr")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        countryNameLabel.text = country.name
        countryFlagLabel.text = country.flag()
    }
}
 
struct CountryCodeStringToFlagConvert {
    
    enum CountryCodeStringToFlagConvertError: Error {
        case flagNotFound
    }
    
    static func flag(country:String) throws -> String {
        let base = 127397
        var usv = String.UnicodeScalarView()
        for utfc in country.uppercased().utf16 {
            guard let us = UnicodeScalar(base + Int(utfc)) else {
                throw CountryCodeStringToFlagConvertError.flagNotFound
            }
            usv.append(us)
        }
        return String(usv)
    }
    
}

 and let’s refactor. We can extract the  logic of the sut creation in a separate method:

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_shouldShowCountryName() {
        let sut = createSUT()
        
        _ = sut.view
        
        XCTAssertEqual("Greece", sut.countryNameLabel.text)
    }
    
    func test_shouldShowFlag() {
        let sut = createSUT()
        
        _ = sut.view
        
        let expectedFlag = try? CountryCodeStringToFlagConvert.flag(country: "gr")
        XCTAssertEqual(expectedFlag, sut.countryFlagLabel.text)
    }
    
    func createSUT() -> CountryDetailsViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        return storyboard.instantiateViewController(withIdentifier: "CountryDetailsViewController") as! CountryDetailsViewController
        
    }

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...

What is TDD?
TDD

XCTestCase lifecycle

May 25, 2021

XCTestCase has many methods as part of its lifecycle. It has a class setup method that executes only once before...

What is TDD?
TDD

Make your tests more readable

May 23, 2021

In the FizzBuzz code challenge, we had to write the following test method: Although the test is easy to...

What is TDD?
TDD

@testable

May 16, 2021

At the beginning of the FizzBuzz code challenge , we deleted the @testable line of code. What does this @testable...

What is TDD?
TDD

What is SUT

May 16, 2021

In many articles about TDD, you will find the term SUT, which refers to the System Under Test. Using SUT...

What is TDD?
TDD

Why unit testing is valuable

May 16, 2021

There are many benefits of writing unit tests. The test we write can serve as a documentation of what the...

Next Post
TDD UITableView

TDD UITableView

Unit Test Result type and closure

Unit Test Result type and closure

Unit Test NotificationCenter

Unit Test NotificationCenter

  • 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.