Last week, I wrote an article titled “Swift Testing: Introduce Confirmation”. This article explores the confirmation specification.

Let’s read Confirmation.swift in Swift Testing.

Interface

Here’s the confirmation implementation. There are five arguments:

public func confirmation<R>(
  _ comment: Comment? = nil,
  expectedCount: Int = 1,
  isolation: isolated (any Actor)? = #isolation,
  sourceLocation: SourceLocation = #_sourceLocation,
  _ body: (Confirmation) async throws -> sending R
) async rethrows -> R {
  try await confirmation(
    comment,
    expectedCount: expectedCount ... expectedCount,
    isolation: isolation,
    sourceLocation: sourceLocation,
    body
  )
}

comment is a comment provided when the test fails. It’s a Comment type and inherits from ExpressibleByStringLiteral in this code:

// MARK: - ExpressibleByStringLiteral, ExpressibleByStringInterpolation, CustomStringConvertible

extension Comment: ExpressibleByStringLiteral, ExpressibleByStringInterpolation, CustomStringConvertible {
  public init(stringLiteral: String) {
    self.init(rawValue: stringLiteral, kind: .stringLiteral)
  }

  public var description: String {
    rawValue
  }
}

That’s why comment accepts the String variable. It seems that comment is used to record an issue`, so let’s check the implementation later.

expectedCount is, as explained in the previous post, the number of times that event occurs. The callee function in this code has almost similar arguments except expectedCount. expectedCount is defined as an Int type in this function while described as a ClosedRange type in the callee function. However, upon closer inspection, it is enclosed by expectedCount, so the range of values seems limited to expectedCount.

isolation is the actor to which body is isolated. This argument was introduced so that tests requiring @MainActor can be compiled in Swift6 language mode.

sourceLocation refers to the file information recorded when the test fails. It’s like the #file and #line of XCTAssert arguments. You can check the SourceLocation implementation here.

body is a function that takes a Confirmation typed argument and invoked to check the test content.

Implementation

Now let’s read the callee function here. It’s implemented in only 13 lines.

In this function, a Confirmation type instance named confirmation is created and body is invoked with confirmation. Finally, an issue will be recorded to fail the test if expectedCount doesn’t match actualCount obtained from confirmation:

@_spi(Experimental)
public func confirmation<R>(
  _ comment: Comment? = nil,
  expectedCount: some RangeExpression<Int> & Sendable,
  isolation: isolated (any Actor)? = #isolation,
  sourceLocation: SourceLocation = #_sourceLocation,
  _ body: (Confirmation) async throws -> sending R
) async rethrows -> R {
  let confirmation = Confirmation()
  defer {
    let actualCount = confirmation.count.rawValue
    if !expectedCount.contains(actualCount) {
      let issue = Issue(
        kind: expectedCount.issueKind(forActualCount: actualCount),
        comments: Array(comment),
        sourceContext: .init(backtrace: .current(), sourceLocation: sourceLocation)
      )
      issue.record()
    }
  }
  return try await body(confirmation)
}

The Confirmation is defined here. It has a count that holds the number of function calls. A confirm function increments the count, and we can call it by callAsFunction:

/// A type that can be used to confirm that an event occurs zero or more times.
public struct Confirmation: Sendable {
  /// The number of times ``confirm(count:)`` has been called.
  ///
  /// This property is fileprivate because it may be mutated asynchronously and
  /// callers may be tempted to use it in ways that result in data races.
  fileprivate var count = Locked(rawValue: 0)

  /// Confirm this confirmation.
  ///
  /// - Parameters:
  ///   - count: The number of times to confirm this instance.
  ///
  /// As a convenience, this method can be called by calling the confirmation
  /// directly.
  public func confirm(count: Int = 1) {
    precondition(count > 0)
    self.count.add(count)
  }
}

extension Confirmation {
  /// Confirm this confirmation.
  ///
  /// - Parameters:
  ///   - count: The number of times to confirm this instance.
  ///
  /// Calling a confirmation as a function is shorthand for calling its
  /// ``confirm(count:)`` method.
  public func callAsFunction(count: Int = 1) {
    confirm(count: count)
  }
}

The type of count is Locked<Int>. Locked<T> leverages ManagedBuffer, which seems to add values based on a locking mechanism tailored to its platform, such as iOS, macOS, or Linux. You can check the details here.

Conclusion

I traced the confirmation specification from the Swift Testing source code. It is a more straightforward implementation than I had imagined. It can be applied as an Extension to XCTestCase, which summarizes the closure of asynchronous processing and the expected number of times it is executed.

Thanks!