Testing Swift Testing
At WWDC24, Apple introduced Swift Testing, which is a new way to write tests in Swift, practically replacing XCTest for unit tests. And it’s great.
There are two sessions that give a great introduction to the new framework, and I recommend checking them out:
Introduction
Where with XCTest, we’d need setting up an XCTestCase
subclass:
import XCTest
class MyTests: XCTestCase {
func testFiltering() {
// test here
}
}
Using Swift Testing requires barely any set up at all. At it’s simplest, we can do the following:
import Testing
@Test func filtering() {
// test here
}
And where with XCTest, we had a whole range of XCTAssert*
functions, Swift
Testing has one “assert” to rule them all: #expect
is built using the power
of macros, and is nice:
@Test func filtering() {
let input = [1, 3, 2]
let expected = [1, 2, 3]
#expect(input.sorted() == expected)
}
If we’d have forgotten to call .sorted()
, we’d get failure output:
@Test func filtering() {
let input = [1, 3, 2]
let expected = [1, 2, 3]
#expect(input == expected)
// Expectation failed: (input → [1, 3, 2]) == (expected → [1, 2, 3])
}
… which also works nicely with things that wouldn’t get rich diagnostics in
XCTest, like .contains
:
@Test func filtering() {
let input = [1, 3, 2]
#expect(input.contains(5))
// Expectation failed: (input → [1, 3, 2]).contains(5)
}
Beyond the basics
Swift Testing supports parameterized testing, allowing us to pass multiple parameters to test at once, where before we’d need to define either individual test functions or loop over a sequence. What’s particularly neat about it is that Xcode makes it so you can see exactly which parameter input may have failed, and allows you to rerun that one input separately.
@Test(arguments: [1, 2, 3, 4])
func filtering(expected: Int) {
let input = [1, 3, 2]
#expect(input.contains(expected))
// Expectation failed: (input → [1, 3, 2]).contains(expected → 4)
}
Which show up in the Test inspector as such:
How do I..?
Some parts you might be familiar with in XCTest have different names under Swift Testing, which may require a bit to get used to. Here’s some:
let result = try XCTUnwrap(myOptional)
// becomes
let result = try #require(myOptional)
XCTFail("You shall not pass")
// becomes
Issue.record("You shall not pass")
XCTAssertTrue(
true,
file: #file, // StaticString
line: #line // UInt
)
// becomes
#expect(
true,
sourceLocation: SourceLocation(
fileID: #fileID, // String
filePath: #filePath, // String
line: #line, // Int
column: #column // Int
)
)
XCTAssertEqual(1.0, 1.0, accuracy: 0.1)
// becomes... tricky. Apple recommends to use `isApproximatelyEqual()` from
// its `swift-numerics` package.
Grouping tests
You can group tests with tags, part of the trait system of Swift Testing. You can do so across package boundaries, which makes it so you’ll probably want to create a package to define said tags, which you’d do like this:
extension Tag {
@Tag static var subscriptions: Self
}
To apply this to a test or test suite, use
@Test(.tags(.subscriptions))
or in a group of tests
@Suite(.tags(.subscriptions))
struct Filtering {
@Test func filter() {}
}
Tags show up in the test inspector, similar to what we saw before with parameterized tests. They can be run from there, or inspected to see if a change you made may have impacted related tests.
I don’t / can’t use Xcode 16 yet!
… bummer. That means you’ll have to wait to start using Swift Testing, although if you have the time, you could use Xcode 16 to start converting tests, and still merge them into your Xcode 15-branch like so:
#if compiler(>=6.0)
import Testing
@Test func filter() {}
#endif
… which may or may not be useful for your project. You can otherwise keep things in a separate branch.
Closing thoughts
Only having scratched the surface, it’s fun to write tests with Swift Testing. I’d recommend you try it out and see how it can clean up some of your tests, perhaps starting with those taking multiple arguments.
I’d recommend taking a look at the documentation on migrating a test from XCTest, which mentions a bunch of great comparisons between the two frameworks that’ll help get you started.
Let me know how you get on