Running Asynchronous Setup Side Effects in `@Test` with Swift Testing

đź§© Introduction

Sometimes in test code, we want to perform side effects just before a test case runs — without placing that setup logic directly inside the test function.

Swift Testing supports Arrange phases before a test, but it doesn’t (y…


This content originally appeared on DEV Community and was authored by Yoshinori Imajo

đź§© Introduction

Sometimes in test code, we want to perform side effects just before a test case runs — without placing that setup logic directly inside the test function.

Swift Testing supports Arrange phases before a test, but it doesn’t (yet) allow asynchronous or side-effectful setup directly in @Test.

Sure, helper functions work — but what if we could express Arrange–Act–Assert (AAA) visibly and cleanly inside Swift Testing itself?

If you have suggestions for improvements, feel free to comment.

🎯 Goal

Here’s what we’d like to achieve:

// MARK: - Test case and its preparation
@Test(
    // MARK: Arrange
    // Ideally, asynchronous side effects could run here
)
func testCase1() async throws {
    // MARK: Action
    // MARK: Assertion
}

In short, this post shows that — yes, you can do this.
@Test already supports parameterized tests, so we can leverage that mechanism to perform async setup work.

đź’ˇ Motivation

You might think:

“You could already do this using TestTrait or TestScoping’s setUp().”

And yes — you’d be right. But I wanted per-test asynchronous setup, not a global one.

Example: testing a “read” operation might require writing data first. I want that Write to live right next to the test that reads it — not far away in a shared helper.

Of course, helper functions are an option, but they separate intent and context. I wanted my test’s Arrange to be visually and contextually adjacent to the test logic itself.

đź§© Example: Using Realm

Let’s look at a concrete example using Realm.

Realm is common in iOS apps, but it can behave unexpectedly across threads.
Testing concurrency-heavy code with Realm requires careful context handling.
In particular, the in-memory configuration is sensitive to scope.

Here’s a test that demonstrates how to perform async setup directly via @Test:

@Test(MyTestTrait(
    actions: [ // Perform pre-test side effects here
        { realm in
            print("Arrange Step 1:", realm, Thread.current)
        },
        { realm in
            // Another example — not always necessary
            print("Arrange Step 2:", realm, Thread.current)
        }
    ]
))
func example() async throws {
    guard let realm = RealmContext.current?.realm else {
        XCTFail("Realm not injected")
        return
    }

    // Perform Realm tests
    print("đź§Ş Using realm:", realm, Thread.current)
}

đź§© TaskLocal Context

We can define a TaskLocal context to safely inject the Realm instance:

struct RealmContext {
    @TaskLocal static var current: RealmContext?
    let realm: RLMRealm
}

This allows RealmContext.current to be available anywhere inside the async test body.

⚙️ Implementing the Trait

Here’s a minimal MyTestTrait implementation that performs setup and cleanup around the test body:

struct MyTestTrait {
    // These actions are passed in from the test
    let actions: [@Sendable (RLMRealm) async throws -> ()]
}

extension MyTestTrait: TestTrait, TestScoping {
    func provideScope(
        for test: Test,
        testCase: Test.Case?,
        performing function: @Sendable () async throws -> ()
    ) async throws {

        // Setup Realm (in-memory)
        let realm = {
            let config = RLMRealmConfiguration.default()
            config.inMemoryIdentifier = UUID().uuidString
            RLMRealmConfiguration.setDefault(config)
            return RLMRealm.default()

            // NOTE: This modifies the global .default configuration.
            // It can leak into other tests, so use with caution.
        }()

        // Execute Arrange steps sequentially
        for action in actions {
            try await action(realm)
        }

        // Run the test body within the scoped context
        try await RealmContext.$current.withValue(.init(realm: realm)) {
            try await function()
        }
    }
}

This keeps setup logic close to your test definition,
while still leveraging Swift Testing’s structured concurrency and scoping.

đź§© Takeaways

  • âś… You can use parameterized traits to perform async setup directly in @Test
  • âś… TaskLocal makes dependency injection ergonomic and thread-safe
  • ⚠️ Beware: changing Realm’s global configuration can leak between tests
  • đźš« localActions = actions–style “safety copies” are unnecessary; traits aren’t shared across tests
  • đź§  Prefer direct initializers to redundant static factories

Notes

  • Tested on Swift 6.1, Xcode 16.3
  • RLMRealm is not Sendable — protect access carefully
  • For projects using DI frameworks, this approach may overlap with existing patterns
  • Yes, whether you should test side effects is a valid philosophical question


This content originally appeared on DEV Community and was authored by Yoshinori Imajo


Print Share Comment Cite Upload Translate Updates
APA

Yoshinori Imajo | Sciencx (2025-10-23T07:55:56+00:00) Running Asynchronous Setup Side Effects in `@Test` with Swift Testing. Retrieved from https://www.scien.cx/2025/10/23/running-asynchronous-setup-side-effects-in-test-with-swift-testing-2/

MLA
" » Running Asynchronous Setup Side Effects in `@Test` with Swift Testing." Yoshinori Imajo | Sciencx - Thursday October 23, 2025, https://www.scien.cx/2025/10/23/running-asynchronous-setup-side-effects-in-test-with-swift-testing-2/
HARVARD
Yoshinori Imajo | Sciencx Thursday October 23, 2025 » Running Asynchronous Setup Side Effects in `@Test` with Swift Testing., viewed ,<https://www.scien.cx/2025/10/23/running-asynchronous-setup-side-effects-in-test-with-swift-testing-2/>
VANCOUVER
Yoshinori Imajo | Sciencx - » Running Asynchronous Setup Side Effects in `@Test` with Swift Testing. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/10/23/running-asynchronous-setup-side-effects-in-test-with-swift-testing-2/
CHICAGO
" » Running Asynchronous Setup Side Effects in `@Test` with Swift Testing." Yoshinori Imajo | Sciencx - Accessed . https://www.scien.cx/2025/10/23/running-asynchronous-setup-side-effects-in-test-with-swift-testing-2/
IEEE
" » Running Asynchronous Setup Side Effects in `@Test` with Swift Testing." Yoshinori Imajo | Sciencx [Online]. Available: https://www.scien.cx/2025/10/23/running-asynchronous-setup-side-effects-in-test-with-swift-testing-2/. [Accessed: ]
rf:citation
» Running Asynchronous Setup Side Effects in `@Test` with Swift Testing | Yoshinori Imajo | Sciencx | https://www.scien.cx/2025/10/23/running-asynchronous-setup-side-effects-in-test-with-swift-testing-2/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.