How to write your first Unit Test


You’re more of a video kind of person? I’ve got you covered! Here’s a video with the same content than this article 🍿


“How do I write my first Unit Test in Swift?” 🤔

If you’re new to Swift or iOS, you’re bound to ask yourself this question at some point!

So I made this article to guide you through that process in record time ⚡️

So let's get started!

I've implemented a simple ViewModel that allows me to store a list of people along with a list of filters, and then only display the people that satisfy all of these filters:

class PeopleViewModel {
    typealias PersonFilter = (Person) -> Bool

    var people: [Person] = []
    var filters: [PersonFilter] = []

    init(people: [Person], filters: [PersonFilter] = []) {
        self.people = people
        self.filters = filters
    }

    var peopleMatchingFilters: [Person] {
        var filteredPeople = people

        for filter in filters {
            filteredPeople = filteredPeople.filter(filter)
        }

        return filteredPeople
    }
}

And now I want to write some tests to make sure that my ViewModel does behave like I expect it to.

For this I'm going to move into my testing target.

(If you don't already have a testing target in your project, you can create one by clicking on this "+" icon right here, and then adding a new Unit Testing Bundle to your project)

You can notice that the Swift file that will contain my testing code looks a little bit different than the ones I have in my app:

import XCTest
@testable import YourFirstUnitTest

final class PeopleViewModelTests: XCTestCase {

}
  • it imports XCTest, which is Xcode's testing framework

  • it does an @testable import of MyApp: this is important because this @testable import will allow me to access the properties of my ViewModel that have an internal visibility and would otherwise be inaccessible outside MyApp

  • finally, it defines a subclass of XCTestCase, which is the base class we need to use to write our tests

But before we start writing our first test, we're first going to add a static constant that will hold the data we're going to use in all our tests:

import XCTest
@testable import YourFirstUnitTest

final class PeopleViewModelTests: XCTestCase {

    static let people = [
        Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
        Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
        Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
        Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
        Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
        Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
        Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true),
    ]
}

And now we're all set to implement our first test!

To implement a test, we simply start by defining a new method, whose name begins with "test" and then describes the behavior we're testing.

For this first test, I want to test that my ViewModel behaves correctly when I give it a filter for the property firstName, so I write the name of the test accordingly:

func testFirstNameFilter() {

}

Then, I'm going to structure the body of my test into three parts: Given, When and Then:

func testFirstNameFilter() {
    // Given
    let peopleViewModel = PeopleViewModel(people: PeopleViewModelTests.people)

    // When
    peopleViewModel.filters = [{ $0.firstName == "Alex" }]

    // Then
    let expectedResult = [
        Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
        Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
        Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
    ]

    XCTAssertEqual(peopleViewModel.peopleMatchingFilters, expectedResult)
}

In the "Given" part, I set up the context, meaning I create my ViewModel with its initial data

In the "When" part, I perform the action I want to test: here I set up my filter.

Finally, in the "Then" part, I check that my ViewModel has behaved as I expected, by comparing the result it gives with my expected result

And that’s it, I can now run my test to make sure that my ViewModel is indeed behaving as I expect it to 👌

Of course, one test is not enough to cover all the possible situations, so let’s write a few more tests!

Using the same approach than for the first test, I’m now going to write a test for the case where I set multiple filters:

func testMultipleFilters() {
    // Given
    let peopleViewModel = PeopleViewModel(people: PeopleViewModelTests.people)

    // When
    peopleViewModel.filters = [
        { $0.firstName == "Alex" },
        { $0.hasDriverLicense == false },
        { $0.isAmerican == true },
    ]

    // Then
    let expectedResult = [
        Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
        Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
    ]

    XCTAssertEqual(peopleViewModel.peopleMatchingFilters, expectedResult)
}

Then, I’m going to write a couple more tests that will cover the edge cases, because it’s often there that you find some bugs!

First, I’ll write a test for the case where a combination of filters produces no result:

func testNoResultFilters() {
    // Given
    let peopleViewModel = PeopleViewModel(people: PeopleViewModelTests.people)

    // When
    peopleViewModel.filters = [
        { $0.firstName == "Alex" },
        { $0.hasDriverLicense == false },
        { $0.isAmerican == false },
    ]

    // Then
    XCTAssertEqual(peopleViewModel.peopleMatchingFilters, [])
}

Then I’ll write another test for the case where I set no filters:

func testNoFilters() {
    // Given
    let peopleViewModel = PeopleViewModel(people: PeopleViewModelTests.people)

    // When
    peopleViewModel.filters = []

    // Then
    let expectedResult = PeopleViewModelTests.people

    XCTAssertEqual(peopleViewModel.peopleMatchingFilters, expectedResult)
}

To finish, I’m going to run all the tests to make sure that my ViewModel passes all of them:

And that’s it: we’ve implemented our first unit tests 🥳

As we’ve seen, the idea behind a unit test is pretty straightforward: we simply write code that asserts that under a given situation, our code does behave like we expect it to behave!

That’s all for this article, I hope you’ve enjoyed it!


You’ve enjoyed this article and you want to support my content creation?

You can leave me a tip ☺️ 👇

Buy Me A Coffee

Here’s the full code if you want to experiment with it!

// Person.swift

import Foundation

struct Person: Equatable {
    let firstName: String
    let lastName: String
    let age: Int
    let hasDriverLicense: Bool
    let isAmerican: Bool
}

// PeopleViewModel.swift

import Foundation

class PeopleViewModel {
    typealias PersonFilter = (Person) -> Bool

    var people: [Person] = []
    var filters: [PersonFilter] = []

    init(people: [Person], filters: [PersonFilter] = []) {
        self.people = people
        self.filters = filters
    }

    var peopleMatchingFilters: [Person] {
        var filteredPeople = people

        for filter in filters {
            filteredPeople = filteredPeople.filter(filter)
        }

        return filteredPeople
    }
}

// PeopleViewModelTests.swift

import XCTest
@testable import YourFirstUnitTest

final class PeopleViewModelTests: XCTestCase {

    static let people = [
        Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
        Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
        Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
        Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
        Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
        Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
        Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true),
    ]

    func testFirstNameFilter() {
        // Given
        let peopleViewModel = PeopleViewModel(people: PeopleViewModelTests.people)

        // When
        peopleViewModel.filters = [{ $0.firstName == "Alex" }]

        // Then
        let expectedResult = [
            Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
            Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
            Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
        ]

        XCTAssertEqual(peopleViewModel.peopleMatchingFilters, expectedResult)
    }

    func testMultipleFilters() {
        // Given
        let peopleViewModel = PeopleViewModel(people: PeopleViewModelTests.people)

        // When
        peopleViewModel.filters = [
            { $0.firstName == "Alex" },
            { $0.hasDriverLicense == false },
            { $0.isAmerican == true },
        ]

        // Then
        let expectedResult = [
            Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
            Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
        ]

        XCTAssertEqual(peopleViewModel.peopleMatchingFilters, expectedResult)
    }

    func testNoResultFilters() {
        // Given
        let peopleViewModel = PeopleViewModel(people: PeopleViewModelTests.people)

        // When
        peopleViewModel.filters = [
            { $0.firstName == "Alex" },
            { $0.hasDriverLicense == false },
            { $0.isAmerican == false },
        ]

        // Then
        XCTAssertEqual(peopleViewModel.peopleMatchingFilters, [])
    }

    func testNoFilters() {
        // Given
        let peopleViewModel = PeopleViewModel(people: PeopleViewModelTests.people)

        // When
        peopleViewModel.filters = []

        // Then
        let expectedResult = PeopleViewModelTests.people

        XCTAssertEqual(peopleViewModel.peopleMatchingFilters, expectedResult)
    }
}
Previous
Previous

Are private properties really private? 🤨

Next
Next

Here are 5 tips you can start using today 😎