Unit Testing in iOS

Ha Duyen Hoa
6 min readNov 14, 2018

--

Part 2: My first Unit Tests

Preface

I’m planning to write a complete series of Unit Testing for iOS. You can find finished parts in the links below. Happy reading :)

Part 1: Why do we need Unit Testing when developing an application?

Part 2: My first Unit Tests

Part 3: Quick/Nimble for a clean test

Part 4: How to deal with context

Part 5: Good practices

Part 6: Unit Test, Jenkins & SonarQube altogether [TO BE WRITTEN]

Let’s start to write the very first test in Xcode. As my stories are about testing in iOS, all examples will be done in Xcode. We will use the XCTest framework to start with.

Preparation

I will use Xcode 10 with Swift 4.2 environment to show you how we write Unit Test for an iOS application.

There are two ways to add Unit Test feature in your project

  • Check the option Incude Unit Tests when you create your project in Xcode
  • Add new Test Target into your Xcode project by going to Menu -> Edit -> Add Target -> choose iOS Unit Test Bundle then click Next to fill some parameters of this test target.

Let’s test

Imagine we have a function sum of the class Calculator.swift

class Calculator {
/**
A dummy sum function
- Parameter first: first value
- Parameter second: second value
- Returns: first + second
*/
class func sum(_ first: Int, second: Int) -> Int {
return 2 * first + second
}
}

My test class for Calculator.swift

import XCTest@testable import TestingForDummies
class CalculatorTests: XCTestCase {
func testSum() {
let a = 100
let b = 200
let c = Calculator.sum(a, second: b)
XCTAssertEqual(c, 300, "Sum of 100 and 200 must be 300")
}
}

What the testSum does is to callsum on Calculator with 2 inputs: 100 and 200. It expects to receive the result = 300

Let’s run this test (choose testable target then press Command + U). Xcode will tell you that there is an error with the test:

With the message that we add for each expectation and the detail of the failure, we can have an idea why the implemented function does not work correctly. In my case, I should implement return first + second instead of return 2 * first + second This is just an example. The implementation depends on the requirements of your application.

You may realize that, in my CalculatorTests class, I call @testable import TestingForDummies Why do we need that import. It’s because, in my project, I don’t include any classes from the main application into the test target. I have to import it here so that classes from test target can use classes from the main application. Here in my case, CalculatorTests need to call to Calculator to process the tests.

XCTAssert

XCTest framework provides several kinds of expectation checks. You can choose the right one depending on your context in the test methods.

  • XCTAssertNil(object): if object is Nil, test case passes.
  • XCAssertNotNil(object): if object is Not Nil, test case passes
  • XCTAssertEqual(object1, object2): if object1 is equal to object2, test case passes.
  • XCTAssetNotEqual(object1, object2): if object1 is not equal to object2, test case passes.
  • XCTAssetTrue(condition): if condition is true, test case passes
  • XCTAssetFalse(condition): if condition does not meet, test case passes
  • XCTAssertGreaterThan(object1, object2): if object1 > object2, test case passes
  • XCTAssertGreaterThanOrEqual(object1, object2): if object1object2, test case passes
  • XCTAssertLessThan(object1, object2): if object1 < object2, test case passes
  • XCTAssertLessThanOrEqual(object1, object2): if object1object2, test case passes
  • XCTAssertThrowException(operation): if operation throws an exception, test case

How to test async function?

Most of iOS applications or any mobile applications are developed with async functions. With help of XCTestExpectation, it’s also easy as per writing a test for synchronous function.

Consider that I have a function that downloads an image

/**
Download data from an URL. When the download is finished, call the completion with the eventual downloaded data
- Parameter completion: completion block to be called when the download task is done.
- Parameter downloadedData: Downloaded data or nil
*/
class func getServerConfigData(_ completion: @escaping (_ downloadedData: Data?) -> Void) {
guard let url = URL(string: "https://url.to.config.file") else {
completion(nil)
return
}

//create download task and start to download
let dataTask = URLSession.shared.dataTask(with: url) { (data, _, _) in
completion(data)
}
dataTask.resume()
}

To test this function, we create the expectations with XCTestExpectation , call the async function then fulfill the expectation in the completion block if the block returns what we are expecting for.

This example is just to show you how to write a test for async methods. The result of this test depends on the reachability of the device, of the availability of the backend and many things. In the real test, we should avoid this by using some stub or mock technic to isolate the Unit Test from those dependencies. This will be discussed in one of my next posts where I will write about mocking while testing.

Now, I want to see how good my test coverage is

The simple way to see the test coverage is to enable it when you execute tests then see the coverage in Xcode.

First, enable it by going to Edit Scheme of the testing target, then chose Test on the left menu then tick to Gather coverage for in Code Coverage option. Here, I enabled code coverage for my target TestingForDummies .

Then, once the Tests are finished, you can see the test coverage by going to Report Navigator (blue button in this screenshot), then select { Coverage: (this is new in Xcode 10). You can find it in: View -> Navigators -> Show Report Navigators, or Command + 9

You can learn from the report that:

  • Your Calculator.swift class is 50% tested. The non-tested function is multiple
  • The test coverage takes into account every class in your application: AppDelegate.swift, *ViewController.swift. Let’s discuss this in another post :)

When your Test is failed, you may interest in looking to the Log menu to see what happen

Hope you enjoy the reading. In the next post, I will write about Quick/Nimble, 2 frameworks that will help you to write unit testing case more natural and easier to read.

You can find codes in examples of this post in my git repo: https://bitbucket.org/haduyenhoa/testingfordummies

Part 3 is ready: Write test with Quick/Nimble https://medium.com/@haduyenhoa/unit-testing-in-ios-5409d4367718

--

--

Ha Duyen Hoa

a Software Engineer who loves audiophile music & traveling.