NSURL to file path in test bundle with XCTest

倾然丶 夕夏残阳落幕 提交于 2019-11-29 22:37:16
Ben Clayton

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, 4:

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

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.

Swift Package Manager (SPM) 4:

let testBundle = Bundle(for: type(of: self)) 
print("testBundle.bundlePath = \(testBundle.bundlePath) ")

Note: By default, the command line swift test will create a MyProjectPackageTests.xctest test bundle. And, the swift package generate-xcodeproj will create a MyProjectTests.xctest test bundle. These different test bundles have different paths. Also, the different test bundles may have some internal directory structure and content differences.

In either case, the .bundlePath and .bundleURL will return the path of test bundle currently being run on macOS. However, Bundle is not currently implemented for Ubuntu.

Also, command line swift build and swift test do not currently provide a mechanism for copying resources.

However, with some effort, it is possible to set up processes for using the Swift Package Manger with resources in the macOS Xcode, macOS command line, and Ubuntu command line environments. One example can be found here: 004.4'2 SW Dev Swift Package Manager (SPM) With Resources Qref

Swift Package Manager (SPM) 4.2

Swift Package Manager PackageDescription 4.2 introduces support of local dependencies.

Local dependencies are packages on disk that can be referred directly using their paths. Local dependencies are only allowed in the root package and they override all dependencies with same name in the package graph.

Note: I expect, but have not yet tested, that something like the following should be possible with SPM 4.2:

// swift-tools-version:4.2
import PackageDescription

let package = Package(
    name: "MyPackageTestResources",
    dependencies: [
        .package(path: "../test-resources"),
    ],
    targets: [
        // ...
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage", "MyPackageTestResources"]
        ),
    ]
)

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.

bpds
  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

Swift version based on accepted answer:

let url = URL(fileURLWithPath: Bundle(for: type(of: self)).path(forResource: "my", ofType: "json") ?? "TODO: Proper checking for file")

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!