Modernizing Your Tests: A Practical Guide to Swift Testing Framework
XCTest
Test setup typically goes into an overridden setUp() method:
class FoodTruckTests: XCTestCase {
var batteryLevel: NSNumber!
override func setUp() async throws {
batteryLevel = 100
}
...
}
XCTest Example
Swift Testing
Setup is cleaner and Swift-native:
struct FoodTruckTests {
var batteryLevel: NSNumber
init() async throws {
batteryLevel = 100
}
...
}
Swift Testing Example
✅
No special methods required — just use the init initializer to prepare test state.
XCTest
teardown looks like this:
class FoodTruckTests: XCTestCase {
var batteryLevel: NSNumber!
override func setUp() async throws {
batteryLevel = 100
}
override func tearDown() {
batteryLevel = 0
// drain the battery
}
...
}
XCTest Example
Swift Testing
You can use Swift’s native deinit:
final class FoodTruckTests {
var batteryLevel: NSNumber
init() async throws {
batteryLevel = 100
}
deinit {
batteryLevel = 0
// drain the battery
}
...
}
Swift Testing Example
Here’s how a typical XCTest case might look:
func testEngineWorks() throws {
let engine = FoodTruck.shared.engine
XCTAssertNotNil(engine.parts.first)
XCTAssertGreaterThan(engine.batteryLevel, 0)
try engine.start()
XCTAssertTrue(engine.isRunning)
}
XCTest Example
In Swift Testing, the same test is more expressive and lighter:
@Test func engineWorks() throws {
let engine = FoodTruck.shared.engine
try #require(engine.parts.first != nil)
#expect(engine.batteryLevel > 0)
try engine.start()
#expect(engine.isRunning)
}
Swift Testing Example
Key improvements:
#require safely unwraps and stops the test if needed.
#expect replaces multiple XCTAssert calls with one unified syntax.
You’re no longer forced to prefix functions with test.
Improvements when compared to XCTest
The introduction of @Test, #expect, #require, and traits like .tags, .timeLimit, and .bug makes your tests feel like first-class Swift code.
No more boilerplate test classes
No forced naming conventions
Your test code looks and feels like Swift — clean and expressive
Thanks to these macros, it’s much easier to understand what each test is doing at a glance. Descriptive traits and display names make your intent crystal clear.
@Test("Validates new checkout flow", .tags(.regression, .payment))
Readability Example
No need to write setUp() or tearDown() if you don’t need to. Use init and deinit like you would in regular Swift types.
Tests can now be written in:
Global functions
Structs
Final classes
Even actors
SPONSOR
The Unique iOS Swift Conference in the UK
SwiftLeeds is a premier iOS conference taking place on October 7-8 this year. If you’re looking to stay updated on the latest trends in iOS development and connect with like-minded professionals, this event is a must-attend! Don’t miss out—book your tickets now!
Get your tickets!Failures in Swift Testing are designed to be helpful. You get better failure messages with context, and in many cases, suggestions or expectations clearly spelled out — not just red error lines.
Problem with XCTest
class UserModelXCTests: XCTestCase {
func testUserProfileUpdate() {
var user = User(name: "John", age: 25, email: "john@example.com")
user.updateProfile(name: "John Doe", age: 29)
// On failure, only shows true != false, not the actual values
XCTAssertTrue(user.name == "John Doe" && user.age == 26)
}
}
XCTest Example
Error:
XCTAssertTrue Failed
When this fails, the output is simply:
true is not equal to false
Hence, it’s not very helpful.
Swift Testing to the Rescue
struct UserModelTests {
@Test func userProfileUpdate() {
var user = User(name: "John", age: 25, email: "john@example.com")
user.updateProfile(name: "John Doe", age: 29)
// On failure, shows the full expression and all values
#expect(user.name == "John Doe" && user.age == 26)
}
}
Swift Testing Example
On failure, Swift Testing provides detailed expression breakdowns:
⚠️
Expectation failed: (user.name == "John Doe" → true) && (user.age == 26 → false)
You’ll also see a “Show” button to expand a full detailed view with all values involved in the condition.
Failure message
Detailed Error Message
Actionable Failures
✅
Swift Testing can coexist with XCTest
Both can live under the same unit test target, making gradual migration easy.
Simplify old XCTestCase classes with only one test → Move to a global @Test function
Consolidate multiple similar XCTests → Use parameterized tests for cleaner logic
Function naming is flexible → The word test at the beginning is no longer required
XCUIApplication-based UI automation
Performance testing with XCTMetric
Tests that must be written in Objective-C
Don’t mix Swift Testing’s #expect/#require with XCTAssert
Each framework has its own lifecycle and assertion style — mixing leads to unexpected behavior
Note
Please find the GitHub Repo
Please refer the previous article on Swift Testing Overview
To learn more about the interesting sections in Swift Testing
Swift Testing Framework Basics - Overview of Swift Testing Framework
Traits, Suites, Concurrency in Swift Testing Framework - Traits, Suites & Concurrency: Unlocking Swift Testing’s Full Potential
This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.