Swift Testing is Apple’s modern replacement library for XCTest and introduces new macros like #require. We’ve covered the #expect macro before and are about to dive into its companion to set requirements for a given test.
Each #require macro needs to pass for a test to continue. It’s kind of an early guard not to continue unless all requirements are met. While you don’t need the macro to write tests using Swift Testing, it will make your tests clearer and easier to understand. Let’s dive in.
Setting test requirements using #require
Many tests need specific input requirements before they can start testing the target. You could write these requirements using regular assertions with the #expect macro. For example, when we have a Person
struct with an optional initializer:
struct Person {
let firstName: String
let lastName: String
var fullName: String {
firstName + " " + lastName
}
init?(firstName: String, lastName: String) {
guard !lastName.isEmpty else {
return nil
}
self.firstName = firstName
self.lastName = lastName
}
}
We could write a requirement for testing the fullName
to ensure our constructed person is not nil:
@Test func fullName() {
let person = Person(firstName: "Antoine", lastName: "van der Lee")
#expect(person != nil, "Person should be constructed successfully")
#expect(person?.fullName == "Antoine van der Lee")
}
However, the #expect macro doesn’t make it readable and clear that it’s a requirement for the test even to get started. Self-explanatory code is robust and should always be your aim when working on a project. Therefore, we can rewrite the above test using the #require macro as follows:
@Test func fullName() throws {
let person = Person(firstName: "Antoine", lastName: "van der Lee")
try #require(person != nil, "Person should be constructed successfully")
#expect(person?.fullName == "Antoine van der Lee")
}
It’s a slight nuance, but the test becomes easier to understand. We can further optimize this test by using the return value of the #require macro.
Replacing XCTUnwrap
When using XCTest, we’ve learned that we can use XCTUnwrap to unwrap an optional or fail the test if it returns nil.
func testFullName() throws {
let person = Person(firstName: "Antoine", lastName: "van der Lee")
let unwrappedPerson = try XCTUnwrap(person)
XCTAssertEqual(unwrappedPerson.fullName, "Antoine van der Lee")
}
In Swift Testing, we can create similar behavior by using the #require macro return value:
@Test func fullName() throws {
let person = Person(firstName: "Antoine", lastName: "van der Lee")
let unwrappedPerson = try #require(person, "Person should be constructed successfully")
#expect(unwrappedPerson.fullName == "Antoine van der Lee")
}
The #require macro will fail the test immediately with a clear message if the value returns nil:
Our test improved since we no longer have an optional within the #expect statement. This makes it clear that if our test fails at that line, it’s because the full name is wrong rather than the person being nil. These little improvements can significantly impact if you’re debugging tests and you don’t know what caused a failure.
Conclusion
The Swift Testing framework allows us to write modern tests with new APIs and macros like #require. While you don’t need the #require macro to write tests, it will make your code self-explanatory and more robust.
Here are a few more articles for you to make the most out of Swift Testing:
- Using the #require macro for Swift Testing
- Vapor and Swift Testing: Running tests in parallel
- Using the #expect macro for Swift Testing
- Using Traits to annotate and customize test behavior
- Swift Testing: Validate your code using expressive APIs
Thanks!