Xcode / iOS: Unit Tests, Schemes, and Configurations - ios

My iOS project has five schemes: Local Development, Integration, QA, Demo, and Production. Each scheme uses a differing configuration to control things like network poll frequency, API endpoints, analytics, and so on.
Similarly, we have five corresponding targets: Local Development, Integration, QA, Demo, and Production. Each target has several User-Defined Build Settings, which contain API keys, numeric values for timing, etc.
Our application's Info.plist file uses application variables such as ${SOME_ENDPOINT_URL} to draw in the corresponding User-Defined Build Settings.
To retrieve the variables, I do something like the following:
[[[NSBundle mainBundle] infoDictionary] valueForKey:#"Some Endpoint URL"]
That would correspond to the User-Defined Build Setting, like this:
"Some Endpoint URL" = ${SOME_ENDPOINT_URL}
I am now looking at how to configure the project appropriately to perform unit and logic tests.
To build out the tests to determine if the environments are configured correctly, I'm not certain what the best practice is.
Is it correct to build out five additional Test-specific targets for each environment?
Or is it better to override the "Run action's arguments" setting for the test component for each scheme, and provide something like an argument to specify which scheme we are looking at?
Are there any existing references for configuring unit and logic tests for iOS projects with multiple environments? This project's complexity seems to exceed the scope of most documentation.

The following are what I do.
Info.plist
Create a master Info.plist file
Write a run script (Shell Script) for each scheme to generate a environment-specific Info.plist by modifying settings in the master Info.plist (Use PlistBuddy -c)
Add the run script to Build Phases (above "Compile Sources")
.h file
Define configuration settings in a .h file (e.g. config.h)
#if defined (CONFIG_FILE)
#import CONFIG_FILE
#endif
Import config.h in your code
Use pre-processor macros for each scheme to select the target .h file.
-DCONFIG_FILE=local-env-config.h

Related

How to access xcconfig variables from XCUITest code

I need to access an environment variable named ENV that is set in the xcconfig file for my main app target, but from inside my UI test target.
After doing a ton of googling this is what I've tried:
set the main xcconfig as the configuration for the UITest target
add an environment variable ENV=$(ENV) in the test plan configuration
print the environment dictionary like this
print(ProcessInfo.processInfo.environment)
But $(ENV) is not expanding to the real value.
I also tried adding a custom entry in the Info.plist for the UITest target
<key>ENV</key>
<string>$(ENV)</string>
And then accessing it via Bundle.main.infoDictionary but again I just get the string $(ENV). Any idea what I am missing, or is this just impossible because of the way UI tests work in Xcode?
What you have to do is to either use the app target's xcconfig as the build config for your test target. Or if you need to have specific test target's build config, you include the app's xcconfig into your test target xcconfig.
Then in your test plan settings you define your test plan environment var as ENV=$(ENV)
Basically:
apptarget.xcconfig:
ENV=appenv
testtarget.xcconfig:
#include apptarget.xcconfig
in testPlan environment you define:
ENV=$(ENV)
XCConfig is used during build time. The $(ENV) has to be expanded in build time to the value you want. You will accomplish that by either using the apptarget.xcconfig as the xcconfig of your test arget or including it into your testarget.xcconfig

Multiple Configuration Files Per Target

In Xcode I am able to assign a single xcconfig file to my project and targets:
Is there any way to assign more than one xcconfig file to a target? In the dropdown it only allows a single selection.
I understand that I am able to override build settings from my project configuration file in my target configuration file, I'm up against a problem where I may need to have separate configuration for environments (dev, QA, prod) and separate configurations for different "flavours" of the app so being able to have multiple configuration files per target seems like a potential solution.
Configuration files support the #include statement so you could build your higher-level files from one-or-more lower-level files.

Set build variable in scheme instead of target

I am currently working on an iOS project in XCode that uses configurations to specify the different API environments my app can connect to. Additionally, I use targets to override one of my project configuration's user-defined values to specify a particular configuration file to use in the app. However, this is the only value that changes in the target. I work with multiple different configuration files (maybe 10 to 20 at a time) and creating a new target for each file to update one value seems clunky.
My question: Is there a way to pass this one value in from the scheme instead of setting it in the target?
I have seen that there is a pre-build script that can be run but I have not yet had any success exporting environment variables.
CONFIG_FILE="My Config File"
export CONFIG_FILE
I have also seen that some people have had success using PlistBuddy to write the values into the info.plist file during the pre-build phase of the scheme. This may be an option as well although it would require I redo a lot of my build process. I wanted to see if there was any other options before heading down this path.
Thanks for the help.
I was able to do this by using a .xcconfig file that is updated during my pre-build action in my scheme.
I used this tutorial to learn how to set up the project: http://www.jontolof.com/cocoa/using-xcconfig-files-for-you-xcode-project/

Which one is the best practice for multiple environment settings in Xcode for iOS project?

Till now, I know there a couple of solutions for multiple environment settings.
Solution 1: Prepare different plist files for the different environment. Then, use Add Run Script Build Phase to copy the specific plist file to overwrites project plist file.
Solution 2: In PROJECT -> Info -> Configurations, duplicate a existing configuration. setup scheme -> Info -> Build Configuration. Then, in the targets -> Build settings, add user-defined settings.
$(kBaseURL)
Solution 3: Add preprocessor macros in PROJECT -> Build Settings to define the environment. Then, in source code write the macro to switch environment setting value.
#ifdef DEBUG_ENV
#define kBaseURL #"http://debug-server.a.com/api/"
#else
#define kBaseURL #"http://production-server.a.com/api/"
#endif
There are several other ways to switch environments settings.
Which way is the best practice to do this?
I think this is also a question of taste and of what you are actually trying to achieve.
For example you might want to have a debug configuration that does not talk to the internet and rather uses HTTP stubs to mock the requests. This won't be possible by simply copying plists.
Personally I prefer creating build configurations for each environment, then set a compiler flag for each config that switches precompiler macros. I also like to take some more tricky steps to modify the bundle id and app name for all configs and then create a build scheme for every config. This results in me being able to build all environments from Xcode and install all apps on the same device, as the bundle ids are different. It's a bit tricky to set up though. However once it runs: perfect for my use cases :)

iOS Settings Bundle with items that appear only in Debug builds

I have user setting that I want to have available in settings, but only if it is a debug build, or perhaps it is one of two separate targets.
In the settings bundle, I have the item in Root.plist, but I want it to only be visible if it is a debug build.
Is there any way to make something conditional in the settings bundle?
I know that I can use separate settings bundles for two different build targets, but that would require me to maintain two separate settings bundles, and that seems pretty messy.
Any ideas?
You could have a Settings.plist with everything in it, then add a build step for the target where you don't want to expose one of the settings. In that build step, use PlistBuddy to remove the setting you don't want.
This has the advantage of letting you use the great tools Xcode provides for editing your plist, while remaining DRY.
As far as I'm aware, there is no way to put conditionals (or, for that matter, any form of code) in the settings bundle. You could create an in-app settings page that is only displayed in debug mode or something of the sort though.
Edit your schema to run a script as one of the build pre-actions. The script can generate Root.plist and and include the extra settings or not as appropriate. Although you could use whatever language you want for your generator script (Ruby, Tcl, Forth,...), I would just use the C Pre-processor.
Specifically,
Create a file, Root.plist.orig which includes all the regular settings. Include your extra settings wrapped in #ifdef DEBUG and #endif
Create a build run-script pre-action in your schema. The script should consist of a shell command to run the pre-processor. So something like (I don't have the details 100% correct): cpp -DDEBUG Root.plist.orig > Root.plist
In you application delegate, read the plist and put into a dictionary. Then use a compile time flag for the DEBUG build, which turns on some code that adds or modifies the plist turned dictionary. From then on don't access the plist directly, interface to the dictionary through a method in the app delegate.
EDIT: write some command line level software to take your standard plist, and based on the build configuration, change it, then output it back as a plist that then is copied into your app bundle, as a build phase. You may be able to do this with standard system cli functions, or if you do it in a custom mac cli program, its a simple thing to construct.

Resources