Keep It Functional - An iOS Functional Testing Framework

6218
917
Objective-C

Build Status CocoaPod Version

IMPORTANT! Even though KIF is used to test your UI, you need to add it to your Unit Test target, not your UI Test target. The magic of KIF is that it allows you to drive your UI from your unit tests and reap all the advantages of testing in-process.

KIF iOS Integration Testing Framework

KIF, which stands for Keep It Functional, is an iOS integration test framework. It allows for easy automation of iOS apps by leveraging the accessibility attributes that the OS makes available for those with visual disabilities.

KIF builds and performs the tests using a standard XCTest testing target. Testing is conducted synchronously in the main thread (running the run loop to force the passage of time) allowing for more complex logic and composition. This also allows KIF to take advantage of the Xcode Test Navigator, command line build tools, and Bot test reports.

KIF uses undocumented Apple APIs. This is true of most iOS testing frameworks, and is safe for testing purposes, but it’s important that KIF does not make it into production code, as it will get your app submission denied by Apple. Follow the instructions below to ensure that KIF is configured correctly for your project.

Actively supports Xcode 11.6 and iOS 11-13 if you need support for an earlier version please use v3.7.9 or an earlier release.

Features

Minimizes Indirection

All of the tests for KIF are written in Objective-C. This allows for maximum integration with your code while minimizing the number of layers you have to build.

Easy Configuration

KIF integrates directly into your Xcode project, so there’s no need to run an additional web server or install any additional packages.

Wide OS and Xcode coverage

KIF’s test suite is being run against iOS 8+ and Xcode 7+. Lower versions will likely still work, but your mileage may vary. We do our best to retain backwards compatibility as much as possible.

Test Like a User

KIF attempts to imitate actual user input. Automation is done using tap events wherever possible.

Automatic Integration with Xcode Testing Tools

You can easily run a single KIF test with the Test Navigator or kick off nightly acceptance tests with Bots.

See KIF in Action

KIF uses techniques described below to validate its internal functionality. You can see a test suite that exercises its entire functionality by simply building and testing the KIF scheme with ⌘U. Look at the tests in the “Tests” group for ideas on how to build your own tests.

Installation (with CocoaPods)

CocoaPods are the easiest way to get set up with KIF.

The first thing you will want to do is set up a test target you will be using for KIF. You may already have one named MyApplication_Tests if you selected to automatically create unit tests. If you did, you can keep using it if you aren’t using it for unit tests. Otherwise, follow these directions to create a new one.

Select your project in Xcode and click on “Add Target” in the bottom left corner of the editor. Select iOS -> Test -> iOS Unit Testing Bundle. Give it a product name like “Acceptance Tests”, “UI Tests”, or something that indicates the intent of your testing process.

The testing target will add a header and implementation file, likely “Acceptance_Tests.m/h” to match your target name. Delete those.

Once your test target set up, add the following to your Podfile file. Use your target’s name as appropriate.

target 'Your Apps' do
  ...
end

target 'Acceptance Tests' do
  pod 'KIF', :configurations => ['Debug']
end

After running pod install complete the tasks in Final Test Target Configurations below for the final details on getting your tests to run.

Installation (from GitHub)

To install KIF, you’ll need to link the libKIF static library directly into your application. Download the source from the kif-framework/KIF and follow the instructions below. The screenshots are from Xcode 6 on Yosemite, but the instructions should be the same for Xcode 5 or later on any OS version.

We’ll be using a simple project as an example, and you can find it in Documentation/Examples/Testable Swift in this repository.

Simple App

Add KIF to your project files

The first step is to add the KIF project into the ./Frameworks/KIF subdirectory of your existing app. If your project uses Git for version control, you can use submodules to make updating in the future easier:

cd /path/to/MyApplicationSource
mkdir Frameworks
git submodule add https://github.com/kif-framework/KIF.git Frameworks/KIF

If you’re not using Git, simply download the source and copy it into the ./Frameworks/KIF directory.

Add KIF to Your Workspace

Let your project know about KIF by adding the KIF project into a workspace along with your main project. Find the KIF.xcodeproj file in Finder and drag it into the Project Navigator (⌘1).

Added KIF to the project

Create a Testing Target

You’ll need to create a test target for your app. You may already have one named MyApplicationTests if you selected to automatically create unit tests when you created the project. If you did, you can keep using it if you aren’t using it for unit tests. Otherwise, follow these directions to create a new one.

Select your project in Xcode and click on “Add Target” in the bottom left corner of the editor. Select iOS -> Test -> iOS Unit Testing Bundle. Give it a product name like “Acceptance Tests”, “UI Tests”, or something that indicates the intent of your testing process.

The testing target will add a header and implementation file, likely “Acceptance_Tests.m/h” to match your target name. Delete those.

Configure the Testing Target

Now that you have a target for your tests, add the tests to that target. With the project settings still selected in the Project Navigator, and the new integration tests target selected in the project settings, select the “Build Phases” tab. Under the “Link Binary With Libraries” section, hit the “+” button. In the sheet that appears, select “libKIF.a” and click “Add”. Repeat the process for CoreGraphics.framework and QuartzCore.framework.

KIF requires the IOKit.framework, but it is not located with the other system frameworks. To link to it, add “-framework IOKit” to the “Other Linker Flags” build setting.

Add libKIF library screen shot

Add libKIF library screen shot

KIF takes advantage of Objective C’s ability to add categories on an object, but this isn’t enabled for static libraries by default. To enable this, add the -ObjC flag to the “Other Linker Flags” build setting on your test bundle target as shown below.

Add category linker flags screen shot

Read Final Test Target Configurations below for the final details on getting your tests to run.

Installing Accessibility Identifier Tests

Normally you identify a UI element via its accessibility label so that KIF simulates the interactions of a real user as closely as possible. In some cases, however, you may have to use accessibility identifiers, which are not exposed to users. If using CocoaPods, install the additional identifier-based Tests via the Identifier CocoaPods subspec:

pod 'KIF/IdentifierTests'

If not using CocoaPods, the identifier-based Tests can be added by including “KIFUITestActor-IdentifierTests.h”.

Final Test Target Configurations

You need your tests to run hosted in your application. Xcode does this for you by default when creating a new testing bundle target, but if you’re migrating an older bundle, follow the steps below.

First add your application by selecting “Build Phases”, expanding the “Target Dependencies” section, clicking on the “+” button, and in the new sheet that appears selecting your application target and clicking “Add”.

Next, configure your bundle loader. In “Build Settings”, expand “Linking” and edit “Bundle Loader” to be $(TEST_HOST). Expand the “Testing” section and edit “Test Host” to be $(BUILT_PRODUCTS_DIR)/MyApplication.app/MyApplication where MyApplication is the name of your app. Also make sure that “Wrapper Extension” is set to xctest.

The last step is to configure your unit tests to run when you trigger a test (⌘U). Click on your scheme name and select “Edit Scheme…”. Click on “Test” in the sidebar followed by the “+” in the bottom left corner. Select your testing target and click “OK”.

Example test cases

With your project configured to use KIF, it’s time to start writing tests. There are two main classes used in KIF testing: the test case (KIFTestCase, subclass of XCTestCase) and the UI test actor (KIFUITestActor). The XCTest test runner loads the test case classes and executes their test. Inside these tests, the tester performs the UI operations which generally imitate a user interaction. Three of the most common tester actions are “tap this view,” “enter text into this view,” and “wait for this view.” These steps are included as factory methods on KIFUITestActor in the base KIF implementation.

KIF relies on the built-in accessibility of iOS to perform its test steps. As such, it’s important that your app is fully accessible. This is also a great way to ensure that your app is usable by everyone. Giving your views reasonable labels is usually a good place to start when making your application accessible. More details are available in Apple’s Documentation.

The first step is to create a test class to test some functionality. In our case, we will create a login test (LoginTests). Create a new class that inherits from KIFTestCase. You may have to update the import to point to <KIF/KIF.h>. The test method name provides a unique identifier. Your KIFTestCase subclass should look something like this:

LoginTestCase.h

#import <KIF/KIF.h>

@interface LoginTests : KIFTestCase
@end

LoginTestCase.m

#import "LoginTests.h"
#import "KIFUITestActor+EXAdditions.h"

@implementation LoginTests

- (void)beforeEach
{
    [tester navigateToLoginPage];
}

- (void)afterEach
{
    [tester returnToLoggedOutHomeScreen];
}

- (void)testSuccessfulLogin
{
    [tester enterText:@"[email protected]" intoViewWithAccessibilityLabel:@"Login User Name"];
    [tester enterText:@"thisismypassword" intoViewWithAccessibilityLabel:@"Login Password"];
    [tester tapViewWithAccessibilityLabel:@"Log In"];

    // Verify that the login succeeded
    [tester waitForTappableViewWithAccessibilityLabel:@"Welcome"];
}

@end

Most of the tester actions in the test are already defined by the KIF framework, but -navigateToLoginPage and -returnToLoggedOutHomeScreen are not. These are examples of custom actions which are specific to your application. Adding such steps is easy, and is done using a factory method in a category of KIFUITestActor, similar to how we added the scenario.

KIFUITestActor+EXAdditions.h

#import <KIF/KIF.h>

@interface KIFUITestActor (EXAdditions)

- (void)navigateToLoginPage;
- (void)returnToLoggedOutHomeScreen;

@end

KIFUITestActor+EXAdditions.m

#import "KIFUITestActor+EXAdditions.h"

@implementation KIFUITestActor (EXAdditions)

- (void)navigateToLoginPage
{
    [self tapViewWithAccessibilityLabel:@"Login/Sign Up"];
    [self tapViewWithAccessibilityLabel:@"Skip this ad"];
}

- (void)returnToLoggedOutHomeScreen
{
    [self tapViewWithAccessibilityLabel:@"Logout"];
    [self tapViewWithAccessibilityLabel:@"Logout"]; // Dismiss alert.
}

@end

Everything should now be configured. When you run the integration tests using the test button, ⌘U, or the Xcode Test Navigator (⌘5).

Use with other testing frameworks

KIFTestCase is not necessary for running Tests. Tests can run directly in XCTestCase or any subclass. The basic requirement is that when you call tester or system, self must be an instance of XCTestCase and you must call KIFEnableAccessibility in setUp.

For example, the following Specta test works without any changes to KIF or Specta:

#import <Specta.h>
#import <KIF.h>

SpecBegin(App)

describe(@"Tab controller", ^{

  it(@"should show second view when I tap on the second tab", ^{
    [tester tapViewWithAccessibilityLabel:@"Second" traits:UIAccessibilityTraitButton];
    [tester waitForViewWithAccessibilityLabel:@"Second View"];
  });

});

SpecEnd

If you want to use KIF with a test runner that does not subclass XCTestCase, your runner class just needs to implement the KIFTestActorDelegate protocol which contains two required methods.

  • (void)failWithException:(NSException *)exception stopTest:(BOOL)stop;
  • (void)failWithExceptions:(NSArray *)exceptions stopTest:(BOOL)stop;

In the first case, the test runner should log the exception and halt the test execution if stop is YES. In the second, the runner should log all the exceptions and halt the test execution if stop is YES. The exceptions take advantage of KIF’s extensions to NSException that include the lineNumber and filename in the exception’s userData to record the error’s origin.

Use with Swift

Since it’s easy to combine Swift and Objective-C code in a single project, KIF is fully capable of testing apps written in both Objective-C and Swift.

If you want to write your test cases in Swift, you’ll need to keep two things in mind.

  1. Your test bundle’s bridging header will need to #import <KIF/KIF.h>, since KIF is a static library and not a header.
  2. The tester and system keywords are C preprocessor macros which aren’t available in Swift. You can easily write a small extension to XCTestCase or any other class to access them:
extension XCTestCase {
    func tester(file : String = #file, _ line : Int = #line) -> KIFUITestActor {
        return KIFUITestActor(inFile: file, atLine: line, delegate: self)
    }

    func system(file : String = #file, _ line : Int = #line) -> KIFSystemTestActor {
        return KIFSystemTestActor(inFile: file, atLine: line, delegate: self)
    }
}

extension KIFTestActor {
    func tester(file : String = #file, _ line : Int = #line) -> KIFUITestActor {
        return KIFUITestActor(inFile: file, atLine: line, delegate: self)
    }

    func system(file : String = #file, _ line : Int = #line) -> KIFSystemTestActor {
        return KIFSystemTestActor(inFile: file, atLine: line, delegate: self)
    }
}

See Documentation/Examples/Testable Swift for sample code.

Troubleshooting

Simulator launches but app doesn’t appear, steps time out after 10 seconds

This issue occurs when XCTest does not have a valid Test Host. Reread the instructions above with regards to the “Bundle Loader” and “Test Host” settings. You may have missed something.

Step fails because a view cannot be found

If KIF is failing to find a view, the most likely cause is that the view doesn’t have its accessibility label set. If the view is defined in a xib, then the label can be set using the inspector. If it’s created programmatically, simply set the accessibilityLabel attribute to the desired label.

If the label is definitely set correctly, take a closer look at the error given by KIF. This error should tell you more specifically why the view was not accessible. If you are using -waitForTappableViewWithAccessibilityLabel:, then make sure the view is actually tappable. For items such as labels which cannot become the first responder, you may need to use -waitForViewWithAccessibilityLabel: instead.

Unrecognized selector when first trying to run

If the first time you try to run KIF you get the following error:

2011-06-13 13:54:53.295 Testable (Integration Tests)[12385:207] -[NSFileManager createUserDirectory:]: unrecognized selector sent to instance 0x4e02830
2011-06-13 13:54:53.298 Testable (Integration Tests)[12385:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSFileManager createUserDirectory:]: unrecognized selector sent to instance 0x4e02830'

or if you get another “unrecognized selector” error inside the KIF code, make sure that you’ve properly set the -ObjC flag as described above. Without this flag your app can’t access the category methods that are necessary for KIF to work properly.

Continuous Integration

A continuous integration (CI) process is highly recommended and is extremely useful in ensuring that your application stays functional. The easiest way to do this will be with Xcode, either using Bots, or Jenkins or another tool that uses xcodebuild. For tools using xcodebuild, review the manpage for instructions on using test destinations.

Contributing

We’re glad you’re interested in KIF, and we’d love to see where you take it.

Any contributors to the master KIF repository must sign the Individual Contributor License Agreement (CLA). It’s a short form that covers our bases and makes sure you’re eligible to contribute.

When you have a change you’d like to see in the master repository, send a pull request. Before we merge your request, we’ll make sure you’re in the list of people who have signed a CLA.

Thanks, and happy testing!