Contents

pointfreeco/swift-debug-snapshots

Better debugging and testing for your data model.

A Point-Free Production

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.

<a href="https://www.pointfree.co/"> <img alt="video poster image" src="https://d3rccdn33rt8ze.cloudfront.net/social-assets/twitter-card-large.png" width="600"> </a>

[Point-Free]: https://www.pointfree.co [Become a member]: https://www.pointfree.co/pricing [beta previews]: https://www.pointfree.co/beta-previews [AI skills]: https://www.pointfree.co/the-way [videos]: https://www.pointfree.co/episodes

Overview

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.

Debugging

Apply the [@DebugSnapshot] macro with the .logChanges option to turn any class into an instantly debuggable object:

[@DebugSnapshot]: https://swiftpackageindex.com/pointfreeco/swift-debug-snapshots/main/documentation/debugsnapshots/debugsnapshot(_:)

@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:

[CustomDump]: https://github.com/pointfreeco/swift-custom-dump

model.saveButtonTapped()
// saveButtonTapped():
//     #1 FeatureModel.DebugSnapshot(
//       count: 101,
//       favoriteNumbers: [
//         … (99 unchanged),
//   +     [100]: 100
//       ]
//     )

Testing

The @DebugSnapshot](/docs/swift-book/?q=DebugSnapshot(%3A))) macro gives you the ability to exhaustively test the logic and behavior in your classes using expect](/docs/swift-book/?q=expect(%3A_%3Aoperation%3Achanges%3AfileID%3AfilePath%3Aline%3Acolumn%3A))). 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](/docs/swift-book/?q=expect(%3A%3Aoperation%3Achanges%3AfileID%3AfilePath%3Aline%3Acolumn%3A))) 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.

Documentation

The documentation for the latest unstable and stable releases are available here:

main 0.x.x

Installation

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+.

License

This library is released under the MIT license. See LICENSE for details.

Package Metadata

Repository: pointfreeco/swift-debug-snapshots

Default branch: main

README: README.md