Skip to content

Unit Testing in SwiftUI Apps

This article explains how to perform unit testing in SwiftUI apps. It covers creating tests, naming conventions, testing functions that throw errors, testing asynchronous functions, and running code before and after tests.

Creating Tests

Create a new Target of type Unit Testing.

Naming Conventions

yml
Naming Structure:
    test_UnitOfWork_StateUnderTest_ExpectedBehavior
    test_[struct or class]_[var or func]_[expected result]
Testing Structure:
    Given, When, Then

Example

swift
import XCTest
@testable import YourProjectName

final class DateUtils_Tests: XCTestCase {
    func test_DateUtils_formatNoteDate_shouldOnlyHaveTime() {
        // Given date within today
        ...

        // When
        ...

        // Then
        ...
    }
}

Testing Functions That Throw Errors

swift
func test_HomeViewModel_saveItem_shouldThrowError_noData() {
    // Given
    let vm = HomeViewModel(isPremium: Bool.random())

    // When
    let loopCount: Int = Int.random(in: 1..<100)
    for _ in 0..<loopCount {
        vm.addItem(item: UUID().uuidString)
    }

    // Then
    do {
        try vm.saveItem(item: "")
    } catch let error {
        let returnedError = error as? HomeViewModel.DataError
        XCTAssertEqual(returnedError, HomeViewModel.DataError.noData)
    }
}

Testing Asynchronous Functions

swift
func test_HomeViewModel_downloadWithEscaping_shouldReturnItems() {
    // Given
    let vm = HomeViewModel(isPremium: Bool.random())

    // When
    let expectation = XCTestExpectation(description: "Should return items after 3s")

    vm.$dataArray
        .dropFirst()
        .sink { _ in
            expectation.fulfill()
        }
        .store(in: &cancellables)
    vm.downloadWithEscaping()

    // Then
    wait(for: [expectation], timeout: 5)
    XCTAssertGreaterThan(vm.dataArray.count, 0)
}

Running Code Before and After Tests

swift
final class HomeViewModel_Test: XCTestCase {
    // 1. Declare optional variables
    var viewModel: HomeViewModel?

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
        // 2. Initialize variables before each test
        viewModel = HomeViewModel(isPremium: Bool.random())
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        // 3. Clean up variables after each test
        viewModel = nil
    }

    func test_HomeViewModel_dataArray_shouldAddItems() {
        // Given
        // 4. Use variables in the test
        guard let vm = viewModel else {
            return
        }

        // When
        let itemNumber = Int.random(in: 1..<10)
        for _ in 0..<itemNumber {
            vm.addItem(item: "hello")
        }
        // Then
        XCTAssertEqual(vm.dataArray.count, itemNumber)
    }
}

References

Unit Testing a SwiftUI application in Xcode | Advanced Learning #17 - YouTube