Swift Testing framework with @Test macro,
/plugin marketplace add bluewaves-creations/bluewaves-skills/plugin install swift-apple-dev@bluewaves-skillsThis skill is limited to using the following tools:
Comprehensive guide to the modern Swift Testing framework, test organization, assertions, and Xcode Playgrounds for iOS 26 development.
| Feature | Swift Testing | XCTest |
|---|---|---|
| Test marking | @Test macro | Method naming test* |
| Assertions | #expect, #require | XCTAssert* |
| Test organization | Structs, actors, classes | XCTestCase subclass |
| Parallelism | Parallel by default | Process-based |
| Setup/Teardown | init/deinit | setUp/tearDown |
import Testing
import Testing
@Test
func additionWorks() {
let result = 2 + 2
#expect(result == 4)
}
@Test("User can create account with valid email")
func createAccountWithValidEmail() async throws {
let account = try await AccountService.create(email: "test@example.com")
#expect(account.email == "test@example.com")
}
@Test
func fetchUserReturnsData() async throws {
let user = try await userService.fetch(id: "123")
#expect(user.name == "John Doe")
}
@Test
func invalidEmailThrows() throws {
#expect(throws: ValidationError.invalidEmail) {
try validate(email: "not-an-email")
}
}
Basic expectations:
@Test
func basicExpectations() {
let value = 42
// Equality
#expect(value == 42)
// Inequality
#expect(value != 0)
// Boolean
#expect(value > 0)
// With message
#expect(value == 42, "Value should be 42")
}
@Test
func expressionExpectations() {
let array = [1, 2, 3]
#expect(array.count == 3)
#expect(array.contains(2))
#expect(!array.isEmpty)
let optional: String? = "hello"
#expect(optional != nil)
}
Unwrap optionals and fail fast:
@Test
func requireUnwrapping() throws {
let optional: String? = "hello"
// Unwrap or fail test
let value = try #require(optional)
#expect(value == "hello")
}
@Test
func requireCondition() throws {
let array = [1, 2, 3]
// Fail if condition is false
try #require(array.count > 0)
let first = try #require(array.first)
#expect(first == 1)
}
@Test
func throwingBehavior() {
// Expect any error
#expect(throws: (any Error).self) {
try riskyOperation()
}
// Expect specific error type
#expect(throws: NetworkError.self) {
try fetchData()
}
// Expect specific error value
#expect(throws: NetworkError.timeout) {
try fetchWithTimeout()
}
}
@Test
func noThrow() {
// Expect no error
#expect(throws: Never.self) {
safeOperation()
}
}
@Test
func customMessages() {
let user = User(name: "Alice", age: 25)
#expect(user.age >= 18, "User must be an adult, but age was \(user.age)")
}
@Suite("User Authentication Tests")
struct AuthenticationTests {
@Test("Valid credentials succeed")
func validLogin() async throws {
let result = try await auth.login(user: "test", pass: "password")
#expect(result.success)
}
@Test("Invalid credentials fail")
func invalidLogin() async throws {
let result = try await auth.login(user: "test", pass: "wrong")
#expect(!result.success)
}
}
@Suite("API Tests")
struct APITests {
@Suite("User Endpoints")
struct UserEndpoints {
@Test func getUser() async { }
@Test func createUser() async { }
}
@Suite("Post Endpoints")
struct PostEndpoints {
@Test func getPosts() async { }
@Test func createPost() async { }
}
}
@Suite
actor DatabaseTests {
var database: TestDatabase
init() async throws {
database = try await TestDatabase.create()
}
@Test
func insertWorks() async throws {
try await database.insert(User(name: "Test"))
let count = try await database.count(User.self)
#expect(count == 1)
}
}
@Suite
struct DatabaseTests {
let database: Database
init() async throws {
// Setup - called before each test
database = try await Database.createInMemory()
try await database.migrate()
}
deinit {
// Teardown - called after each test
// Note: async cleanup should be done differently
}
@Test
func testInsert() async throws {
try await database.insert(item)
#expect(try await database.count() == 1)
}
}
@Test("Validation", arguments: [
"test@example.com",
"user@domain.org",
"name@company.co.uk"
])
func validEmails(email: String) {
#expect(isValidEmail(email))
}
@Test("Addition", arguments: [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300)
])
func addition(a: Int, b: Int, expected: Int) {
#expect(a + b == expected)
}
@Test(arguments: zip(
["hello", "world", "test"],
[5, 5, 4]
))
func stringLength(string: String, expectedLength: Int) {
#expect(string.count == expectedLength)
}
struct TestCase: CustomTestStringConvertible {
let input: String
let expected: Int
var testDescription: String {
"'\(input)' should have length \(expected)"
}
}
@Test("String lengths", arguments: [
TestCase(input: "hello", expected: 5),
TestCase(input: "", expected: 0),
TestCase(input: "Swift", expected: 5)
])
func stringLength(testCase: TestCase) {
#expect(testCase.input.count == testCase.expected)
}
Run tests sequentially:
@Suite(.serialized)
struct OrderDependentTests {
@Test func step1() { }
@Test func step2() { }
@Test func step3() { }
}
Skip tests:
@Test(.disabled("Known bug, see issue #123"))
func brokenFeature() {
// Won't run
}
@Test(.disabled(if: isCI, "Flaky on CI"))
func flakyTest() {
// Conditionally disabled
}
Conditionally enable:
@Test(.enabled(if: ProcessInfo.processInfo.environment["RUN_SLOW_TESTS"] != nil))
func slowIntegrationTest() async throws {
// Only runs when environment variable is set
}
Organize with tags:
extension Tag {
@Tag static var critical: Self
@Tag static var slow: Self
@Tag static var integration: Self
}
@Test(.tags(.critical))
func criticalFeature() { }
@Test(.tags(.slow, .integration))
func slowIntegrationTest() async { }
Set execution limit:
@Test(.timeLimit(.seconds(5)))
func mustCompleteQuickly() async throws {
// Fails if takes more than 5 seconds
}
Reference known issues:
@Test(.bug("https://github.com/org/repo/issues/123", "Expected failure"))
func knownIssue() {
// Test expected to fail
}
Tests run in parallel by default:
@Suite
struct ParallelTests {
// These run concurrently
@Test func test1() async { }
@Test func test2() async { }
@Test func test3() async { }
}
@Suite(.serialized)
struct SerialTests {
static var sharedState = 0
@Test func first() {
Self.sharedState = 1
#expect(Self.sharedState == 1)
}
@Test func second() {
Self.sharedState = 2
#expect(Self.sharedState == 2)
}
}
protocol UserService {
func fetch(id: String) async throws -> User
}
struct MockUserService: UserService {
var userToReturn: User?
var errorToThrow: Error?
func fetch(id: String) async throws -> User {
if let error = errorToThrow {
throw error
}
guard let user = userToReturn else {
throw TestError.notConfigured
}
return user
}
}
@Suite
struct UserViewModelTests {
@Test
func fetchUserSuccess() async throws {
var mockService = MockUserService()
mockService.userToReturn = User(id: "1", name: "Test")
let viewModel = UserViewModel(service: mockService)
try await viewModel.loadUser(id: "1")
#expect(viewModel.user?.name == "Test")
}
}
final class SpyUserService: UserService {
var fetchCallCount = 0
var lastFetchedId: String?
func fetch(id: String) async throws -> User {
fetchCallCount += 1
lastFetchedId = id
return User(id: id, name: "Test")
}
}
@Test
func loadsUserOnAppear() async throws {
let spy = SpyUserService()
let viewModel = UserViewModel(service: spy)
await viewModel.loadUser(id: "123")
#expect(spy.fetchCallCount == 1)
#expect(spy.lastFetchedId == "123")
}
@Observable
class CounterViewModel {
var count = 0
func increment() {
count += 1
}
}
@Suite
struct CounterViewModelTests {
@Test
func incrementIncreasesCount() {
let viewModel = CounterViewModel()
viewModel.increment()
#expect(viewModel.count == 1)
}
@Test
func multipleIncrements() {
let viewModel = CounterViewModel()
viewModel.increment()
viewModel.increment()
viewModel.increment()
#expect(viewModel.count == 3)
}
}
@Observable
@MainActor
class UserListViewModel {
var users: [User] = []
var isLoading = false
private let service: UserService
init(service: UserService) {
self.service = service
}
func loadUsers() async {
isLoading = true
defer { isLoading = false }
users = (try? await service.fetchAll()) ?? []
}
}
@Suite
struct UserListViewModelTests {
@Test
@MainActor
func loadUsersPopulatesArray() async {
var mock = MockUserService()
mock.usersToReturn = [User(id: "1", name: "Alice")]
let viewModel = UserListViewModel(service: mock)
await viewModel.loadUsers()
#expect(viewModel.users.count == 1)
#expect(viewModel.isLoading == false)
}
}
Both frameworks can coexist:
// XCTest
import XCTest
class LegacyTests: XCTestCase {
func testOldStyle() {
XCTAssertEqual(2 + 2, 4)
}
}
// Swift Testing
import Testing
@Test
func newStyle() {
#expect(2 + 2 == 4)
}
| XCTest | Swift Testing |
|---|---|
XCTAssertTrue(x) | #expect(x) |
XCTAssertFalse(x) | #expect(!x) |
XCTAssertEqual(a, b) | #expect(a == b) |
XCTAssertNil(x) | #expect(x == nil) |
XCTAssertNotNil(x) | try #require(x) |
XCTAssertThrowsError | #expect(throws:) |
XCTUnwrap(x) | try #require(x) |
measure {})import SwiftUI
#Playground {
let greeting = "Hello, Playgrounds!"
print(greeting)
}
#Playground("SwiftUI Preview") {
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
ContentView()
}
#Playground("Data Processing") {
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled)
}
#Playground("API Simulation") {
struct User: Codable {
let name: String
}
let json = #"{"name": "Alice"}"#
let user = try? JSONDecoder().decode(User.self, from: json.data(using: .utf8)!)
print(user?.name ?? "Unknown")
}
#Playground("Interactive UI") {
struct Counter: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
Counter()
}
// GOOD
@Test("User cannot login with expired token")
func expiredTokenLogin() { }
// AVOID
@Test
func test1() { }
// GOOD: Focused test
@Test
func userNameIsCapitalized() {
let user = User(name: "alice")
#expect(user.displayName == "Alice")
}
// AVOID: Multiple unrelated assertions
@Test
func userTests() {
let user = User(name: "alice")
#expect(user.displayName == "Alice")
#expect(user.email != nil)
#expect(user.createdAt <= Date())
}
// GOOD: Struct-based, each test gets fresh instance
@Suite
struct UserTests {
let service = UserService()
@Test func fetch() { }
@Test func create() { }
}
// GOOD: Parameterized
@Test(arguments: ["", " ", " "])
func emptyStringsAreInvalid(input: String) {
#expect(!isValid(input))
}
// AVOID: Duplicated tests
@Test func emptyIsInvalid() { #expect(!isValid("")) }
@Test func spaceIsInvalid() { #expect(!isValid(" ")) }
@Test func spacesAreInvalid() { #expect(!isValid(" ")) }
extension Tag {
@Tag static var unit: Self
@Tag static var integration: Self
@Tag static var slow: Self
}
// Filter in Xcode or command line
// swift test --filter "unit"
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 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 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.