56

I would like my app to run special code (e.g. resetting its state) when running in UI Testing mode. I looked at environment variables that are set when the app is running from UI Testing and there aren't any obvious parameters to differentiate between the app running normally vs in UI Testing. Is there a way to find out?

Two workarounds that I'm not satisfied with are:

  1. Set XCUIApplication.launchEnvironment with some variable that I later check in the app. This isn't good because you have to set it in the setUp method of each test file. I tried setting the environment variable from the scheme settings but that doesn't propagate to the app itself when running UI Testing tests.
  2. Check for the lack of existence of the environment variable __XPC_DYLD_LIBRARY_PATH. This seems very hacky and might only be working now because of a coincidence in how we have our target build settings set up.
Liron Yahdav
  • 10,152
  • 8
  • 68
  • 104

8 Answers8

60

I've been researching this myself and came across this question. I ended up going with @LironYahdav's first workaround:

In your UI test:

- (void)setUp
{
    [super setUp];

    XCUIApplication *app = [[XCUIApplication alloc] init];
    app.launchEnvironment = @{@"isUITest": @YES};
    [app launch];
}

In your app:

NSDictionary *environment = [[NSProcessInfo processInfo] environment];
if (environment[@"isUITest"]) {
    // Running in a UI test
}

@JoeMasilotti's solutions are useful for unit tests, because they share the same runtime as the app being tested, but are not relevant for UI tests.

jnic
  • 8,695
  • 3
  • 33
  • 47
  • This does not seem to work for me in Xcode 7. Is there any other way around this? – nemesis Nov 17 '15 at 11:43
  • 4
    the launch environment is now NSDictionary nemesis. You can't have @YES now. You must instead use a string. – Joey Clover Nov 18 '15 at 16:29
  • 1
    swift version: `let isUITest = NSProcessInfo.processInfo().environment["isUITest"] != nil` – bbjay Aug 08 '16 at 16:58
  • My AppDelegate is written in Objective C and my tests are in Swift. That's the only solution that worked for me. – RawKnee Aug 14 '18 at 11:26
27

I didn't succeed with setting a launch environment, but got it to work with launch arguments.

In your tests setUp() function add:

let app = XCUIApplication()
app.launchArguments = ["testMode"]
app.launch()

In your production code add a check like:

let testMode =  NSProcessInfo.processInfo().arguments.contains("testMode")
if testMode {
  // Do stuff
}

Verified using Xcode 7.1.1.

shim
  • 9,289
  • 12
  • 69
  • 108
Ciryon
  • 2,637
  • 3
  • 31
  • 33
  • 4
    This doesn't seem to be working for me. After launching the app from within a UI test, the `NSProcessInfo` arguments don't contain the test string. Xcode 7.3b3. – Zev Eisenberg Feb 16 '16 at 22:49
  • Getting the same thing in 7.3. I set the launchArguments in setup, but if I break point on the next line, the launchArguments are empty. It's as if they are not being stored. – drekka Apr 08 '16 at 06:26
  • Launch Arguments are different than an environment I believe. According to the Apple docs... NSProcessInfo has two process information properties that we want to examine. The first is "arguments" : Array of string with the command-line arguments for the process The second is "environment": The variable names (keys) and their values in the environment from which the process was launched. You should note the key differences between these and that is "environment from which the process was launched" as opposed to "command line arguments". – TimD Sep 12 '16 at 18:12
  • so putting the launchArguments is valid (if launching from the command line) but you must also check for these launchArgs in your code. You cannot check the processInfo().environment dictionary and expect "arguments" to be inside them. That would be in the "arguments" property on the NSProcessInfo. Again see my comment above straight from the Apple Docs. – TimD Sep 12 '16 at 18:15
  • It might be a good idea to append the launch arguments instead of assigning to not overwrite other possible arguments: app.launchArguments.append("testMode") – trishcode Jun 13 '19 at 18:13
  • 1
    newer versions of Xcode/swift use `import Foundation` and `ProcessInfo.processInfo.arguments.contains("testMode")` to check for the flag in the app. This along with the above worked for me. – alexbhandari Jan 26 '20 at 03:06
  • Be sure to mark the reading of args with #if DEBUG #endif to avoid leaving open backdoors – Sentry.co Apr 02 '23 at 03:32
7

You can use Preprocessor Macros for this. I found that you have couple of choices:

New Target

Make a copy of the App's target and use this as the Target to be Tested. Any preproocessor macro in this target copy is accessible in code.

One drawback is you will have to add new classes / resources to the copy target as well and sometimes it very easy to forget.

New Build Configuration

Make a duplicate of the Debug build configuration , set any preprocessor macro to this configuration and use it for your test (See screenshots below).

A minor gotcha: whenever you want to record a UI Testing session you need to change the Run to use the new testing configuration.

Add a duplicate configuration:

Add a duplicate conf

Use it for your Test:

Use it for your *Test*

Koen.
  • 25,449
  • 7
  • 83
  • 78
Sandeep Chayapathi
  • 1,490
  • 2
  • 13
  • 31
6

Swift 3 based on previous answers.

class YourApplicationUITests: XCTestCase {

    override func setUp() {
        super.setUp()

        // Put setup code here. This method is called before the invocation of each test method in the class.

        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false
        // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
        let app = XCUIApplication()
        app.launchArguments = ["testMode"]
        app.launch()

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }

    func testExample() {
        // Use recording to get started writing UI tests.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

}


extension UIApplication {
    public static var isRunningTest: Bool {
        return ProcessInfo().arguments.contains("testMode")
    }
}

Then just call UIApplication.isRunningTest in your code.

GregP
  • 1,584
  • 17
  • 16
3

I've just added this extension

 @available(iOS 9, *)
 extension XCUIApplication {

 func test(){
   launchEnvironment = ["TEST":"true"]
   launch()
  }
 }

So I can just use test() instead of launch()

andrea sciutto
  • 196
  • 1
  • 2
3

In Swift 3 you can check for the XCInjectBundleInto key, or something that starts with XC.

let isInTestMode = ProcessInfo.processInfo.environment["XCInjectBundleInto"] != nil

This works in OS X as well.

Linus Oleander
  • 17,746
  • 15
  • 69
  • 102
1

My solution is almost identical to that of Ciryon above, except that for my macOS Document-based app I had to prepend a hyphen to the argument name:

let app = XCUIApplication()
app.launchArguments.append("-Testing")
app.launch() 

...otherwise, "Testing" ends up interpreted as the name of the document to open when launching the app, so I was getting an error alert:

enter image description here

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189
1

Not specific to UI tests, but tests in general, create an extension for ProcessInfo

public extension ProcessInfo {
    static var isTesting: Bool {
        processInfo.environment["XCTestConfigurationFilePath"] != nil
    }
}

Usage:

if ProcessInfo.isTesting {}
guard !ProcessInfo.isTesting else { return }
Evan
  • 101
  • 7