38

When creating a new project with unit tests, Xcode sets the build configuration to Debug for the Test scheme (same for the Run scheme).

Should I differentiate between Run (Command-R) & Test (Command-U) schemes?

I.e., should I create a new Build Configuration called Test, add a preprocessor macro TEST=1 to it, and use it as the build configuration for the Test scheme instead? Or, should I just keep Run & Test both as Debug?

I come from a Ruby/Rails background, where you usually have test, development, and production environments. It seems to me that Debug is like development and Release is like production, but we're missing a test, which is why I'm thinking it might make sense to add Test.

Comments? Opinions? Suggestions?

I'm specifically asking this because I want to compile something for Test with:

#ifdef TEST
// Do something when I test.
#endif

I don't think it matters if I also compile this for Debug. So, I really could just do:

#ifdef DEBUG
// Do something when I run or test.
#endif

But, I'm really only intending to do it for tests for now. So, that's why I'm thinking I should differentiate between debug & test but am wondering why Xcode doesn't do that for you by default? Does Apple think you shouldn't differentiate between them?

Eli Barzilay
  • 28,131
  • 3
  • 62
  • 107
ma11hew28
  • 106,283
  • 107
  • 420
  • 616

11 Answers11

33

Preprocessor macros will not work, you need to check the environment at runtime.

Objective-c

static BOOL isRunningTests(void)
{
    NSDictionary* environment = [[NSProcessInfo processInfo] environment];
    return (environment[@"XCTestConfigurationFilePath"] != nil);
}

Swift

var unitTesting : Bool 
{
    return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}

(Updated for Xcode 11)

BadPirate
  • 24,683
  • 10
  • 85
  • 118
Robert
  • 35,442
  • 34
  • 158
  • 205
  • blog post link is broken – Andrea Zonca Apr 04 '16 at 23:20
  • 2
    This environment variable is not being set in Xcode 7.3.1 – Ayush Goel May 06 '16 at 11:06
  • @AyushGoel - Thanks for the heads up. Best thing to do is to check the the contents of the environment dictionary. Seems like you can look for the `XCInjectBundleInto` key. Ill update this shortly. – Robert May 06 '16 at 11:20
  • Swift version for fast copy-paste: `func isRunningTests() -> Bool { let env: [String: String] = NSProcessInfo.processInfo().environment return env["XCInjectBundleInto"] != nil }` – Milan Cermak Jul 19 '16 at 14:24
30

You might consider adding a new build configuration.

In xcode 4, click on your project on the left hand navigator.

In the main window, click on your project, and then select the "info" tab.

Click the "+" button to add a new configuration (you can call yours "test" if you like").

Now, click on your target, and go to the build settings tab.

Search for "preprocessor macros"

Here, you can add preprocessor macros for your new build configuration.

Just double click on your new "test" configuration, and add TESTING=1.

Finally, edit your build scheme. Select the test options for your scheme. There should be a "Build Configuration" drop down menu. Select your "test" configuration.

David
  • 1,929
  • 19
  • 15
  • 4
    This is by far the best way to do this if you want to have preprocessor macros not only available in your test code but also in the regular app. Much cleaner than the other solutions using helper methods. Still works perfectly in Xcode6 – Lukas Gross Dec 09 '14 at 09:04
  • 3
    If you're using Cocoapods, make sure you run `pod install` after creating the new build configuration. – Snowman Oct 07 '15 at 15:09
25

Instead of creating a Test build configuration, I:

  1. created a Tests-Prefix.pch file:

    #define TEST 1
    #import <SenTestingKit/SenTestingKit.h>
    #import "CocoaPlant-Prefix.pch"
    
  2. entered its path in the Prefix Header field of the Tests target's build settings.

  3. added the following code to the top of a file I created called MyAppDefines.h, imported in MyApp-Prefix.pch:

    #ifdef TEST
    #define TEST_CLASS NSClassFromString(@"AppDelegateTests") // any test class
    #define BUNDLE [NSBundle bundleForClass:TEST_CLASS]
    #define APP_NAME @"Tests"
    #else
    #define BUNDLE [NSBundle mainBundle]
    #define APP_NAME [[BUNDLE infoDictionary] objectForKey:(NSString *)kCFBundleNameKey]
    #endif
    

This allows me to use BUNDLE where ever I mean [NSBundle mainBundle] and also have it work when I run Tests.

Importing SenTestingKit in Tests-Prefix.pch also speeds up the compiling of the SenTestingKit Framework and allows me to leave out #import <SenTestingKit/SenTestingKit.h> from the top of all the tests files.

ma11hew28
  • 106,283
  • 107
  • 420
  • 616
  • 13
    Adding a #define TEST 1 to my unit test target preprocessor macro sets the macro within the executing unit test code, but not in the code in the app that is being unit tested. Any idea how I can accomplish this? – shawnwall May 01 '12 at 21:13
  • 3
    Facing the same issue as @shawnwall : the TEST variable isn't in the context of the compiler when compiling the app. It's there just when compiling the test case itself. – yonel Jun 07 '12 at 12:28
  • 2
    Did you guys ever figure this out? Having the same problem. – marklar Jul 30 '13 at 19:23
  • Found an answer below to these comment-questions: http://stackoverflow.com/a/13928458/623999 . – jab Mar 25 '14 at 16:40
  • 1
    Another thing that works in XCTest is that `[UIApplication sharedApplication]` returns nil during testing. I can't imagine another scenario where that would be nil, so I've used it as a testing flag before (please comment if I'm wrong!). – jab Mar 25 '14 at 16:53
  • I found David's answer to be far simpler: http://stackoverflow.com/a/14718914/2069 – Chris Gillum May 15 '14 at 05:02
16

I decided to add a check for an environment variable in the code itself, instead of using the isRunningTests() suggestion Robert made.

  1. Edit the current Scheme (Product/Scheme/Edit Scheme) or Command+<
  2. Click on the Test configuration
  3. Click on Arguments Uncheck "Use the Run action's arguments and environment variables
  4. Expand Environment Variable section and add the variable TESTING with value YES
  5. Add this to your code somewhere and call is whenever you need to:
 + (BOOL) isTesting
    {
        NSDictionary* environment = [[NSProcessInfo processInfo] environment];
        return [environment objectForKey:@"TESTING"] != nil;
    }

The screen should look like this when you are done.

The screen should look like this

The code above will find the TESTING environment variable when running in test mode or application mode. This code goes in your application, not the unit test files. You can use

#ifdef DEBUG
...
#endif

To prevent the code from being executed in production.

Vivo
  • 758
  • 1
  • 7
  • 20
focused4success
  • 363
  • 2
  • 10
5

Robert's answer in SWIFT 3.0:

func isRunningTests() -> Bool {
    let environment = ProcessInfo().environment
    return (environment["XCInjectBundleInto"] != nil);
}
Houman
  • 58,044
  • 75
  • 235
  • 407
4

If you create a Test build configuration and then set the "Other Swift Flags" property of your Target to "-DTEST" it will define a TEST macro that will work in your swift code. Make sure you set it in the build settings of your App target so that you can use it in your App's Swift code.

Other Swift Flags setting for DEBUG and TEST

Then with this set, you can test your code like so:

func testMacro() {
   #if !TEST 
       // skipping over this block of code for unit tests
   #endif
}
ucangetit
  • 2,445
  • 24
  • 19
3

I test such a long time, found a result:

Not only you add the preproessor macro into your unit test target(You could have many methods using variables for unit testing only, and follow the @MattDiPasquale methods),

but also You must add the condition complie file in your test target. we should recomplie this file, because you have a new preprocessor macro for this file, but this file has built in the application target that time your preprocessor macro didn't set.

Hope this help you.

Community
  • 1
  • 1
regrecall
  • 491
  • 1
  • 5
  • 20
  • So in other words, for any file in the main target where you want the `TEST` preprocessor macro to be defined during testing, add that file to the "Compile Sources" list under "Build Phases" in your test target. – jab Mar 25 '14 at 16:42
2

Updated for Xcode10:

static let isRunningUnitTests: Bool = {
    let environment = ProcessInfo().environment
    return (environment["XCTestConfigurationFilePath"] != nil)
}()
raf
  • 2,339
  • 17
  • 19
0

Look at environment variables to see if unit tests are running. Similar to Robert's answer but I only check once for performance sake.

+ (BOOL)isRunningTests {
   static BOOL runningTests;
   static dispatch_once_t onceToken;

   // Only check once
   dispatch_once(&onceToken, ^{
      NSDictionary* environment = [[NSProcessInfo processInfo] environment];
      NSString* injectBundle = environment[@"XCInjectBundle"];
      NSString* pathExtension = [injectBundle pathExtension];
      runningTests = ([pathExtension isEqualToString:@"octest"] ||
                      [pathExtension isEqualToString:@"xctest"]);
   });
   return runningTests;
}
kev
  • 7,062
  • 10
  • 26
  • 40
0

On iOS, [UIApplication sharedApplication] will return nil when a unit test is running.

Frank Schmitt
  • 24,850
  • 8
  • 56
  • 69
0

Modified version of Kev's answer that works for me on Xcode 8.3.2

+(BOOL)isUnitTest {

    static BOOL runningTests;
    static dispatch_once_t onceToken;

    // Only check once
    dispatch_once(&onceToken, ^{
        NSDictionary* environment = [[NSProcessInfo processInfo] environment];
        if (environment[@"XCTestConfigurationFilePath"] != nil && ((NSString *)environment[@"XCTestConfigurationFilePath"]).length > 0) {
            runningTests = true;
        } else {
            runningTests = false;
        }
    });
    return runningTests;
}
Fresh One
  • 150
  • 1
  • 9