Swift Testing Setup and Teardown: Just Use init()

Swift Testing Setup and Teardown: Just Use init()
XCTest's setUp() and tearDown() lifecycle methods feel ceremonial. Swift Testing replaces them with something you already know: initializers.
Follow along with the code: iOS-Practice on GitHub
XCTest: The Ceremony
Here's the classic XCTest setup pattern:
class WeatherManagerTests: XCTestCase {
var mockService: MockWeatherService!
var sut: WeatherManager!
override func setUp() {
super.setUp()
mockService = MockWeatherService()
sut = WeatherManager(service: mockService)
}
override func tearDown() {
sut = nil
mockService = nil
super.tearDown()
}
func testFetchReturnsData() async throws {
let weather = try await sut.getWeather(for: "London")
XCTAssertEqual(weather.city, "London")
}
}
Notice the implicitly unwrapped optionals (!), the super calls, and the tearDown that just sets things to nil. This is boilerplate.
Swift Testing: Just Use init()
The same tests in Swift Testing:
struct WeatherManagerTests {
let mockService: MockWeatherService
let sut: WeatherManager
init() {
mockService = MockWeatherService()
sut = WeatherManager(service: mockService)
}
@Test func fetchReturnsData() async throws {
let weather = try await sut.getWeather(for: "London")
#expect(weather.city == "London")
}
}
No optionals. No forced unwrapping. No tearDown. Each test gets a fresh instance of the struct, so init() runs before every test automatically.
Why No Teardown?
Swift Testing creates a new instance of your test struct for each test. When the test completes, that instance goes out of scope and is deallocated. ARC handles cleanup automatically.
For most tests, this means teardown is simply unnecessary.
When You Actually Need Cleanup
Sometimes you have external resources that need explicit cleanup—temporary files, database connections, or resources that don't participate in ARC. Use a helper class:
final class TestResources {
let tempDirectory: URL
init() {
tempDirectory = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
try! FileManager.default.createDirectory(
at: tempDirectory,
withIntermediateDirectories: true
)
}
deinit {
try? FileManager.default.removeItem(at: tempDirectory)
}
}
struct FileSystemTests {
let resources = TestResources()
@Test func writesFile() throws {
let file = resources.tempDirectory.appendingPathComponent("test.txt")
try "Hello".write(to: file, atomically: true, encoding: .utf8)
#expect(FileManager.default.fileExists(atPath: file.path))
}
}
The helper class's deinit runs when the test struct is deallocated.
Async Setup
Need async setup? Use an async initializer isn't available, but you can use a static factory method with @Test:
struct WeatherTests {
let preloadedCache: [String: Weather]
init() async throws {
// Can't do async init directly, but tests can be async
preloadedCache = [:]
}
}
For complex async setup, consider doing it in the test itself:
@Test func fetchWithPreloadedCache() async throws {
// Setup
let sut = await WeatherManager.withPreloadedCache()
// Test
let weather = try await sut.getWeather(for: "London")
#expect(weather.city == "London")
}
Suite-Level Setup
Suites can share expensive setup across tests:
@Suite struct DatabaseTests {
// This is created once per suite, not per test
static let sharedDatabase = TestDatabase()
@Test func insertsRecord() async throws {
// Uses Self.sharedDatabase
}
@Test func queriesRecord() async throws {
// Uses Self.sharedDatabase
}
}
Use static for truly shared resources, and instance properties for per-test isolation.
Migration Summary
| XCTest | Swift Testing |
|---|---|
override func setUp() | init() |
override func tearDown() | Not needed (ARC) |
setUpWithError() throws | init() throws |
| Implicitly unwrapped optionals | Regular let properties |
| Inheritance | Struct composition |
Interview tip: "Swift Testing's approach to setup is refreshingly simple—I just use init() like any other Swift type. Each test gets a fresh instance, so I don't need tearDown. If I have external resources that need cleanup, I wrap them in a helper class with deinit."