Howto: Integrate KIF into an iOS project using single build target with CocoaPods?

Recently our project started to utilize CocoaPods for dependency management. We are also using KIF for our testing needs. While KIF is an amazing framework for iOS testing, it has a major flaw - either developers or QA engineers need to maintain a separate build target for KIF. Well, I've had it with constant build failures due to a file removal or addition to the target and I have decided to create a ruby script that will be executed as part of CocoaPods installation. Below is our Podfile that depicts our solution. This file will reconfigure your project based on the environment variable and will use *main* target for either KIF or regular build.
So, without further delay, the Podfile:

# Script variables: Change to your use case
$projectPath = "MyProject/MyProject/MyProject.xcodeproj"    # Location of the project (relative to this file)
$groupName = "UITests"                                      # Name of the group that will house KIF tests in the project tree
$testsFolder = "UITests"                                    # Name of the folder where KIF tests reside on the disk (relative to "$(SRCROOT)")
workspace "MyProject/MyProject.xcworkspace"
xcodeproj $projectPath
 
platform :ios, "6.0"
target :"MainTarget" do
    pod "TestFlightSDK"
    pod "Mixpanel"
    pod "CrittercismSDK", "~>4.1.2"
    if ENV['PODFILE_TYPE'] == "KIF"
        pod "KIF"
    end
end
 
target :"GHUnitTests" do
    pod "TestFlightSDK"
    pod "Mixpanel"
    pod "CrittercismSDK", "~>4.1.2"
end
 
post_install do |ir|
    project = Xcodeproj::Project.open($projectPath)
    group = getKIFGroup(project, $groupName)
    if not group
        return
    end
    puts "Removing KIF elements"
    removeKIF(project, group)
 
    if ENV['PODFILE_TYPE'] == "KIF"
        integrateKIFIntoProject(project, group)
    end
end
 
# Support methods
 
def removeFileFromBuildPhase(project, fileRef)
    project.targets.each do |target|
        target.build_phases.each do |buildPhase|
            buildPhase.files.each do |buildFile|
                if buildFile.display_name == fileRef.name
                    buildFile.remove_from_project
                end
            end
        end
    end
end
 
def addAllFiles(project, group)
    path = "**/#{$testsFolder}/**/*.{h,m,pch,plist}"
    files = Dir.glob(path)
 
    groupFiles = Array.new()
    files.each do |file|
        file.insert(0, Dir.pwd.concat("/"))
        cmd = "cd #{Dir.pwd} && git ls-files #{file} --error-unmatch > /dev/null 2>&1"
        if system(cmd)
            retVal = group.new_reference(file)
            if retVal.display_name.end_with?(*%w(.m .pch))
                puts "Adding '#{file}' to target."
                groupFiles << retVal
            end
        else
            puts "WARNING: File '#{$file}' is not tracked by git. Skipping."
        end
 
    end
 
    project.targets.first.add_file_references(groupFiles)
end
 
def removeFilesFromProject(project, mainGroup)
    mainGroup.groups.each do |group|
        group.files.each do |file|
            print "Removing file: #{file.display_name} from group: #{group.display_name}....."
            removeFileFromBuildPhase(project, file)
            puts "\tDone"
        end
    end
 
    mainGroup.files.each do |file|
        print "Removing file: #{file.display_name} from group: #{mainGroup.display_name}....."
        removeFileFromBuildPhase(project, file)
        puts "\tDone"
    end
 
    mainGroup.clear
end
 
def updateHeaderSearchPaths(target)
    target.build_configuration_list.build_configurations.each do |config|
        if config.build_settings["HEADER_SEARCH_PATHS"].index($testsFolder) == nil
            print "Updating 'HEADER_SEARCH_PATHS' by adding '$(SRCROOT)/#{$testsFolder}/**' for build configuration '#{config.display_name}'....."
            config.build_settings["HEADER_SEARCH_PATHS"] << "$(SRCROOT)/#{$testsFolder}/**"
            puts "\tDone"
        end
    end
end
 
def getKIFGroup(project, groupName)
    project.groups.each do |group|
        if group.name === groupName
            return group
        end
    end
    return nil
end
 
def removeKIF(project, group)
    if not group.empty?
        puts "About to perform group cleanup..."
        removeFilesFromProject(project, group)
        puts "Cleanup complete..."
        project.save()
    end
end
 
def integrateKIFIntoProject(project, group)
    main_target = project.targets.first
    updateHeaderSearchPaths(main_target)
 
    puts "About to add #{$testsFolder} files to project..."
    addAllFiles(project, group)
    puts "File addition complete"
    project.save()
end

Now that you have modified this Podfile to fit your needs you are ready to configure your project:
Run the following command to configure project for development and normal execution mode:

pod install

and to configure project for KIF test development and KIF testing run:

env PODFILE_TYPE="KIF" pod install

Questions and comments are welcomed!

Comments