Swift Macro Testing

repository·main·Indexed 43 days ago

https://github.com/pointfreeco/swift-macro-testing

A library providing ergonomic tools for testing Swift macros. It allows developers to assert macro expansions, diagnostics, and fix-its using the assertMacro function, with support for automatic snapshot recording via the .macros trait in the swift-testing framework.

Tokens
1.6K
Snippets
5
Records
7
Agent score
56%

What's inside Swift Macro Testing

  1. Configure macro recording with Swift Testing Traits

    main

    When using the swift-testing framework, you can use the .macros trait to configure macro testing behavior for a @Suite. This allows you to specify which macros are available and control how expansions are recorded.

    Available configurations for the record parameter within .macros(...):

    • .all: Automatically re-records the freshest expanded macro into the expansion trailing closure when the test is run.
    • .missing: Records only missing snapshots.

    You can also control recording behavior globally using the SNAPSHOT_TESTING_RECORD environment variable.

    import Testing
    import MacroTesting
    
    @Suite(
      .macros(
        ["stringify": StringifyMacro.self],
        record: .missing // Record only missing snapshots
      )
    )
    struct StringifyMacroSwiftTestingTests {
      @Test
      func testStringify() {
        assertMacro {
          """
          #stringify(a + b)
          """
          """
          (a + b, "a + b")
          """
        }
      }
    }
  2. Automatically record macro expansions

    main

    To avoid manually writing expansion strings, you can instruct the library to automatically re-record the freshest macro expansion directly into your test file.

    This is achieved by providing the record argument to the .macros trait in Swift Testing. Using .all will ensure that when you run the test, the latest expanded macro is written to the expansion: trailing closure.

    @Suite(.macros([StringifyMacro.self], record: .all))
    struct StringifyTests {
      // ...
    }
  3. Use assertMacro to test Swift macros

    main

    The assertMacro function is the primary tool for verifying the behavior of Swift macros. It allows you to assert that a macro expands to a specific expected code string, and can optionally verify diagnostics (errors/warnings), fixes (code transformations), and indentation.

    Depending on the complexity of your test, you can use different overloads to check for matching expansion, specific diagnostics, or applied fixes.

    // Note: Specific usage examples for the overloads are not provided in this documentation segment, 
    // but the function is used to assert macro expansion and properties.
  4. Test macro diagnostics, fixes, and expansions

    main

    Macro Testing can verify not just the code expansion, but also the diagnostics (errors, warnings, notes) and fix-its emitted by a macro. You can use the diagnostics, fixes, and expansion trailing closures to assert the exact state of the macro application.

    • diagnostics: Asserts the expected diagnostic messages and their locations.
    • fixes: Asserts the expected code changes suggested by the macro's fix-its.
    • expansion: Asserts the final expanded source code.
    func testNonAsyncFunctionDiagnostic() {
      assertMacro {
        """
        @AddCompletionHandler
        func f(a: Int, for b: String) -> String {
          return b
        }
        """
      } diagnostics: {
        """
        @AddCompletionHandler
        func f(a: Int, for b: String) -> String {
        ┬───
        ╰─ 🛑 can only add a completion-handler variant to an 'async' function
           ✏️ add 'async'
          return b
        }
        """
      } fixes: {
        """
        @AddCompletionHandler
        func f(a: Int, for b: String) async -> String {
          return b
        }
        """
      } expansion: {
        """
        func f(a: Int, for b: String) async -> String {
          return b
        }
    
        func f(a: Int, for b: String, completionHandler: @escaping (String) -> Void) {
          Task {
            completionHandler(await f(a: a, for: b, value))
          }
        }
        """
      }
    }
  5. Test Swift macros with assertMacro

    main

    Use assertMacro to verify the expansion of Swift macros. You provide a string of Swift source code containing the macro call, and the library automatically expands it.

    If you haven't written the expected expansion yet, running the test will automatically write the expansion into your test file within an expansion: trailing closure. This makes the testing workflow highly ergonomic.

    If the macro output changes in the future, the test will fail and provide a clear diff between the expected and actual output.

    import MacroTesting
    import Testing
    
    @Suite(.macros([StringifyMacro.self]))
    struct StringifyTests {
      @Test func stringify() {
        assertMacro {
          """
          #stringify(a + b)
          """
        }
      }
    }
  6. Available assertMacro overloads

    main

    The library provides several overloads of assertMacro to cater to different testing requirements:

    • assertMacro(_:record:of:matches:fileID:file:function:line:column:): A basic overload for matching macro expansion.
    • assertMacro(_:indentationWidth:record:of:diagnostics:fixes:expansion:fileID:file:function:line:column:): A comprehensive overload that allows asserting on indentation width, diagnostics, fixes, and the final expansion.
  7. Deprecated assertMacro APIs

    main

    The following assertMacro overloads are deprecated and should be avoided in new code:

    • assertMacro(_:record:of:matches:fileID:file:function:line:column:) (Note: This conflicts with the active overload name in the documentation, implying a specific signature is deprecated)
    • assertMacro(_:applyFixIts:record:of:matches:fileID:file:function:line:column:) (Multiple versions)