How to test a class in a Swift package?
Apparently, the issue won't reproduce anymore in the latest Swift version. The Car
class can be imported and the swift test
command won't fail.
For the record, my current swift --version
output is:
swift-driver version: 1.45.2 Apple Swift version 5.6.1 (swiftlang-5.6.0.323.66 clang-1316.0.20.12)
Target: arm64-apple-macosx12.0
What is a good or common way to structure a CLI appliction in Swift
You cannot unit test a command line tool main file, because it is not a testable target.
But you can test a framework. So make a framework target, make an accompanying unit test target, and put your testable code into the framework.
However, a command line tool can't include a framework; it is just source files. So the source files of the framework need to be included with the command line tool target too. You do not embed or link the framework into the command line tool; that can't work.
So the command line tool sees the framework not as a framework but just as part of the same "module" as itself. Meanwhile, the test target sees the framework as what it is testing. And all is well.
I should mention that this is also a good way to structure the unit testing of a GUI iOS app, because the tests do not require the app to be loaded and run in the Simulator.
See https://developer.apple.com/forums/thread/52211 for further discussion.
Use resources in unit tests with Swift Package Manager
Swift 5.3
See Apple Documentation: "Bundling Resources with a Swift Package"
Swift 5.3 includes Package Manager Resources SE-0271 evolution proposal with "Status: Implemented (Swift 5.3)".
Resources aren't always intended for use by clients of the package; one use of resources might include test fixtures that are only needed by unit tests. Such resources would not be incorporated into clients of the package along with the library code, but would only be used while running the package's tests.
- Add a new
resources
parameter intarget
andtestTarget
APIs to allow declaring resource files explicitly.SwiftPM uses file system conventions for determining the set of source files that belongs to each target in a package: specifically, a target's source files are those that are located underneath the designated "target directory" for the target. By default this is a directory that has the same name as the target and is located in "Sources" (for a regular target) or "Tests" (for a test target), but this location can be customized in the package manifest.
// Get path to DefaultSettings.plist file.
let path = Bundle.module.path(forResource: "DefaultSettings", ofType: "plist")
// Load an image that can be in an asset archive in a bundle.
let image = UIImage(named: "MyIcon", in: Bundle.module, compatibleWith: UITraitCollection(userInterfaceStyle: .dark))
// Find a vertex function in a compiled Metal shader library.
let shader = try mtlDevice.makeDefaultLibrary(bundle: Bundle.module).makeFunction(name: "vertexShader")
// Load a texture.
let texture = MTKTextureLoader(device: mtlDevice).newTexture(name: "Grass", scaleFactor: 1.0, bundle: Bundle.module, options: options)
Example
// swift-tools-version:5.3
import PackageDescription
targets: [
.target(
name: "Example",
dependencies: [],
resources: [
// Apply platform-specific rules.
// For example, images might be optimized per specific platform rule.
// If path is a directory, the rule is applied recursively.
// By default, a file will be copied if no rule applies.
// Process file in Sources/Example/Resources/*
.process("Resources"),
]),
.testTarget(
name: "ExampleTests",
dependencies: [Example],
resources: [
// Copy Tests/ExampleTests/Resources directories as-is.
// Use to retain directory structure.
// Will be at top level in bundle.
.copy("Resources"),
]),
Reported Issues & Possible Workarounds
- Swift 5.3 SPM Resources in tests uses wrong bundle path?
- Swift Package Manager - Resources in test targets
Xcode
Bundle.module
is generated by SwiftPM (see Build/BuildPlan.swift SwiftTargetBuildDescription generateResourceAccessor()) and thus not present in Foundation.Bundle when built by Xcode.
A comparable approach in Xcode would be to manually add a Resources
reference folder to the Xcode project, add an Xcode build phase copy
to put the Resource
into some *.bundle
directory, and add a some custom #ifdef XCODE_BUILD
compiler directive for the Xcode build to work with the resources.
#if XCODE_BUILD
extension Foundation.Bundle {
/// Returns resource bundle as a `Bundle`.
/// Requires Xcode copy phase to locate files into `ExecutableName.bundle`;
/// or `ExecutableNameTests.bundle` for test resources
static var module: Bundle = {
var thisModuleName = "CLIQuickstartLib"
var url = Bundle.main.bundleURL
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
url = bundle.bundleURL.deletingLastPathComponent()
thisModuleName = thisModuleName.appending("Tests")
}
url = url.appendingPathComponent("\(thisModuleName).bundle")
guard let bundle = Bundle(url: url) else {
fatalError("Foundation.Bundle.module could not load resource bundle: \(url.path)")
}
return bundle
}()
/// Directory containing resource bundle
static var moduleDir: URL = {
var url = Bundle.main.bundleURL
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
// remove 'ExecutableNameTests.xctest' path component
url = bundle.bundleURL.deletingLastPathComponent()
}
return url
}()
}
#endif
Related Topics
Can Swift Playgrounds See Other Source Files in the Same Project
Local Notification While App Not Running
Class-Only Generic Constraints in Swift
Clearing Uiwebview's Cache in Swift
How to Concatenate Optional Swift Strings
Binary Operator '===' Cannot Be Applied to Operands of Type 'Any' and 'Uibarbuttonitem!'
Count Number of Decimal Places in a Float (Or Decimal) in Swift
Propertywrappers and Protocol Declaration
Multi-Component Picker (Uipickerview) in Swiftui
How to Utilize Nslock to Prevent a Function from Firing Twice
How to Resume Audio After Interruption in Swift
Binary Operator '+' Cannot Be Applied to Two 'T' Operands
How to Link Xctest Dependency to Production/Main Target
How to Restrict the Type That a Function Throws in Swift
How to Use Unsafemutablerawpointer to Fill an Array
Why Do I Need to Declare an Optional Value as Nil Explicitly in Struct - Swift