Ronald Mannak
Ronald Mannak

Reputation: 456

What is the proper way to detect if unit tests are running at runtime in Xcode?

When I'm running unit tests, I'd like to skip some code (e.g. I don't want [[UIApplication sharedApplication] openURL:..] to run). I'm looking for a runtime check if I'm currently running units tests or not.

I know I have seen code that checks the Objective-C runtime if unit tests are running but am not able to find it anymore.

Upvotes: 22

Views: 12276

Answers (7)

Jon Reid
Jon Reid

Reputation: 20980

Rather that sprinkling "am I testing?" conditionals throughout production code, I isolate the check to one place: main. There, I check for an alternate application delegate for testing. If it's available, I use it instead of the the regular application delegate. This completely bypasses the regular launch sequence:

int main(int argc, char *argv[])
{
    @autoreleasepool {
        Class appDelegateClass = NSClassFromString(@"TestingAppDelegate");
        if (!appDelegateClass)
            appDelegateClass = [AppDelegate class];
        return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass));
    }
}

You can read more about this technique here: How to Easily Switch Your iOS App Delegate for Testing

Upvotes: 13

Chuck H
Chuck H

Reputation: 8276

I'm not sure how long this will continue to work, but it works for me right now with Version 9.0 beta 6 (9M214v).

let isTesting = { () -> Bool in
    if let _ = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] {
        return true
    } else if let testingEnv = ProcessInfo.processInfo.environment["DYLD_INSERT_LIBRARIES"] {
        return testingEnv.contains("libXCTTargetBootstrapInject.dylib")
    } else {
        return false
    }
}()

No build environment or scheme changes are necessary.

It appears that there are two different environment variables in play depending on whether you are running a single test case or the entire test suite. Also, the variable value also differs depending whether or not you are running in simulator or on a real device.

Upvotes: 9

cvetomirst
cvetomirst

Reputation: 51

Just use this:

+ (BOOL)isUnitTestRunning
{
    Class testProbeClass;
    testProbeClass = NSClassFromString(@"XCTestProbe");
    return (testProbeClass != nil);
}

Upvotes: 3

david
david

Reputation: 61

I think you can check like this for Xcode 7.3

-(BOOL) isRunningUnitTests
{
    NSDictionary* environment = [ [ NSProcessInfo processInfo ] environment ];
    NSString* theTestConfigPath = environment[ @"XCTestConfigurationFilePath" ];
    return theTestConfigPath != nil;
}

Upvotes: 6

Gavin Bunney
Gavin Bunney

Reputation: 1240

The easiest (and working in Xcode 7 with XCTest!) way to check is to have a look at the process info for a matching xctest bundle:

static BOOL isRunningTests(void)
{
    NSDictionary* environment = [[NSProcessInfo processInfo] environment];
    NSString* injectBundle = environment[@"XCInjectBundle"];
    return [[injectBundle pathExtension] isEqualToString:@"xctest"];
}

Source: https://www.objc.io/issues/1-view-controllers/testing-view-controllers/#integration-with-xcode

Upvotes: 2

Bryan Chen
Bryan Chen

Reputation: 46598

You can use this method from google-toolbox-for-mac

// Returns YES if we are currently being unittested.
+ (BOOL)areWeBeingUnitTested {
  BOOL answer = NO;
  Class testProbeClass;
#if GTM_USING_XCTEST // you may need to change this to reflect which framework are you using
  testProbeClass = NSClassFromString(@"XCTestProbe");
#else
  testProbeClass = NSClassFromString(@"SenTestProbe");
#endif
  if (testProbeClass != Nil) {
    // Doing this little dance so we don't actually have to link
    // SenTestingKit in
    SEL selector = NSSelectorFromString(@"isTesting");
    NSMethodSignature *sig = [testProbeClass methodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
    [invocation setSelector:selector];
    [invocation invokeWithTarget:testProbeClass];
    [invocation getReturnValue:&answer];
  }
  return answer;
}

The reason that NSClassFromString and NSInvocation are used is to allow code compile without linking to xctest or ocunit

Upvotes: 17

Undo
Undo

Reputation: 25697

Select the project, and then the test target:

enter image description here

Select Build Settings and choose All and Combined. Type 'preproc' in the search box - you're after Preprocessor Macros.

enter image description here

Add a macro to the Debug configuration called TEST and set it equal to 1:

enter image description here

Then in your code, you can do this:

#ifndef TEST
    [[UIApplication sharedApplication] doEvilThingForTesting];
#endif 

Or if you have code that you want to only run in a test environment:

#ifdef TEST
    [[UIApplication sharedApplication] doAwesomeTestOnlyThing];
#endif 

It's not exactly runtime, but the unit tester compiles the code before it runs the tests IIRC, so it should be the same effect - you're essentially modifying the code right before running the tests.

Upvotes: 12

Related Questions