Introducing SnapshotTestingMacros

Introducing SnapshotTestingMacros

I've been working on a swift macro library to generate snapshot tests in a format and structure similar to Swift Testing.

Instead of @Suite and @Test you use @SnapshotSuite and @SnapshotTest.

View more (including documentation) here: GitHub - adammcarter/swift-snapshot-testing-macros: A Swift Macro library for generating snapshot tests from functions

Create snapshots like this

@SnapshotSuite
struct MySnapshots {

  @SnapshotTest
  func myView() -> some View {
    Text("Some text")
  }
}

Traits

Specify device sizes, theme (dark/light mode), background colours, padding and even force record and set snapshot strategies.

@SnapshotSuite
struct MySnapshots {

  @SnapshotTest(
    .sizes(devices: .iPhoneX, .iPadPro11),
    .backgroundColor(.red),
    .padding(20),
    .record
  )
  func myPhoneView() -> some View {
    Text("Some text here")
  }
}

Traits can also be set on the suite so all tests inherit them.

And sensible defaults exist, such as automatically creating light and dark mode snapshot variants.

Parameterisation

configurations

@Suite
@SnapshotSuite
struct MySnapshots {

  @SnapshotTest(
    configurations: [ // ⬅️ Set configurations to pass names and values
      SnapshotConfiguration(name: "Name 1", value: 1),
      SnapshotConfiguration(name: "Name 2", value: 2),
    ]
  )
  func myView(value: Int) -> some View {
    Text("value: \(value)")
  }
}

Folder structure:

configurationValues

@Suite
@SnapshotSuite
struct MySnapshots {

  @SnapshotTest(configurationValues: [1, 2]) // ⬅️ Set configurationValues to pass values and infer the name from the value
  func myView(int: Int) -> some View {
    Text("value: \(int)")
  }
}

Folder structure:

And more

See the documentation on the README of the repo for a more comprehensive list of things you can do and example code.

Feedback

It's still in development but it would be great to get some early feedback from people who are interested in using this and fixing up any issues, adding new features or just discussing any technical sides of this macro.

Thanks!

2 Likes

Very cool, @adammcarter! Thanks for sharing. Interesting to see an example of a library composing Swift Testing and interoperating with its built-in traits and macros.

I noticed this note in the README:

Note that while @Suite isn't explicitly needed to run the snapshots, it's currently recommneded so Xcode can pickup the generated Suite inside the macro. Due to macro limitations it seems that Xcode cannot see Suites when they're embedded inside macro expansion code.

I believe you’re referring to Xcode’s ability to detect a suite type via its indexer, statically, which is different from discovery at runtime (which presumably does work already). This isn’t surprising because that indexing system is syntax-only, and doesn’t expand other macros to determine whether they include a @Suite or @Test macro. I would encourage you to file a Feedback with Apple about that limitation if you haven’t already.

3 Likes

Thanks @smontgomery !

You're right, at runtime the discovery happens fine, but the indexer doesn't pick them up. I've added a bug report through feedback assistant: FB17207500

Thanks for taking a look and giving some feedback!

Interesting stuff! I wonder if you'd be able to make use of Swift Testing's Trait protocol and related types so that developers don't have to specify a separate protocol conformance? (I haven't had a chance to read through everything yet, so if you're already doing this, ignore me!)

1 Like

Thanks @grynspan ! I'm guessing you mean for the trait types I've added (like .backgroundColor to conform to Swift Testing's Trait protocol?

If so, what are you thinking is the benefit of this out of interest?

As I said, I haven't taken a deep look at your code, but if you're leveraging parts of Swift Testing's infrastructure, it might prove useful.

I guess my current goal is to just abstract away Swift Testing and the snapshot testing frameworks as an implementation detail. But might be good in future to be able re-use any existing protocols / typealais them if there's some benefit