Error handling and tests

Whenever there’s an error, the SDK returns a CustomerIOError instance. For now, the SDK doesn’t handle errors for your app; you need to handle errors on your own.

 Our mobile SDKs are in alpha and subject to change

While we’re very excited about our mobile SDKs, they’re still works in progress! If you want to try them out, contact product@customer.io

This page is part of an introductory series to help you get started with the essential features of our SDK. The highlighted step(s) below are covered on this page. Before you continue, make sure you've implemented previous features—i.e. you can't identify people before you initialize the SDK!

graph LR getting-started(Install SDK) -->B(Initialize SDK) B --> identify(identify people) identify -.-> track-events(Send events) identify -.-> push(Receive push) identify -.-> rich-push(Receive Rich Push) track-events --> testing-error-handling(handle errors) push --> testing-error-handling rich-push --> testing-error-handling click getting-started href "/docs/sdk/ios/getting-started" click B href "/docs/sdk/ios/getting-started/#initialize-the-sdk" click identify href "/docs/sdk/ios/identify" click track-events href "/docs/sdk/ios/track-events/" click push href "/docs/sdk/ios/push" click rich-push href "/docs/sdk/ios/rich-push" click testing-error-handling href "/docs/sdk/ios/testing-error-handling" style testing-error-handling fill:#B5FFEF,stroke:#007069

The CustomerIOError

The CustomerIOError class helps you understand what went wrong and suggests how to handle the error.

To learn more about error handling, see all of the different types of cases of the CustomerIOError class. Also, see our example code giving suggestions on how to handle the various errors.

let error: CustomerIOError = ...

switch error {
case .http(let httpError):
    // An error happened while performing a HTTP request. 
    // `httpError` is an instance of `HttpRequestError` and can also be parsed:
    switch httpError {
    ...
    }
    break
case .notInitialized:
    // SDK has not been initialized yet. Check the docs for `CustomerIO` class.
    break 
...
}

Tests

We designed the SDK with first-class support for automated testing, making it easy to inject dependencies and perform mocking in your code.

Dependency injection

Every SDK class inherits from a Swift protocol. Inherited protocols use a consistent naming convention: <NameOfClass>Instance. For example, the CustomerIO class inherits the protocol CustomerIOInstance.

If you want to inject a class in your project, it could look something like the example below.

import CioTracking

class ProfileRepository {
    
    private let cio: CustomerIOInstance

    init(cio: CustomerIOInstance) {
        self.cio = cio
    }

    // Now, you can call call any of the `CustomerIO` class functions with `self.cio`!
    func loginUser(email: String, password: String, onComplete: @escaping (Result<Success, Error>) -> Void) {
        // login the user to your system. If successful, 
        self.cio.identify(identifier: email) { result in 
            // handle `result` of identify() call. 
        }
    }
}

// Provide an instance of the `Tracking` class to your class:
let cio = CustomerIO(...)
let repository = ProfileRepository(cio: cio)

Mocking

The Customer.io SDK comes bundled with mock classes ready for you to use. That’s right, we generated mocks for you!

Mock classes follow the naming convention: <NameOfClass>Mock. For example, mock the CustomerIO class with CustomerIOMock. See our github repository for a list of mock classes.

Here’s an example test class showing how you would test your ProfileRepository class.

import Foundation
import CioTracking
import XCTest

class ProfileRepositoryTest: XCTestCase {
    private var cioMock: CustomerIOMock!
    private var repository: ProfileRepository!

    override func setUp() {
        super.setUp()

        cioMock = CustomerIOMock() // Create a new instance of the mock in setUp() to reset the mock. 

        repository = ProfileRepository(cio: cioMock)
    }

    func test_loginUser() {
        // Because the `identify()` function returns a result, you must return a result from the mock 
        // using the onComplete callback. 
        cioMock.identifyBodyClosure = { identifier, body, onComplete, _ in 
            // You can return a successful result:
            onComplete(Result.success(Void()))
            // Or, return an error. Like here when a request couldn't be made possibly because of a network error. 
            onComplete(Result.failure(CustomerIOError.http(.noResponse)))
        }

        // Now, call your function under test:
        repository.loginUser(...)

        // You can access many properties of the mock class to assert the behavior of the mock. 
        XCTAssertTrue(cioMock.mockCalled)
        XCTAssertEqual(cioMock.identifyBodyCallsCount, 1)
        XCTAssertEqual(cioMock.identifyBodyReceivedInvocations[0].identifier, expectedIdentifier) 
    }
}
Copied to clipboard!