Swift 5.10 Concurrency and XCTest

I think it’s solved in nightly builds now, so should be in the next beta.

1 Like

Has the adoption of @MainActor changed in some way regarding XCTestCase since this?

I'm running the Xcode 16.1 beta and trudging through issues with our tests, many of which were originally done to get around this. We have some setUp overrides marked @MainActor where the entire test case subclass would have been marked @MainActor prior to Swift 5.10, however now it seems to complain instead about marking setUp as @MainActor, but not the subclass. What is the direction here?

1 Like

XCTestCase is not actor-isolated, nor do we plan to change that. It sounds like you're running into a newer, stricter concurrency diagnostic added or enforced in Swift 6. If you could share the full diagnostic output, we may be able to help you resolve the issue.

I have some setUp methods declared @MainActor to call something that is main actor isolated. But in Xcode 16.0 I am getting the warning:

Main actor-isolated instance method 'setUp()' has different actor isolation from nonisolated overridden declaration; this is an error in the Swift 6 language mode

/Users/…/ViewControllerPresentationSpy/SwiftSample/Tests/ViewControllerAlertTests.swift:14:19: warning: main actor-isolated instance method 'setUp()' has different actor isolation from nonisolated overridden declaration; this is an error in the Swift 6 language mode
    override func setUp() {
                  ^
XCTest.XCTest:17:15: note: overridden declaration is here
    open func setUp()
              ^

How should I resolve set-up situations like this? This example is in GitHub - jonreid/ViewControllerPresentationSpy: Unit test presented and dismissed iOS view controllers, including alerts and action sheets, using the project in the SwiftSample folder.

If I remove the @MainActor declaration, I get a number of issues around calling UIKit at all, such as:

Call to main actor-isolated instance method 'loadViewIfNeeded()' in a synchronous nonisolated context; this is an error in the Swift 6 language mode

@jonreid here is a similar issue being tracked. I do not know if there is a workaround.

Thanks, @vanvoorden! Happy to continue the discussion here or there.

I think you can work around the issue by favoring the async throws version of setUp, which will allow you to await actor-isolated functionality.

1 Like

@jonreid In addition turning on the upcoming feature for "global actor usability" will allow you to make a subclass of XCTestCase a @MainActor.

The only thing I don't understand is, if you then try to access a property on your subclass from within the async throws version of setUp it requires self to be Sendable, which the compiler will let you do, but seems like SE-0434 would suggest that should result in an error.

I was able to move forward under complete concurrency checking (Swift 5 language mode) by turning on that upcoming flag, moving the @MainActor back to the subclass as a whole, and convert to use the async throws version of setUp and marking the subclass Sendable. This lets you access self and all of its properties without an await because an async function on a @MainActor class is considered bound to the @MainActor (unless on an extension or whatnot).

1 Like

In Xcode 16.1 through 16.2b3 I'm seeing that annotating an XCTestCase subclass with @MainActor is allowed under Swift 6 language mode with Complete strict concurrency checking. Can someone confirm if this is intentional or a temporary bug?

For example, this compiles and runs without errors and the test passes.

@MainActor final class TestActorExampleTests: XCTestCase {
    func testExample() throws {
        let viewController = UIViewController()
        viewController.title = "test"
        XCTAssertEqual(viewController.title, "test")
    }
}
3 Likes