Testing with Swift Testing and XCTest
Prerequisites: Swift Package Manager
A test is code that checks your other code works. Once written, a test runs in a fraction of a second every time you change something, catching mistakes before your users do.
Swift has two testing frameworks:
- Swift Testing — the modern framework (Swift 6 / Xcode 16 and
later), using
@Testand#expect. - XCTest — the older framework, still widely used in existing projects and required for UI tests.
The code under test
// Sources/Shop/Cart.swift
public struct Cart {
public private(set) var items: [(name: String, price: Int)] = []
public init() {}
public mutating func add(_ name: String, price: Int) {
precondition(price > 0, "price must be positive")
items.append((name, price))
}
public var total: Int { items.reduce(0) { $0 + $1.price } }
}
Swift Testing
// Tests/ShopTests/CartTests.swift
import Testing
@testable import Shop
@Test func startsEmpty() {
let cart = Cart()
#expect(cart.total == 0)
#expect(cart.items.isEmpty)
}
@Test func totalAddsPrices() {
var cart = Cart()
cart.add("book", price: 12)
cart.add("pen", price: 3)
#expect(cart.total == 15)
}
@Test(arguments: [1, 5, 100])
func singleItemTotal(price: Int) {
var cart = Cart()
cart.add("item", price: price)
#expect(cart.total == price)
}
$ swift test
...
✔ Test startsEmpty() passed after 0.001 seconds.
✔ Test totalAddsPrices() passed after 0.001 seconds.
✔ Test singleItemTotal(price:) with 3 test cases passed after 0.001 seconds.
✔ Test run with 3 tests in 0 suites passed after 0.002 seconds.
(Output abbreviated; the exact wording varies a little between Swift versions.)
@Testmarks a test function.#expect(...)checks a condition. When it fails, the report shows the actual values on each side, such ascart.total → 12 == 15.@Test(arguments:)runs the same test with several inputs.@testable import Shoplets tests useinternalmembers, not justpublicones.
The same test in XCTest
import XCTest
@testable import Shop
final class CartTests: XCTestCase {
func testTotalAddsPrices() {
var cart = Cart()
cart.add("book", price: 12)
cart.add("pen", price: 3)
XCTAssertEqual(cart.total, 15)
}
}
Test methods start with test, and assertions are functions such as
XCTAssertEqual, XCTAssertTrue, and XCTAssertNil.
In Xcode, press ⌘U to run all tests, or click the diamond next to a single test.
What to test
Test your logic — calculations, validation, parsing, state changes — including edge cases: empty input, zero, the limits of a range, and error cases. Keep that logic out of your views, so it's easy to test.
Common mistakes
- Testing private implementation details instead of behavior. Test what goes in and what comes out.
- Tests that depend on the network, the clock, or each other. Make each test self-contained.