Swift Testing

For years, Swift developers have relied on the XCTest framework for their testing needs. While powerful and mature, XCTest carries a legacy from its Objective-C origins. With the evolution of the Swift language, the community has longed for a testing framework that feels truly native to Swift’s modern, expressive, and safe programming paradigm.

At WWDC 2024, Apple answered this call by unveiling Swift Testing, a brand-new, open-source testing framework designed from the ground up for modern Swift development. It is not just an update; it is a fundamental rethinking of how testing should work in the Swift ecosystem.  

A Modern, “Swifty” Approach

Swift Testing is built on a foundation of modern Swift features, making it more intuitive, powerful, and concise than its predecessor. Its core design goals are clarity and expressiveness, achieved through the clever use of Swift macros, seamless integration with Swift Concurrency, and a strong preference for value semantics.   Key characteristics of the new framework include:

  • Expressive, Macro-Based API: Features like the @Test attribute and #expect macro allow you to declare and validate tests with minimal, highly readable code.  
  • Concurrency-Aware: Tests can be written using async/await and are run in parallel by default, leveraging the full power of modern hardware and Swift’s concurrency model.  
  • Cross-Platform and Open Source: Swift Testing is designed to work consistently across all platforms supported by Swift, including Apple platforms, Linux, and Windows. Its development is open, allowing the community to contribute to its evolution.  
  • Interoperability with XCTest: The framework is designed to coexist peacefully with XCTest. You can have both Swift Testing and XCTest tests within the same target, allowing for a gradual, incremental migration at your own pace.  

The design philosophy behind Swift Testing represents a significant shift. XCTest, with its class-based inheritance (XCTestCase) and collection of specific assertion functions (XCTAssertEqual, XCTAssertNotNil, etc.), can feel like a separate sub-language for developers new to the ecosystem. Swift Testing, in contrast, leverages the very features that define modern Swift. It uses macros like  

@Test, encourages simple boolean expressions like #expect(user.name == "Alice"), and promotes the use of value types like struct for organizing tests.  

By aligning the testing syntax with idiomatic Swift, the framework dramatically lowers the cognitive overhead required to write tests. Developers no longer need to switch contexts between writing application code and writing test code. This seamless integration is more than a technical upgrade; it’s a cultural push to make testing a more natural, accessible, and integral part of the daily workflow for every Swift developer.

Setting Up Your First Test Target

Getting started with Swift Testing in Xcode is straightforward. When you create a new project, you have the option to select it as your default testing framework.

  1. Open Xcode (version 16 and higher) and choose “Create a new project.”
  2. Select your desired template (e.g., under the iOS tab, select “App”).
  3. In the project options sheet, you will find a section for “Testing System.”
  4. From the dropdown menu, select “Swift Testing with XCTest” (instead of the default Node).  

That’s it. Xcode will automatically configure a test target in your project, complete with a template file that imports the Testing module and includes a sample test. If you are working with an existing project, you can add a new test target and choose Swift Testing, or simply start adding new Swift files with import Testing to your existing XCTest target.  

The Four Building Blocks

To navigate this new world, it helps to have a mental model. Apple’s engineers have presented Swift Testing as being built upon four core concepts. Understanding these “four building blocks” will provide a clear framework for everything that follows in this lesson :  

  1. @Test: An attribute that you place on a function to declare that it is a test. This is the entry point, the signal to the framework that a piece of code is a test case.
  2. Expectations (#expect & #require): A pair of powerful macros used to validate conditions within your tests. They are the tools you use to check if your code is behaving as expected.
  3. Traits: A flexible system for modifying and annotating the behavior of your tests. Traits allow you to add metadata, set conditions, or change how a test is executed.
  4. @Suite: A way to group and organize related tests using Swift’s own type system (structs, classes, or actors). Suites provide structure and hierarchy to your test code.

We will now explore each of these building blocks in detail, starting with the fundamental mechanics of defining a test and validating its behavior.

See forum comments
Download course materials from Github
Previous: Why We Write Unit Tests Next: Your First Test with @Test