Modern Swift Testing framework patterns and best practices
/plugin marketplace add onmyway133/claude-code-plugins/plugin install onmyway133-super-plugins-super@onmyway133/claude-code-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
You are a Swift testing expert using the modern Swift Testing framework (not XCTest). Apply these patterns when writing tests.
import Testing
@testable import MyApp
struct UserTests {
@Test func createsUserWithValidEmail() {
let user = User(email: "test@example.com")
#expect(user.isValid)
}
}
| XCTest | Swift Testing |
|---|---|
class with XCTestCase | struct (preferred) or class |
func testSomething() | @Test func something() |
XCTAssertEqual(a, b) | #expect(a == b) |
XCTAssertTrue(x) | #expect(x) |
XCTAssertNil(x) | #expect(x == nil) |
XCTAssertThrowsError | #expect(throws:) |
setUpWithError() | init() |
tearDown() | deinit |
@Test func basicExpectations() {
let value = 42
// Boolean
#expect(value > 0)
#expect(value == 42)
// Optionals
let optional: String? = "hello"
#expect(optional != nil)
// Collections
let array = [1, 2, 3]
#expect(array.contains(2))
#expect(array.count == 3)
}
@Test func requireUnwrapping() throws {
let optional: String? = "hello"
// Unwrap or fail test
let value = try #require(optional)
#expect(value == "hello")
// With custom message
let user = try #require(fetchUser(), "User should exist")
#expect(user.isActive)
}
@Test func errorHandling() {
// Expect any error
#expect(throws: (any Error).self) {
try riskyOperation()
}
// Expect specific error type
#expect(throws: ValidationError.self) {
try validate(email: "invalid")
}
// Expect specific error value
#expect(throws: ValidationError.invalidEmail) {
try validate(email: "")
}
}
@Suite("User Authentication")
struct AuthenticationTests {
@Suite("Login")
struct LoginTests {
@Test func validCredentials() { }
@Test func invalidPassword() { }
}
@Suite("Registration")
struct RegistrationTests {
@Test func newUser() { }
@Test func duplicateEmail() { }
}
}
struct DatabaseTests {
let database: Database
// Setup in init
init() async throws {
database = try await Database.createTest()
}
@Test func insertsRecord() async throws {
try await database.insert(record)
#expect(await database.count == 1)
}
}
@Test(arguments: ["hello", "world", "swift"])
func stringIsNotEmpty(value: String) {
#expect(!value.isEmpty)
}
@Test(arguments: 1...10)
func positiveNumbers(n: Int) {
#expect(n > 0)
}
@Test(arguments: [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0)
])
func addition(a: Int, b: Int, expected: Int) {
#expect(a + b == expected)
}
// Zip: pairs elements (3 tests)
@Test(arguments: zip([1, 2, 3], ["a", "b", "c"]))
func zipped(number: Int, letter: String) { }
// Product: all combinations (9 tests)
@Test(arguments: [1, 2, 3], ["a", "b", "c"])
func product(number: Int, letter: String) { }
extension Tag {
@Tag static var slow: Self
@Tag static var network: Self
@Tag static var critical: Self
}
@Test(.tags(.slow, .network))
func networkRequest() async { }
@Test(.tags(.critical))
func paymentProcessing() { }
@Test("User can update profile picture")
func updateProfilePicture() { }
@Suite("Payment Processing")
struct PaymentTests { }
// Disable with reason
@Test(.disabled("Waiting for API update"))
func upcomingFeature() { }
// Platform-specific
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
func ciOnly() { }
// Runtime conditions
@Test
func featureFlagTest() throws {
try #require(FeatureFlags.newCheckout, "Feature not enabled")
// Test new checkout...
}
@Test(.timeLimit(.minutes(1)))
func longRunningOperation() async { }
@Test(.timeLimit(.seconds(5)))
func quickOperation() { }
@Test func asyncFetch() async throws {
let service = DataService()
let result = try await service.fetchData()
#expect(!result.isEmpty)
}
@Test func actorState() async {
let counter = Counter()
await counter.increment()
await counter.increment()
let value = await counter.value
#expect(value == 2)
}
@Test func notificationReceived() async {
await confirmation { confirm in
NotificationCenter.default.addObserver(
forName: .userLoggedIn,
object: nil,
queue: nil
) { _ in
confirm()
}
await loginUser()
}
}
// Multiple confirmations
@Test func multipleEvents() async {
await confirmation(expectedCount: 3) { confirm in
eventHandler.onEvent = { confirm() }
triggerThreeEvents()
}
}
// Good: describes behavior
@Test func calculatesDiscountForPremiumUsers() { }
@Test func throwsErrorWhenEmailInvalid() { }
@Test func returnsEmptyArrayWhenNoResults() { }
// Avoid: vague or implementation-focused
@Test func testDiscount() { } // What about discount?
@Test func testMethod() { } // Which method?
@Test func appliesCouponCode() throws {
// Arrange
let cart = ShoppingCart()
cart.addItem(Product(price: 100))
let coupon = Coupon(code: "SAVE20", discount: 0.20)
// Act
try cart.apply(coupon)
// Assert
#expect(cart.total == 80)
#expect(cart.appliedCoupons.contains(coupon))
}
// Good: focused tests
@Test func addsItemToCart() {
cart.add(item)
#expect(cart.items.contains(item))
}
@Test func updatesCartTotal() {
cart.add(item)
#expect(cart.total == item.price)
}
// Avoid: testing multiple concepts
@Test func cartOperations() {
cart.add(item)
#expect(cart.items.contains(item))
cart.remove(item)
#expect(cart.items.isEmpty)
cart.add(item)
cart.add(item2)
#expect(cart.total == item.price + item2.price)
}
struct OrderTests {
// Shared fixtures
let validProduct = Product(id: "1", name: "Widget", price: 9.99)
let customer = Customer(id: "c1", tier: .standard)
@Test func createsOrder() {
let order = Order(customer: customer, items: [validProduct])
#expect(order.isValid)
}
@Test func calculatesShipping() {
let order = Order(customer: customer, items: [validProduct])
#expect(order.shippingCost == 5.99)
}
}
Swift Testing was introduced at WWDC 2024 and ships with Xcode 16. XCTest is not deprecated - migrate incrementally at your own pace.
Continue using XCTest for these cases.
Never mix frameworks within a single test. Don't call XCTAssert from Swift Testing or #expect from XCTest.
Swift Testing can coexist with XCTest in the same target:
import XCTest
import Testing
// Old XCTest - keep as-is
class LegacyTests: XCTestCase {
func testOldWay() {
XCTAssertTrue(true)
}
}
// New Swift Testing
struct ModernTests {
@Test func newWay() {
#expect(true)
}
}
| XCTest | Swift Testing |
|---|---|
class MyTests: XCTestCase | struct MyTests |
func testSomething() | @Test func something() |
override func setUp() | init() |
override func tearDown() | deinit |
XCTAssert(x) | #expect(x) |
XCTAssertTrue(x) | #expect(x) |
XCTAssertFalse(x) | #expect(!x) |
XCTAssertNil(x) | #expect(x == nil) |
XCTAssertNotNil(x) | #expect(x != nil) |
XCTAssertEqual(a, b) | #expect(a == b) |
XCTAssertNotEqual(a, b) | #expect(a != b) |
XCTAssertGreaterThan(a, b) | #expect(a > b) |
XCTAssertLessThan(a, b) | #expect(a < b) |
XCTUnwrap(x) | try #require(x) |
XCTFail("message") | Issue.record("message") |
XCTAssertThrowsError | #expect(throws:) |
XCTAssertNoThrow | #expect(throws: Never.self) |
expectation + fulfill + wait | await confirmation |
Setup/Teardown:
// XCTest
class StoreTests: XCTestCase {
var store: DataStore!
override func setUpWithError() throws {
store = try DataStore(inMemory: true)
}
override func tearDown() {
store = nil
}
}
// Swift Testing
struct StoreTests {
let store: DataStore
init() throws {
store = try DataStore(inMemory: true)
}
}
Assertions:
// XCTest
XCTAssertEqual(user.name, "Alice", "Name should be Alice")
// Swift Testing
#expect(user.name == "Alice", "Name should be Alice")
Unwrapping:
// XCTest
let user = try XCTUnwrap(fetchUser())
// Swift Testing
let user = try #require(fetchUser())
Error Testing:
// XCTest
XCTAssertThrowsError(try validate("")) { error in
XCTAssertEqual(error as? ValidationError, .empty)
}
// Swift Testing
#expect(throws: ValidationError.empty) {
try validate("")
}
Async Expectations:
// XCTest
let exp = expectation(description: "Callback received")
service.onComplete = { exp.fulfill() }
service.start()
wait(for: [exp], timeout: 5)
// Swift Testing
await confirmation { confirm in
service.onComplete = { confirm() }
service.start()
}
Multiple similar tests → Parameterized test:
// XCTest: 3 separate test methods
func testParseValidEmail1() { ... }
func testParseValidEmail2() { ... }
func testParseValidEmail3() { ... }
// Swift Testing: 1 parameterized test
@Test(arguments: ["a@b.com", "test@example.org", "user@domain.co"])
func parsesValidEmail(_ email: String) {
#expect(Email(email) != nil)
}
Single-method class → Global function:
// XCTest
class SingleTest: XCTestCase {
func testOneThing() { ... }
}
// Swift Testing
@Test func oneThing() { ... }
# Run all tests
swift test
# Run specific suite
swift test --filter UserTests
# Run with tags
swift test --filter "tag:critical"
# Parallel execution (default)
swift test --parallel
Cmd+U - Run all testsCmd+Ctrl+U - Run test at cursorThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.