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

7

22

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.

Mochun answered 11/7, 2014 at 0:45 Comment(2)
Check thisThreatt
This is what mock is for.Cumulate
C
17

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

Carver answered 11/7, 2014 at 1:22 Comment(2)
Works for iOS as wellSunset
I would suggest using [testProbeClass valueForKey:@"isTesting"]; to avoid NSInvocation.Crabby
M
13

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

Midwifery answered 22/8, 2015 at 4:5 Comment(0)
C
12

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.

Chouest answered 11/7, 2014 at 1:12 Comment(1)
it works but it is also restricted to code compiled with tests, for instance the target application code is not affected (i.e. your app delegate has the macro not defined). My way to solve this was to duplicate DEBUG configuration and set the macro there as well and then edited my scheme to use this new configuration when testing.Sanatorium
T
9

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.

Tophole answered 11/9, 2017 at 21:7 Comment(2)
FYI, this no longer works using Xcode Version 12.0 beta 1 (12A6159).Tophole
In Xcode 14.2 I see the following keys in environment to indicate the code is running in a unit test: XCInjectBundleInto, XCTestSessionIdentifier and XCTestBundlePathShed
H
6

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;
}
Headsail answered 24/3, 2016 at 20:26 Comment(1)
nope... Not working on xcode 7.3 and also on xcode 8Veneaux
F
2

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

Fayfayal answered 18/1, 2016 at 22:1 Comment(1)
In Xcode 14.2, I see XCInjectBundleInto (not XCInjectBundle). There's also XCTestSessionIdentifier which seems a bit clearer for detecting unit tests.Shed
C
2

Just use this:

+ (BOOL)isUnitTestRunning
{
    Class testProbeClass;
    testProbeClass = NSClassFromString(@"XCTestProbe");
    return (testProbeClass != nil);
}
Calycine answered 17/5, 2017 at 11:22 Comment(1)
Some additional context or explanation would be helpful to explain why this answer is the best solution.Interminable

© 2022 - 2024 — McMap. All rights reserved.