问题
Some code I am unit testing needs to load a resource file. It contains the following line:
NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];
In the app it runs just fine, but when run by the unit testing framework pathForResource:
returns nil, meaning it could not locate foo.txt
.
I've made sure that foo.txt
is included in the Copy Bundle Resources build phase of the unit test target, so why can't it find the file?
回答1:
When the unit test harness runs your code, your unit test bundle is NOT the main bundle.
Even though you are running tests, not your application, your application bundle is still the main bundle. (Presumably, this prevents the code you are testing from searching the wrong bundle.) Thus, if you add a resource file to the unit test bundle, you won't find it if search the main bundle. If you replace the above line with:
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *path = [bundle pathForResource:@"foo" ofType:@"txt"];
Then your code will search the bundle that your unit test class is in, and everything will be fine.
回答2:
A Swift implementation:
Swift 2
let testBundle = NSBundle(forClass: self.dynamicType)
let fileURL = testBundle.URLForResource("imageName", withExtension: "png")
XCTAssertNotNil(fileURL)
Swift 3, Swift 4
let testBundle = Bundle(for: type(of: self))
let filePath = testBundle.path(forResource: "imageName", ofType: "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
In Xcode 6|7|8|9, a unit-test bundle path will be in Developer/Xcode/DerivedData
something like ...
/Users/
UserName/
Library/
Developer/
Xcode/
DerivedData/
App-qwertyuiop.../
Build/
Products/
Debug-iphonesimulator/
AppTests.xctest/
foo.txt
... which is separate from the Developer/CoreSimulator/Devices
regular (non-unit-test) bundle path:
/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 Linux.
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
See also: Use resources in unit tests with Swift Package Manager
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"]
),
]
)
回答3:
With swift Swift 3 the syntax self.dynamicType
has been deprecated, use this instead
let testBundle = Bundle(for: type(of: self))
let fooTxtPath = testBundle.path(forResource: "foo", ofType: "txt")
or
let fooTxtURL = testBundle.url(forResource: "foo", withExtension: "txt")
回答4:
Confirm that the resource is added to the test target.
回答5:
if you have multiple target in your project then you need to add resources between different target available in the Target Membership and you may need to switch between different Target as 3 steps shown in the figure below
回答6:
I had to ensure this General Testing checkbox was set
来源:https://stackoverflow.com/questions/1879247/why-cant-code-inside-unit-tests-find-bundle-resources