Waldo sessions now support scripting! – Learn more
Testing

Useful Examples of Mobile App Testing With XCTest

Nabendu Biswas
Nabendu Biswas
Useful Examples of Mobile App Testing With XCTest
January 24, 2023
7
min read

XCTest is the default unit testing framework in Xcode and is different than XCUITest, which is used for automation testing. Unit testing is done by developers mostly in enterprise-grade projects. Most large projects are divided into smaller parts and given to individual developers. These smaller parts are called stories in agile development. Now, when the developer completes their story and a feature, they also have to write unit tests for that story.

The testers are then responsible for doing manual testing of the whole app to check everything is working as desired. But nowadays automation testing is preferred, as it saves time and effort. Again, Apple has its own framework to do automation testing called XCUITest. Check out this post for Mmore on the difference between XCTest and XCUITest.

In this post, we're first going to learn a little about XCTest. (To learn about this testing framework more in-depth, our earlier post on XCTest is a great starting point.) We'll follow this by checking some complex XCTest cases example and also learn about debugging a test.

XCTest is a built-in unit testing framework in Xcode, which is the default IDE (integrated development environment) for Mac

What Is XCTest?

XCTest is a built-in unit testing framework in Xcode, which is the default IDE (integrated development environment) for Mac. The code for both is written in the Swift language. As the developer creating the code for the story has already created the code in Swift, they don't need to learn a new language in order to test it.

Apple released XCTest at the WWDC 2016 conference, along with XCUITest. Since it is tightly integrated with the Apple ecosystem, it's very easy to get started with no installation required.

The Setup

We'll start with writing XCTest cases in the Playground, as we did in our earlier post on Swift String. So first, open Xcode and then click on File -> Playground.

new playground in xctest

In the pop-up, select iOS and then Blank. And then press the Next button.

In the pop-up, select iOS and then Blank. And then press the Next button.

Next, we need to give the Playground a name, which is UsefulXCTests in our case.

Next, we need to give the Playground a name, which is UsefulXCTests in our case.

Finally, we'll get to the Playground where we'll start writing our XCTest cases.

Finally, we'll get to the Playground where we'll start writing our XCTest cases.

Basic Setup With XCTest

We require some more boilerplate code to use XCTest in the Playground, as we did in Swift Struct. Create a file called TestRunner.swift in the Sources folder, and add the below code in it. Here, we need to write a structure called TestRunner. Inside it, the code tells us that it will run all test suites.


  import Foundation
  import XCTest
  
  public struct TestRunner
  {
     public init() { }
  
     public func runTests(testClass:AnyClass){
      let tests = testClass as! XCTestCase.Type
      let testSuite = tests.defaultTestSuite
      testSuite.run()
    }
  }

Next, in the UsefulXCTests.playground root file, we'll create a class called UsefulXCTests. Then, inside a function of testShouldPass(), we'll create a variable of emojiStr. We're using the XCTAssertEqual() to check if the count is 6.

Next, with TestRunner from the previous part, we'll call the runTests() function in it. Here we're passing UsefulXCTests.

The boilerplate code for PlaygroundTestObserver is required. After this, click on the play button, and it will show the test case to be successful in the console.


  import UIKit
  import Foundation
  import PlaygroundSupport
  import XCTest
  
  class UsefulXCTests: XCTestCase
  {
     func testShouldPass() {
        let emojiStr = "Swift😄"
        XCTAssertEqual("\(emojiStr.unicodeScalars.count)", "6", "String Length Test")
     }
  }
  
  TestRunner().runTests(testClass: UsefulXCTests.self)
  
  class PlaygroundTestObserver: NSObject, XCTestObservation
  {
       @objc func testCase(_ testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: Int) {
          print("Failed on line \(lineNumber): \(testCase.name), \(description)")
       }
  }
  
  let observer = PlaygroundTestObserver()
  let center = XCTestObservationCenter.shared
  center.addTestObserver(observer)

Testing Asynchronous Functions With XCTest

As mentioned earlier, we'll do some fairly complex tests in this post. So now let's look at how to test asynchronous functions. Asynchronous functions are the backbone of the internet because most web apps or mobile apps do API calls. These API endpoints are connected to the database and get data from it or put data in it. The API calls generally take one to two seconds because the data is located on another server. For this time, the program doesn't stop and execute other code after it.

First, let's update our UsefulXCTests.playground file. Here, we'll add a function called msgAfterDelay in which we're expecting a message as a String, time as Double, and a callback function. This function calls the callback function after the time delay.

To test the function msgAfterDelay, we have a testdelay function inside our class UsefulXCTests. Here, we'll need to first create an exception, which is exp in our case. After that, we'll call the msgAfterDelay function with a message of theMessage and a time of 1 second. The callback function called callbackFn will pass the message.

In this message, we'll check with the XCTAssertEqual function. We need to complete the exception with the fulfill() function. Also notice that we need a waitForExpectations method with a timeout. This timeout should be greater than the timeout given in the msgAfterDelay function.


  func msgAfterDelay(message: String, time: Double, callbackFn: @escaping (String)->()) {
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time) {
       callbackFn(message)
   }
}
…
func testDelay() {
   let exp = expectation(description: "\(#function)")
   msgAfterDelay(message: "theMessage", time: 1.0, callbackFn: {message in
      XCTAssertEqual(message, "theMessage")
      exp.fulfill()
   })
   waitForExpectations(timeout: 3.0)
}
Testing Asynchronous Functions With XCTest

We also have a variety of exceptions where we use the XCTestExpectation(). In this, the exp is an array, which means we can check more exceptions. So here, the waitForExpectations method is also changed to the wait method. We'll then need to pass the array of exp in for the parameter.

Also notice that we're checking the emojiStr here again. This tells us that the asynchronous code is tested, along with other simple synchronous code.


  func testDelay() {
    let emojiStr = "Swift😄"
    let exp = XCTestExpectation(description: "\(#function)")
    msgAfterDelay(message: "theMessage", time: 1.0, callbackFn: {message in
       XCTAssertEqual(message, "theMessage")
       exp.fulfill()
    })
    wait(for: [exp], timeout: 3.0)
    XCTAssertEqual("\(emojiStr.unicodeScalars.count)", "6", "String Length Test")
  }

Debugging Test Setup

Sometimes the test cases don't run as expected, and we get errors that we don't understand. In those cases, we need to debug a test. The debugging feature is available in Xcode projects, so we'll use the Kingfisher app as our starting point.

Debugging Test Setup

Next, we'll add a variable called color with the value purple in the existing KingFisherTest.swift file. We've also added a simple basicColor function, which has a switch statement matching the color. Next, we'll use an XCTAssertEqual function to check if the color is red.


  public let color = "purple"
  ...
  @discardableResult
   func basicColor(completion: Completion = nil) -> Self {
     switch color{
      case "blue": print("Color is blue")
      case "purple": print("Color is purple")
      case "brown": print("Color is brown")
      default : print("No color matched")
    }
    XCTAssertEqual("Color is \(color)", "Color is red", "Color Test")
    return self
  }

Now we need to include this new function in BasicTests.swift for it to run:


  KingFisherTest().basicColor()
Now we need to include this new function in BasicTests.swift for it to run:

When we run the test, it fails, which was intentional.

When we run the test, it fails, which was intentional.

Debugging the Test

Suppose in our above test, we don't know why it failed. To learn more, we'll go to the debug tab by clicking on it in the top-left menu. Next, click on the + sign in the bottom-left corner. And then click Test Failure Breakpoint.

This will show that a breakpoint has been created with the name Test Failure. This is a special breakpoint that monitors our test cases for any exceptions when they run.

This will show that a breakpoint has been created with the name Test Failure.

Now, when we run the test case again, it doesn't throw any error. But it shows the exact reason and the place where there is an issue.

Using this, we can understand the source of the error and add a normal breakpoint with a print statement for the color variable.

Using this, we can understand the source of the error and add a normal breakpoint with a print statement for the color variable.

Now, when we run the test case again, we'll see the color variable is wrong in the console.

We can then rectify this, and our test case runs successfully.

We can then rectify this, and our test case runs successfully.

What We've Learned

In this post, we've talked about unit test cases and how to perform them in Xcode using XCTest. We also learned to do complex XCTest cases example for asynchronous functions using XCTestExpectation in the Playground. And after that, we learned to debug a failing test for an app in Xcode.

But testing the network calls using XCTestExpectation is still a difficult task. Instead of using this or other code, we can use Waldo to perform such tests. You're only required to provide the APK or IPA file. Waldo automatically generates test cases and emails you.

Automated E2E tests for your mobile app

Waldo provides the best-in-class runtime for all your mobile testing needs.
Get true E2E testing in minutes, not months.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.