How to Write Unit Tests in Xcode for iOS Projects: A Quick Guide

Written by maxkalik | Published 2023/01/30
Tech Story Tags: xcode | xctest | unit-testing | ios | ios-development | mobiledebugging | testing-ios-apps | software-development

TLDRRecently I conducted several tech interviews and noticed one curious thing. More and more engineers (even middle and seniors) neglect writing tests. They were great candidates, but they didn’t write tests at all. So, let this article be here just for those who need to start writing tests right now. via the TL;DR App

I think it’s unnecessary to start this article with how important it is to write tests — you know it better than me. Also, I won’t go to dive into the theory of patterns and approaches. My goal is just to show you how easy and quickly you can start writing tests today.

To avoid turning this article for the sake of the article itself, I will explain why I decided to prepare this small tutorial.

Recently I conducted several tech interviews and noticed one curious thing. More and more engineers (even middle and seniors) neglect writing tests. They were great candidates, but they didn’t write tests at all. So, let this article be here just for those who need to start writing tests right now.

Stay tuned.

Prerequisites

  1. You use at least Xcode 13
  2. You have an existing iOS project
  3. You have what to test, of course

Create Unit Testing Bundle target

In your existing project, we need to create a new target — Unit Testing Bundle.

  1. Go to the project navigator and select the project where you want to add a target.
  2. In the list of targets, press +
  3. Find Unit Testing Bundle
  4. Press Next.

Name your product. A target to be tested should be your app or framework.

If you don’t see your target in the list of targets, then we need to add it manually using the manage targets option.

Quick overview of a template

When you open a file with the test, you will see XCTestCase template with a few methods. We need only testExample(). Let’s run it using a rectangle on the left side of this method.

You will see a green checkmark. Your console log will be filled with information about when the test started, how a lot it takes, and so on. But let’s open Test navigator using CMD + 6. You will see the hierarchy of tests and the same green checkmarks.

Green checkmarks mean — your tests are passed. Let’s make our own now.

Create your first Unit test

You testExample() method let’s add a following code and run it using triangle.

func testExample() throws {
    let expr1 = "expression 1"
    let expr2 = expr1
    XCTAssertEqual(expr1, expr2, "Expressions are equal")
}

In this implementation, we used a specific assertion which is a part of XCTest framework. Let’s try another example. For that, you can create testExample2().

func testExample2() throws {
    let expr1 = "expression 1"
    let expr2 = "expression 2"
    XCTAssertNotEqual(expr1, expr2, "Expressions are not equal")
}

Since we have several tests, you would want to run them all. For that, you can use either the Test navigator or the class declaration line.

As you can see XCTest framework provides a lot of asserts for all your general test cases:

// Bool assert TRUE
XCTAssertTrue(expression, "description")

// Bool assert FALSE
XCTAssertFalse(expression, "description")

// Assert that expression is nil
XCTAssertNil(expression, "description")

// Assert that expression is NOT nil
XCTAssertNotNil(expression, "description")

// Assert that an expression is not nil and returns the unwrapped value
XCTUnwrap(expression, "description")

// Asserts that two expressons have the same value or not
XCTAssertEqual(expr1, expr2, "description")
XCTAssertNotEqual(expr1, expr2, "description")

// expr1 > expr2
XCTAssertGreaterThat(expr1, expr2, "description")

// expr1 < expr2
XCTAssertLessThat(expr1, expr2, "description")

// expr1 <= expr2
XCTAssertLessThanOrEqual(expr1, expr2, "description")

// Asserts taht two expressions have the same value within a certain accuracy
XCTAssertEqualWithAccuracy(expr1, expr2, accuracy, "description")

// Asserts that two floating-point values are equal within a specified accuracy.
XCTAssertEqual(49.2827, 49.2826, accuracy: 0.001)

Name your test well

Let’s say you have a model:

struct SignUpFormModel {
    let firstName: String
    let lastName: String
    let email: String
    let password: String
    let repeatPassword: String
}

Where you would have a method to validate email:

extension SignUpFormModel {
    func isValidEmailFormat() -> Bool {
        return email.contains("@") && email.contains(".")
    }
}

Ok, now it’s time to make a more professional test, but before that to make it more professional let’s think about naming. To name your tests properly is very important to get used to this from the very beginning. Let’s consider this name:

testSignUpFormModel_WhenCreated_EmailShouldHaveValidFormat()

If to break it you can see several parts:

test<Subject><Condition Or State Change><Expected Result>()

The name starts with test — it’s important to start from this word, otherwise, XCTest won’t recognize your methods (you won’t see this triangle icon). Other parts describe your test almost in detail. So, if you will make yourself keep using this naming format then your colleagues sooner or later will say thank you in some time.

Create Unit test using Arrange, Act, Assert (AAA) pattern

Okay, we have an empty test; let’s fill it using a simple pattern called Arrange, Act, Assert.

  1. Arrange — in other words, we can call this step Given because it’s where you arrange your test data.
  2. Act can be called When means where you will call your unit — testable method.
  3. Assert — or Then. The place where you will assert the result describing this in detail. By the way — don’t be lazy to describe your test well.

func testSignUpFormModel_WhenCreated_EmailShouldHaveValidFormat() {
 
    // Arrange (GIVEN)
    let firstName = "Maksim"
    let lastName = "Kalik"
    let email = "[email protected]"
    let password = "qwerty123"
    let repeatPassword = "qwerty123"

    let signUpFormModel = SignUpFormModel(
        firstName: firstName,
        lastName: lastName,
        email: email,
        password: password,
        repeatPassword: repeatPassword
    )

    // Act (WHEN)
    let isEmailFormatValid = signUpFormModel.isValidEmailFormat()
 
    // Assert (THEN)
    XCTAssertTrue(isEmailFormatValid, "Provided valid email address does not have a valid format")
}

Now you can run your first beautiful test and see how it beautifully fit into the list of other tests.

Wrapping up

Your first unit test (at least using this described format) will be a great contribution to the project you are working on. If you want to see your project from another point of purpose to make it more robust, then it’s a good time to start writing tests, even if you are working in a startup. Just start from the simple and obvious part of your code, then slowly take other more complicated units to test. Good luck!

Links


Written by maxkalik | iOS Developer. Triumph contributor. Launched WordDeposit, Simple Ruler Apps.
Published by HackerNoon on 2023/01/30