Custom build configurations for testing and debugging in iOS/Xcode
Tags: swift, ios
Published: 2021-04-28 17:46:24

---
title: Custom build configurations for testing and debugging in iOS/Xcode
published: true
series: Learning Swift
description: Custom build configurations for testing and debugging in iOS/Xcode
tags: swift, ios
---

When debugging one of my unit tests I coincidentally noticed that the complete start-up procedure/code was running before the unit tests get executed. With my web development background, this was somehow surprising to me as I expected only the class/method... referenced by the test suite being run. After taking some time to think about it this makes a lot of sense as the tests are run e.g. on a simulator where the application first needs to be installed and started before the tests run.  
This means that any side effects at application start e.g. an HTTP call is also triggered when any unit test gets executed. Even though this did not lead to any unwanted side effects in my particular case I felt uneasy about the requests being performed. So I was looking for some means to exchange the "production" implementation with a version suitable for test runs or debugging.

For the unit tests, I already relied on constructor-based dependency injection to pass stubs/mocks to avoid unwanted side effects and/or return test data. However, this only covered the dependencies for the subject under test, but not whatever is executed at application start.
Having dependency injection already in place I just required some means to "determine" at compile and/or runtime if the problematic dependencies should be replaced by e.g. a test- or in-memory version.  
The idea was to define a "mode" like `test` or `production` which could be derived and set at application start.

# Build Configuration based dependency injection

When creating a new project in XCode it also defines build configurations like `DEBUG` and `RELEASE` which can be used in combination with preprocessor macros to include different code sections depending on the configuration the build is executed with.  
This seems to be often used e.g. to exclude SwiftUI previews from release builds or define a different `PersistenceController` when working with `CoreData`.  
[Schemes](https://stackoverflow.com/questions/20637435/xcode-what-is-a-target-and-scheme-in-plain-language) can then be used to set unique build configurations e.g. for `Run` or `Test`. This seemed to be sufficient to solve my test run dependency issues.

Using the `DEBUG` flag was one option but would also replace the release dependencies with the test/in-memory versions for debug runs on the simulator/real device (something I did not want). So a dedicated `Test` configuration would allow for having different dependencies injected during debug- and test runs.  
As it turned out creating and using such a custom configuration requires three steps:

1. Creating a custom build configuration and configuring the Active Compilation Conditions
2. Injecting dependencies based on the configuration selected
3. Creating/updating the Xcode `scheme` for `Run` and `Test`

## Build configurations and Active Compilation Conditions

First a new custom `Build Configuration` is required, which can be created in the project settings `Info` tab by duplicating from the e.g. `Debug` configuration:

![Test Build Configuration](https://github.com/remshams/xcode-custom-build-configurations/blob/images/images/test-build-configuration.png?raw=true)

Next a new entry for `Active Compilations Conditions` needs to be added for `Test` which can be done in the `Build Settings` Tab. It made sense to me to set `TEST` as well as `DEBUG` for the `Test` build configuration to make sure everything applied to `Debug` is also applied for `Test` (in addition to what is defined for `Test`).

![Custom Active Compilations Conditions](https://github.com/remshams/xcode-custom-build-configurations/blob/images/images/active-compilation-conditions.png?raw=true)

Having it setup like this the `TEST` flag is now available (in addition to `DEBUG` and `RELEASE`) for usage in preprocessor macros.

## Build Configuration based injection

Using macros alone, it is already possible to switch between different dependencies based on the active build configuration:

```swift
private static func makeTodosRepository() -> TodosRepository {
  #if TEST
  return InMemoryTodosRepository()
  #else
  return ProductionTodosRepository(host: "https://jsonplaceholder.typicode.com")
  #endif
}
```

I did not want to use a macro wherever configuration specific code is required. So I next created a swift enum reflecting all application `mode`s. The mode is determined at startup and made available as `shared` global property (same as it is often done e.g. for the `PersistenceController` and `CoreData`):

```swift
enum ApplicationMode: String {
  case release
  case test
  case debug
}


#if TEST
let mode = ApplicationMode.test
#elseif DEBUG
let mode = ApplicationMode.debug
#else
let mode = RunningMode.release
#endif
```

To me, the approach has the advantage that Swift language features can be used when working with the configuration. It also decouples the application running mode from the build configurations (in case necessary for any reason).

The global available application `mode` property can next be combined with any dependency inject approach (e.g. like the [Single-container approach](https://www.raywenderlich.com/books/advanced-ios-app-architecture/v3.0)), restricting the configuration dependant implementations to a few dedicated classes:

```swift
protocol TodosRepository: ListTodos {}

class AppContainer: ObservableObject {
  private let todosRepository: TodosRepository
  private let todosContainer: TodosContainer

  init() {
    todosRepository = AppContainer.makeTodosRepository()
    todosContainer = AppContainer.makeTodosContainer(listTodos: todosRepository)
    Logger().debug("Application running in mode: \(mode.rawValue)")
  }

  func makeTodosContainer() -> TodosContainer {
    todosContainer
  }

  private static func makeTodosRepository() -> TodosRepository {
    switch mode {
    case .test:
      return InMemoryTodosRepository()
    default:
      return ProductionTodosRepository(host: "https://jsonplaceholder.typicode.com")
    }
  }

  private static func makeTodosContainer(listTodos: ListTodos) -> TodosContainer {
    TodosContainer(listTodos: listTodos)
  }
}

extension ProductionTodosRepository: TodosRepository {}
extension InMemoryTodosRepository: TodosRepository {}
```

## Schemes

To make it all work for test runs the build configuration is set to `Test` for the `Test` step:

![Test step scheme](https://github.com/remshams/xcode-custom-build-configurations/blob/images/images/schema-test.png?raw=true)

If everything has been wired up correctly the application should now use the `Test` dependencies on test startup (at least mine did ;) ).

# Conclusion

The short article described how to exchange dependencies based on tags by:

- Defining a custom build configuration and `Active Compilation Conditions` property
- Injecting dependencies based on the application running `mode` derived from the active build configuration
- Creating/Updating a scheme to run the tests with the custom build configuration

This now allows for having different dependencies during test and debugging sessions. In case of interest, a small example application using the approach can be found on [Github](https://github.com/remshams/xcode-custom-build-configurations).

# Environment Variables

As explained [here](https://stackoverflow.com/a/24344459) exchanging dependencies only requires different behavior and not different compilation, so environment variables read at runtime could be a solution as well.  
I opted against such an approach as the variables are set for all build configurations and therefore need to be manually switched on/off depending e.g. when running tests or debugging.  
Besides the advantage of not having to recompile the application when changing the variable was not relevant for my use case/problem.