Attachments
Attach values to tests to help diagnose issues and gather feedback.
Overview
Attach values such as strings and files to tests. Implement the Attachable protocol to create your own attachable types.
Attach data or strings
If your test produces encoded data that you want to save as an attachment, you can call record(_:named:sourceLocation:).
struct SalesReport { ... }
@Test func `sales report adds up`() async throws {
let salesReport = await generateSalesReport()
try salesReport.validate()
let bytes: [UInt8] = try salesReport.convertToCSV()
Attachment.record(bytes, named: "sales report.csv")
}You can attach an instance of Array<UInt8>, ContiguousArray<UInt8>, ArraySlice<UInt8>, or Data because these types automatically conform to Attachable.
You can also attach an instance of String or Substring. The testing library treats attached strings as UTF-8 text files. If you want to save a string as an attachment using a different encoding, convert it to Data using data(using:allowLossyConversion:) and attach the resulting data instead of the original string.
Attach encodable values
If you have a value you want to save as an attachment that conforms to either Encodable or NSSecureCoding, you can extend it to add conformance to Attachable. When you import the Foundation module, the testing library automatically provides a default implementation of Attachable to types that also conform to Encodable or NSSecureCoding.
import Testing
import Foundation
struct SalesReport { ... }
extension SalesReport: Encodable, Attachable {}
@Test func `sales report adds up`() async throws {
let salesReport = await generateSalesReport()
try salesReport.validate()
Attachment.record(salesReport, named: "sales report.json")
}Attach files and directories
If you have a file you want to save as an attachment, you can attach it using its file URL. The testing library needs to read or map the file before attaching it to your test, and those operations can fail, so you need to explicitly create an instance of Attachment before you record it.
import Foundation
@Test func `sales report adds up`() async throws {
let salesReport = await generateSalesReport()
try salesReport.validate()
let salesReportURL = try salesReport.save()
let attachment = try await Attachment(contentsOf: salesReportURL)
Attachment.record(attachment)
}You can also attach a directory to a test using its file URL. When you attach a directory to a test, the testing library creates a ZIP file containing the directory’s contents, then attaches that ZIP file in place of the directory.
Attach images
You can attach instances of the following system-provided image types to a test:
Platform | Supported types |
|---|---|
macOS | |
iOS, tvOS, and visionOS | |
watchOS | |
Windows | Bitmaps, Icons, Nn Wincodec Iwicbitmapsource (including its subclasses declared by Windows Imaging Component) |
When you attach an image to a test, you can specify the image format to use in addition to a preferred name.
struct SalesReport { ... }
@Test func `sales report adds up`() async throws {
let salesReport = await generateSalesReport()
let image = try salesReport.renderTrendsGraph()
Attachment.record(image, named: "sales report", as: .png)
}If you don’t specify an image format when attaching an image to a test, the testing library selects the format to use based on the preferred name you pass.
Attach other values
If you have a value that needs a custom encoded representation when you save it as an attachment, implement withUnsafeBytes(for:_:). The implementation of this function calls its body argument and passes the encoded representation of self or, if a failure occurs, throws an error representing that failure.
struct SalesReport { ... }
extension SalesReport: Attachable {
borrowing func withUnsafeBytes<R>(
for attachment: borrowing Attachment<Self>,
_ body: (UnsafeRawBufferPointer) throws -> R
) throws -> R {
let bytes = try salesReport.convertToCSV() // might fail to convert to CSV
try bytes.withUnsafeBytes { buffer in // rethrows any error from `body`
try body(buffer)
}
}
}If your type conforms to Sendable, the testing library avoids calling this function until it needs to save the attachment. If your type doesn’t conform to Sendable, the testing library calls this function as soon as you record the attachment.
Customize attachment behavior
If you can reliably estimate in advance how large the encoded representation will be, implement estimatedAttachmentByteCount. The testing library uses the value of this property as a hint to optimize memory and disk usage.
extension SalesReport: Attachable {
...
var estimatedAttachmentByteCount: Int? {
return self.entries.count * 123
}
}You can also implement preferredName(for:basedOn:) if you want to customize the name of the attachment when saving it.
extension SalesReport: Attachable {
...
borrowing func preferredName(
for attachment: borrowing Attachment<Self>,
basedOn suggestedName: String
) -> String {
if suggestedName.contains(".") {
// The name already contains a path extension, so don't append another.
return suggestedName
}
// Append ".csv" to the name so the resulting file opens as a spreadsheet.
return "\(suggestedName).csv"
}
}Inspect attachments after a test run ends
By default, the testing library saves your attachments as soon as you call record(_:sourceLocation:) or record(_:named:sourceLocation:). You can access saved attachments after your tests finish running:
When using Xcode, you can access attachments from the test report.
When using Visual Studio Code, the testing library saves attachments to
.build/attachmentsby default. Visual Studio Code reports the paths to individual attachments in its Tests Results panel.When using Swift Package Manager’s
swift testcommand, you can pass the--attachments-pathoption. The testing library saves attachments to the specified directory.If you do not pass the
--attachments-pathoption, the testing library does not save any attachments you record.