January 15, 2013

Automating iOS Unit Tests

3Pillar’s expertise in designing, developing, and deploying mobile apps for the iPhone and iPad has given us the opportunity to develop a number of internal best practices for testing mobile apps. Testing is a vitally important phase of the development life cycle, as it accelerates time-to-market and increases a team’s velocity when done well. To deploy native iOS applications for any large or small enterprise client, running automated unit tests is very useful. This helps mobile developers to create an app which is easy to use, maintainable and extensible.

This blog post provides a method to enable iOS unit tests to be run from the command line. Mobile app developers now have the ability to run unit tests before a build is produced on Hudson. The solution was developed for libraries as well as apps, so you can be more confident when making changes to shared code (provided they have unit tests of course). In addition to running the tests, the reporting mechanism is built into Hudson, as we have employed a Ruby script that converts OCUnit output into a JUnit friendly XML document.

This method is a combination of solutions from Raingrove, RedGreenFactor, and StackOverflow.

Setting up the build environment

This section is for setup on a build machine. If you are only setting up your project for unit tests, please refer to the next section.

1. Download and install ios-sim.

In short, ios-sim is a command line application that allows you to launch an app via the iOS simulator without opening Xcode.
You can then pass command line and environment variables to the app.

  • From the directory containing a Rakefile, run this to build the ios-sim application: rake install prefix=/usr/local/
  • The executable “ios-sim” will then be created in this directory: YOUR_PATH/ios-sim/build/Release

2. Download and install OCUnit2JUnit.

  • The script can be downloaded here: OCUnit2JUnit.
  • After unzipping, the ruby script will be found here: YOUR_PATH/OCUnit2JUnit/bin/ocunit2junit.rb

3. Add ios-sim and ocunit2junit.rb to your path

  • Modify your bash profile so these tools will always be available: vi ~/.bash_profile
  • Add these lines to the file:
  • After saving, exit your terminal session and restart tomcat.

Setting up Xcode projects to run unit tests from the command line
App or Library Projects

1. Create a new scheme for the unit test target naming it “UnitTests” (convention).

2. Edit the scheme and check the test target’s Run box under Build.

3. Go to Manage Schemes… and check “Share”.

4. Select the test target and then the Build Phases tab.

5. Open the Run Script and replace “${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests” with  “${PROJECT_DIR}/RunUnitTests.rb”

6. Add “i386” to Valid Architectures to this and any targets that UnitTests depend on.

7. Set “Build Active Architecture” to “No” for the library.

8. Place the RunUnitTests Ruby script below into the .xcodeproj’s directory:

#!/usr/bin/env ruby
system("echo Running Build Script")
    launcher_path = "ios-sim"
    test_bundle_path = File.join(ENV['BUILT_PRODUCTS_DIR'], "#{ENV['PRODUCT_NAME']}.#{ENV['WRAPPER_EXTENSION']}")
    environment = {
        'DYLD_INSERT_LIBRARIES' => "/../../Library/PrivateFrameworks/IDEBundleInjection.framework/IDEBundleInjection",
        'XCInjectBundle' => test_bundle_path,
        'XCInjectBundleInto' => ENV["TEST_HOST"]
    environment_args = environment.collect { |key, value| "--setenv #{key}=\"#{value}\""}.join(" ")
    app_test_host = File.dirname(ENV["TEST_HOST"])
    system("echo #{launcher_path} launch \"#{app_test_host}\" #{environment_args} --args -SenTest All #{test_bundle_path}")
    system("#{launcher_path} launch \"#{app_test_host}\" #{environment_args} --args -SenTest All #{test_bundle_path}")
    puts "SL_RUN_UNIT_TESTS not set - Did not run unit tests!"

9. Change the file permissions of RunUnitTests.rb (if needed): chmod 755 RunUnitTests.rb

10. Use this command to build and run the tests (apps only):

xcodebuild -sdk iphonesimulator -scheme UnitTests clean build SL_RUN_UNIT_TESTS=YES 2>&1 | ocunit2junit.rb

Library Projects Continued

The above setup only works because an app exists for the unit tests to be injected into. When using the same configuration shown above on a library, you will get a build error because xcodebuild won’t find an app.

To run unit tests for a library project, follow the above steps for an app/library, as well as making these additional configurations:

1. Create a new target as an empty application with no unit tests, naming it “UnitTestsApp”.

  • Select the app target → Build Phases.
    • ​Add the library as a target dependency.
    • Link the app target’s binary with the library (the .a file).
  • Select the unit test target → Build Phases.
    • Remove the library target dependency.
    • Add the app as a target dependency.
    • Go to Build Settings.
    • Set Bundle Loader to:
    • $(BUILT_PRODUCTS_DIR)/UnitTestsApp.app/UnitTestsApp  
    • Set Test Host to:

2. Any target that links its binary to the library’s .a file must be configured with special linker flags and header search path.

In this example, the app target was linked manually, so its flags/paths definitely need to be configured.
While the unit test target came automatically linked, its flags and paths still need to be manually configured:

Go to Build Settings.

  • Set Other Linker Flags to:
  • -ObjC -all_load
  • Set Header Search Paths to:
  • $(SRCROOT)/Libraries/**
  • Unselect the Header Search Paths field and then double-click it again to set it to ‘recursive’.
  • Repeat a-d for each target linking its binary to the library

3. The library’s unit tests can now be run just like an app’s unit tests:

xcodebuild -sdk iphonesimulator -scheme UnitTests clean build SL_RUN_UNIT_TESTS=YES 2>&1 | ocunit2junit.rb

Hudson Job Configurations

  1. Check the Publish JUnit test result report box under Post-build Actions.
  2. After the Test reports XML field appears, enter this: