Moving from XCTest to Swift Testing
Swift Testing is Apple’s replacement for the objective-C XCTest unit testing framework. Discover how to migrate your existing XCTest suites over to Swift Testing, including how to get some assistance from Xcode’s agentic AI tooling. By Renan Benatti Dias.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Moving from XCTest to Swift Testing
35 mins
- Getting Started
- Updating Your First Unit Test
- Importance of Unit Testing
- Updating OrderModel
- Understanding @Suite
- Understanding @Test
- Updating Assertion Functions
- Migrating Async Methods
- Running Unit Tests in Serial
- Migrating XCTUnwrap Tests
- Putting Everything Together
- Migrating CoffeeShopTests
- Migrating Set Up and Tear Down Methods
- Migrating Callback Methods
- Failing Tests on Purpose
- Asserting Error Types
- Testing Methods That Should Not Throw Errors
- Using Traits
- Disabling and Enabling Unit Tests
- Tracking Bugs
- Limiting Test Run times
- Tagging Tests
- Using Parameters to Test Permutations
- Using Xcode 27 AI Skill to Migrate Unit Test
- Where to Go From Here
Swift Testing is the latest and greatest testing framework from Apple. It was built by Apple Engineers to help Swift developers build unit tests in a modern, fast, simple and expressive way.
This new framework replaces the old unit test framework XCTest, that was built for Objective-C projects and was bridged to be used with Swift.
While XCTest works sufficiently, Swift Testing builds upon it by having a more modern syntax and new, useful constructs.
In this tutorial, you’ll learn about:
- How to migrate unit tests written with XCTest to Swift Testing.
- How Swift Testing handles most usual test scenarios covered by XCTest.
- Useful abilities specific to Swift Testing.
Additionally, you’ll also learn how to leverage Xcode’s Agentic Coding to help you migrate unit tests.
You’ll learn all about this by working on SwiftBrew, an app for ordering different types of coffee and keeping track of your orders.
So grab your favorite cup, because it’s time to brew some code!
Getting Started
Download the project materials by clicking the Download Materials button at the top or bottom of this tutorial. Open SwiftBrew.xcodeproj inside the Starter folder.
SwiftBrew is an app where users select their coffee order from a premium selection of brews, submit their order and brew it. It keeps receipts of orders and calculates the grand total of all your items.
Build and run the project to check it out.

It’s a simple app that leverages SwiftUI to build an intuitive UI that is simple and fast to use. However, you’ll be focusing only on the unit tests and won’t be changing the application code that defines the UI and logic of the app.
Updating Your First Unit Test
You’ll start by working on the tests for the code that handles the user’s orders, OrderModel. Inside the SwiftBrewTests group, open OrderModelTests.swift and review its code.
This class uses XCTest to test OrderModel, the view model that handles the logic of the order view. It tests several things such as the ability of the object to add orders, calculate the total and update the UI.
Importance of Unit Testing
XCTest was launched in 2013 by Apple as a framework that allowed developers to write unit tests for their apps. Unit tests check the functional correctness of small units of code. It’s a common practice for many kinds of developers, including iOS developers, to write tests to cover as much behavior of their app as possible to safeguard against later code changes breaking existing functionality.
XCTest was originally written to test Objective-C code and was later adapted to work with Swift. The framework served its purpose for many years and is still usable; but, with the wide adoption of Swift, a new Swift-ier testing framework, one taking advantage of it syntax and language features, was bound to come along. Enter Swift Testing, a new framework launched by Apple in 2024 that leverages Swift to allow developers to write unit tests in a simple and idiomatic way.
You’ll learn how to migrate all the unit tests of this project from XCTest to Swift Testing.
Updating OrderModel
Still inside OrderModelTests.swift, run the whole test file by clicking the diamond button on the line of the class definition.

Xcode will run all the unit tests of this file and you can see the results of each unit test under the Test Navigator in the left panel:

You can also see each individual test result in the Swift file indicated by a green check in the line of the test method, instead of the diamond button.

All unit tests of OrderModelTests.swift are already passing. You’ll update this file to use Swift Testing and you’ll rework each test method one by one.
First, import the new framework at the top of the file:
import Testing
Next, replace the declaration of the class with the following:
// 1
@Suite
// 2
struct OrderModelTests {
Here’s a breakdown of this change:
- Adds
@Suite(_:)to the type declaration, making this type a suite of tests - Changes
OrderModelTestsfrom a class to a struct.
Understanding @Suite
@Suite(_:) is a new macro that you use to group a set of related test functions. Here, you’re adding this macro to the type definition and making OrderModelTests a test suite.
Also notice that XCTestCase can only be implemented by class types but you can add @Suite(_:) to any type, not just classes, allowing you to change OrderModelTests from a class to a struct.
Next, find the following method declaration:
func test_adding_coffee_updates_items_and_total() {
And replace for the following:
@Test func addingCoffeeUpdatesItemsAndTotal() {
Understanding @Test
@Test(_:) is a new macro that is the center of Swift Testing. It declares a single unit test. Here, you’re adding @Test(_:) to the method declaration making it a method that Swift Testing may call to run a test.
Notice that you’re also renaming the method name to use camel case. With XCTest, the framework required each test method to start with test_, otherwise the framework would not recognize the method as a test method. And, following coding convention, test method names would be written using snake case.
However, with Swift Testing, @Test(_:) is all you need to make a method a unit test, allowing you to name it however you like. Additionally, coding convention remains the same as application code, where camel case is used to name functions. That’s simpler!
Click the diamond button on the line of the method declaration to run this test method.

Xcode runs the unit test and reports it as a success. That’s because Swift Testing interoperability allows you to run new unit tests with code from XCTest. Even without changing the inner code of the method, Swift Testing runs the test as usual.
Updating Assertion Functions
Now, you’ll replace the code that actually tests the model to use the new expectation macro.
Still inside OrderModelTests.swift, find the following code:
XCTAssertTrue(model.isOrderEmpty)
XCTAssertTrue(model.actionsAreDisabled)
And replace with the following:
#expect(model.isOrderEmpty == true)
#expect(model.actionsAreDisabled == true)
#expect(_:_:sourceLocation:) is a new macro from Swift Testing used to assert a specific expression. In this case, it checks that isOrderEmpty and actionsAreDisabled are true.
Next, find this piece of code:
XCTAssertFalse(model.actionsAreDisabled == false)
XCTAssertFalse(model.isOrderEmpty)
XCTAssertEqual(model.items.count, 2)
XCTAssertEqual(model.total, 7.75, accuracy: 0.001)
XCTAssertNil(model.errorDescription)
XCTAssertEqual(
model.items,
[
Coffee(kind: .latte, size: .large),
Coffee(kind: .espresso, size: .small)
]
)
And replace for:
// 1
#expect(model.actionsAreDisabled == false)
#expect(model.isOrderEmpty == false)
// 2
#expect(model.items.count == 2)
// 3
#expect(model.total == 7.75)
// 4
#expect(model.errorDescription == nil)
// 5
#expect(
model.items ==
[
Coffee(kind: .latte, size: .large),
Coffee(kind: .espresso, size: .small)
]
)
Here’s a breakdown of the code:
- Here, you replace
XCTAssertFalse(_:_:file:line:)by#expect(_:_:sourceLocation:)and assert thatisOrderEmptyandactionsAreDisabledarefalse - Next, use
expect(_:_:sourceLocation:)instead ofXCTAssertEqual(_:_:_:file:line:)to assert the count of orders to be 2 - Here, you also use
expect(_:_:sourceLocation:)to check the grand total of the order. Notice thatXCTAssertEqual(_:_:_:file:line:)has a parameter to check the accuracy oftotal. However, that’s not needed on the new macro - Then,
XCTAssertNil(_:_:file:line:)is replaced by#expect(_:_:sourceLocation:)too, where you just compareerrorDescriptiontonil - Finally, you use
#expect(_:_:sourceLocation:)to assert thatmodel.itemshas the correct array of Coffee
XCTAssertEqual(_:_:accuracy:_:file:line:) when you use the accuracy argument to check a decimal. In most cases expect(_:_:sourceLocation:) is going to be enough. However, if you need to compare two values to a specific accuracy you have to use isApproximatelyEqual() from the swift-numerics package.
Notice that unlike the old assertion methods, XCTAssertTrue(_:_:file:line:) and XCTAssertFalse(_:_:file:line:), you no longer need different methods to check different types of data. The #expect(_:_:sourceLocation:) macro already addresses all of that and you use simple Swift operators to express your expectation, making the code cleaner and easier to understand.
XCTAssert functions and what their equivalents are in Swift Testing. Most of them use the new #expect(_:_:sourceLocation:) macro.
Run the unit test and make sure it is passing.

Success! You just migrated your first unit test.
Next, you’ll learn how to migrate a test method that tests an async method call.