I am looking for a way to load files from disk within an XCTestCase
that does not depend on Bundle
.
Bundle
works well when running the tests from Xcode (or with xcodebuild
on the terminal), but bundles are part of the Xcode project and not available to Swift Package Manager (when running swift test
), neither on the Mac nor in Linux.
Is there a way to specify the current directory where tests should be run that works in all platforms? Or maybe there is a way to determine where the tests are located that also works on all platforms?
FileManager.default.currentDirectoryPath
only returns the current execution path (working directory).
Seems like when running tests with Swift Package Manager (swift test
), the working directory is the root of the project. This allows for easily loading resources from disk using a relative path (eg. ./Tests/Resources
).
After evaluating different options, I wrote the following class to retrieve the path both from the test bundle, if available, or from a given path.
class Resource {
static var resourcePath = "./Tests/Resources"
let name: String
let type: String
init(name: String, type: String) {
self.name = name
self.type = type
}
var path: String {
guard let path: String = Bundle(for: Swift.type(of: self)).path(forResource: name, ofType: type) else {
let filename: String = type.isEmpty ? name : "\(name).\(type)"
return "\(Resource.resourcePath)/\(filename)"
}
return path
}
}
Resources must be added to all test targets for them to be available in the bundle. The above class could be updated to support multiple resource paths, by file extension, for instance.
Resources can be then loaded from an XCTest
as needed:
let file = Resource(name: "datafile", type: "csv")
let content = try String(contentsOfFile: file)
let image = try UIImage(contentsOfFile: Resource(name: "image", type: "png"))
Extensions can be added to Resource
to load the contents in different formats.
extension Resource {
var content: String? {
return try? String(contentsOfFile: path).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
var base64EncodedData: Data? {
guard let string = content, let data = Data(base64Encoded: string) else {
return nil
}
return data
}
var image: Image? {
return try? UIImage(contentsOfFile: path)
}
}
And be used as:
let json = Resource(name: "datafile", type: "json").contents
let image = Resource(name: "image", type: "jpeg").image