At work we are slowly moving our whole app architecture towards using SPM packages to encapsulate different features of our apps. Since we write software, that is used in the medical environment, we need to write a lot of unit tests to verify that everything is working as intended. Therefore we often have test data in external files that are loaded during our tests. In this small article I want to highlight how you can include test data in test targets with the Swift Package manager.
Setup
For the purpose of this small article, let's assume we have the following directory structure of an SPM project called MyUtils
.
.
├── Package.swift
├── README.md
├── Sources
│ └── <Source Files>
└── Tests
├─ MyUnitTest.swift
├── TestData
│ └── TestData.txt
└── Utils
Inside the Tests
directory we created a folder called TestData
which contains resources we want to load inside MyUnitTest.swift
.
Declare data in Package.swift
SPM allows us to declare resource files inside the package manifest in a very convenient way. Resources can be added to .target
as well as .testTarget
declarations by adding the resources
property. Its an array of "process rules" which tells SPM how to handle the provided resources. In total two rules are available:
process
- automatically applies optimizations to files. e.g. optimizing image files for different platformscopy
- copies resources files at the given path without touching them. Directory structure is kept. You can read about the two rules here.
For adding test data, it is totally fine to use the copy
rule as we do not need to optimize the files in any way for unit tests.
In the following you can find a default Package.swift
file, which declares a library called MyUtils
. MyUtils
includes the MyUtils
target which is tested by the MyUtilsTests
target. MyUtilsTests
includes every resource file inside the TestData
directory.
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "MyUtils",
platforms: [
.iOS(.v14), .macOS(.v11)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "MyUtils",
targets: ["MyUtils"]),
],
dependencies: [],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "MyUtils",
dependencies: [],
path: "Sources" // Source files
),
.testTarget(
name: "MyUtilsTests",
dependencies: ["MyUtils"],
path: "Tests", // Test files
resources: [
.copy("TestData") // The test data files, copy files without modifying them
]
)
]
)
Access the test data
After declaring our test data we can start using it. SPM automatically creates an accessor for your resource files. You can retrieve a path to a single resource by calling Bundle.module.path(forResource:ofType:)
. This method creates an optional path (String
type) to the requested resource file.
// Example error to indicate the test file could not be read successfully
enum ReadError {
case fileNotFound
}
func testRead() throws {
// Retrieve file path of test data and create URL out of it
guard let filePath = Bundle.module.path(forResource: "TestData" ofType: "txt"),
let url = URL(fileURLWithPath: file) else {
throw ReadError.fileNotFound
}
// Read test data file contents
let data = Data(contentsOf: url))
let text = String(data: data, encoding: .utf8)
// Check that reading of test data worked
XCTAssertNotNil(text)
}
We can use the provided method to create a path to the resource we want to load. Afterwards convert it to an URL
type and use it to read its content.
Conclusion
In this small article I showed you how you can include resource files in your tests when using the Swift Package Manager. If you have any questions or suggestions do not hesitate to reach out to me on Twitter.
Until next time and stay healthy 👋