NSURL to file path in test bundle with XCTest

I am trying to write an iOS app using TDD and the new XCTest framework. One of my methods retrieves a file from the internet (given a NSURL object) and stores it in the user’s documents. The signature of the method is similar to:

- (void) fetchAndStoreImage:(NSURL *)imageUrl

I’m trying to write the test for this method in a way that it doesn’t fail if there is no connection to the internet. My approach (taken from a previous question) is to call the method using a NSURL to an image in the local file system.

  • Singleton not working with XCTests - iOS
  • XCTest: Load file from disk without Bundle on all platforms (Xcode, SPM Mac/Linux)
  • API violation - multiple calls made to -
  • Run XCTest bundle with simctl
  • How do I unit test HTTP request and response using NSURLSession in iOS 7.1?
  • Unit testing Parse framework iOS
  • When a new project with unit tests enabled is created, the Tests directory has a subdirectory named ‘Supporting Files’. I suppose that is where my test images should go. My question is how can I get a NSURL object that points to an image in this directory, as I wouldn’t want test images to be bundled with the application. Any help is appreciated.

    6 Solutions Collect From Internet About “NSURL to file path in test bundle with XCTest”

    In fact, the [NSBundle mainBundle] when running a UnitTest is not the path of your app, but is /Developer/usr/bin, so this will not work.

    The way to get resources in a unit test is here: OCUnit & NSBundle

    In short, use:

    [[NSBundle bundleForClass:[self class]] resourcePath]
    

    or in your case:

    [[NSBundle bundleForClass:[self class]] resourceURL]
    

    Swift 2:

    let testBundle = NSBundle(forClass: self.dynamicType)
    let fileURL = testBundle.URLForResource("imageName", withExtension: "png")
    XCTAssertNotNil(fileURL)
    

    Swift 3:

    let testBundle = Bundle(for: type(of: self))
    let fileURL = testBundle.url(forResource: "imageName", withExtension: "png")
    

    Bundle provides ways to discover the main and test paths for your configuration:

    @testable import Example
    
    class ExampleTests: XCTestCase {
    
        func testExample() {
            let bundleMain = Bundle.main
            let bundleDoingTest = Bundle(for: type(of: self ))
            let bundleBeingTested = Bundle(identifier: "com.example.Example")!
    
            print("bundleMain.bundlePath : \(bundleMain.bundlePath)")
            // …/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Agents
            print("bundleDoingTest.bundlePath : \(bundleDoingTest.bundlePath)")
            // …/PATH/TO/Debug/ExampleTests.xctest
            print("bundleBeingTested.bundlePath : \(bundleBeingTested.bundlePath)")
            // …/PATH/TO/Debug/Example.app
    
            print("bundleMain = " + bundleMain.description) // Xcode Test Agent
            print("bundleDoingTest = " + bundleDoingTest.description) // Test Case Bundle
            print("bundleUnderTest = " + bundleBeingTested.description) // App Bundle
    

    The Xcode 6|7|8 URL will be in Developer/Xcode/DerivedData something like …

    file:///Users/
      UserName/
        Library/
          Developer/
            Xcode/
              DerivedData/
                App-qwertyuiop.../
                  Build/
                    Products/
                      Debug-iphonesimulator/
                        AppTests.xctest/
                          imageName.png
    

    … which is separate from Developer/CoreSimulator/Devices URL

    file:///Users/
      UserName/
        Library/
        Developer/
          CoreSimulator/
            Devices/
              _UUID_/
                data/
                  Containers/
                    Bundle/
                      Application/
                        _UUID_/
                          App.app/
    

    Also note the unit test executable is, by default, linked with the application code. However, the unit test code should only have Target Membership in just the test bundle. The application code should only have Target Membership in the application bundle. At runtime, the unit test target bundle is injected into the application bundle for execution.

    Just to append to correct answer, this is example how to get filePath for a file in your UnitTests/Supporting Files:

    NSString *filePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"YourFileName.json"];
    XCTAssertNotNil(filePath);
    

    This probably will be useful for someone.

    1. You can reference bundle files like in any target.
    2. Check if the file is Copyed in the Copy Bundle Resources build phase (of your test target)
    3. To access to the local file :

      NSURL*imageUrl=[[NSBundle mainBundle]URLForResource:@"imageName" withExtension:@"png"];
      

    You can make asynchronous access and wait for the response using : https://github.com/travisjeffery/TRVSMonitor

    If you have added : dataset1.json in the test target (2) :

          NSString *p=[[NSBundle mainBundle] pathForResource:@"dataset1" ofType:@"json"];
          NSLog(@"%@",p);
    
         2013-10-29 15:49:30.547 PlayerSample[13771:70b] WT(0): /Users/bpds/Library/Application Support/iPhone Simulator/7.0/Applications/7F78780B-684A-40E0-AA35-A3B5D8AA9DBD/PlayerSample.app.app/dataset1.json    
    

    The problem I was facing was that application code was trying to access the main bundle for things like bundleIdentifier and since the main bundle wasn’t my unit test project it would return nil.

    A hack around this that works in both Swift 3.0.1 and Objective-C is to create an Objective-C category on NSBundle and include it in your unit test project. You don’t need a bridging header or anything. This category will get loaded and now when you application code asks for the main bundle your category will return the unit test bundle.

    @interface NSBundle (MainBundle)
    
    +(NSBundle *)mainBundle;
    
    @end
    
    @implementation NSBundle (Main)
    
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
    +(NSBundle *)mainBundle
    {
        return [NSBundle bundleForClass:[SomeUnitTest class]];
    }
    #pragma clang diagnostic pop
    
    @end
    

    Here’s the Swift version of this, Xcode 7, iOS 9, etc.

    let testBundle = NSBundle(forClass: self.dynamicType)
    let path = testBundle.pathForResource("someImage", ofType: "jpg")
    XCTAssertNotNil(path)
    

    Note: someImage.jpg must be included in your test target.