Better debugging and testing for your data model.
DebugSnapshots is part of the Point-Free ecosystem. Become a member to support the development of this library and get access to expert Swift knowledge, beta previews, AI skills, behind-the-scenes videos, and more.
DebugSnapshots gives you a powerful macro that converts complex model data types into simple, inert values that can be easily debugged and tested over time.
Apply the @DebugSnapshot macro with the .logChanges option to turn any class into an instantly
debuggable object:
@DebugSnapshot(.logChanges)
class FeatureModel {
var count = 0
var favoriteNumbers: [Int] = []
func incrementButtonTapped() {
count += 1
}
func saveButtonTapped() {
favoriteNumbers.append(count)
}
}With the macro applied, every invocation of a method on FeatureModel will automatically print
how the state changed:
model.incrementButtonTapped()
// incrementButtonTapped():
// #1 FeatureModel.DebugSnapshot(
// - count: 0,
// + count: 1,
// favoriteNumbers: []
// )
model.saveButtonTapped()
// saveButtonTapped():
// #1 FeatureModel.DebugSnapshot(
// count: 1,
// favoriteNumbers: [
// + [0]: 1
// ]
// )Note
Changes are logged only in debug builds. All logging is disabled in release builds.
DebugSnapshots leverages our CustomDump library to print minimal and concise differences between values, so if an array contains 100 elements and only a single one changes, the diff focuses on just that element:
model.saveButtonTapped()
// saveButtonTapped():
// #1 FeatureModel.DebugSnapshot(
// count: 101,
// favoriteNumbers: [
// … (99 unchanged),
// + [100]: 100
// ]
// )The @DebugSnapshot macro gives you the ability to exhaustively test the
logic and behavior in your classes using
expect. Start by applying
the macro to your class:
@DebugSnapshot
class FeatureModel {
var count = 0
var favoriteNumbers: [Int] = []
func incrementButtonTapped() {
count += 1
}
func saveButtonTapped() {
favoriteNumbers.append(count)
}
}With that done you can now write tests that invoke the various methods on the class and assert exhaustively how the state in the class changes:
@Test func testIncrement() {
let model = FeatureModel()
expect(model) {
model.incrementButtonTapped()
} changes: {
$0.count = 1
}
}Important
If your code is in an Xcode app target with default settings (i.e. main actor isolation
and Swift 5 mode), then you will have to additionally mark all tests as @MainActor.
The first trailing closure of
expect allows you to perform
any number of actions on your model, and the second argument asserts on how the state changes
after the actions are performed.
If you assert the wrong thing, or do not assert on everything that changed, you will get a test failure message that tells you exactly what went wrong:
@Test func testIncrement() {
let model = FeatureModel()
expect(model) {
model.incrementButtonTapped()
} changes: {
$0.count = 2
}
}❌ Issue recorded: Expected changes do not match: ...
#1 FeatureModel.DebugSnapshot( - count: 2, + count: 1, favoriteNumbers: [] ) (Expected: −, Actual: +)
That is the basics of using the library, but be sure to read the articles and documentation to learn more.
The documentation for the latest unstable and stable releases are available here:
Add DebugSnapshots to your Package.swift dependencies:
dependencies: [
.package(
url: "https://github.com/pointfreeco/swift-debug-snapshots",
from: "0.1.0"
)
]And add the product to your target:
targets: [
.target(
name: "MyFeature",
dependencies: [
.product(name: "DebugSnapshots", package: "swift-debug-snapshots")
]
)
]This package currently requires Swift 6.2 or later and supports iOS 13+, macOS 10.15+, tvOS 13+, and watchOS 6+.
This library is released under the MIT license. See LICENSE for details.