Skip to content

TestStore expects shared state to be nil when state was set to nil in an independent test showing the same destination #3468

@dominikmayer

Description

@dominikmayer

Description

I have the following struct, which is shared in memory:

struct Journal: Equatable {}

extension PersistenceReaderKey where Self == PersistenceKeyDefault<InMemoryKey<Journal?>> {
    static var journal: Self {
        return PersistenceKeyDefault(.inMemory("journal"), nil)
    }
}

I then have a feature that can show two other features, one of which contains the shared journal:

@Reducer
struct EntryFeature {
    
    @ObservableState
    struct State: Equatable {
        var url: URL
        @SharedReader(.journal) var journal
    }
}

@Reducer
struct DayFeature {
    
    @ObservableState
    struct State: Equatable {
        var entries: [EntryFeature.State]
    }
}

And the feature that shows them:

@Reducer
struct CalendarFeature {
    
    @Reducer(state: .equatable)
    enum Destination {
        case day(DayFeature)
        case entry(EntryFeature)
    }
    
    @ObservableState
    struct State: Equatable {

        @Presents
        var destination: Destination.State?
        @Shared(.journal) var journal
    }
    
    enum Action: Equatable {
        case destination(PresentationAction<Destination.Action>)
        case loadEntry(URL)
    }
    
    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
                
            case .loadEntry(let url):
                let updatedEntryState = EntryFeature.State(url: url)
                state.destination = .entry(updatedEntryState)
                return .none
                
            case .destination:
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

extension CalendarFeature.Destination.Action: Equatable {}

Now when trying to test this setup then the test passes when run on its own but fails when run together with another, independent test, that

a) shows the same destination as will be set in the brittle test and
b) sets the shared journal to nil.

This happens even when the test are run in .serialized mode.

@Suite(.serialized)
@MainActor
struct Test_CalendarFeature {

    let journal = Journal()
        
    @Test
    func settingSharedVariableNil() async {
        // We never use this store but if we remove it, the issue doesn't appear
        let _ = TestStore(
            initialState: .init(
                // This has to be the same entry that will be set by the other test. You can try to change it to .entryFeature2 and everything passes
                destination: .day(.init(entries: [.entryFeature1]))
            )
        ) {
            CalendarFeature()
        }

        @Shared(.journal) var sharedJournal
        $sharedJournal.withLock {
            $0 = nil
        }
        // We can but don't even need to send an action here
    }
    
    // This test passes when it's run alone but fails when it's run as part of the suite together with the other test, even when both tests are serialized
    @Test
    func brittleTest() async {
        @Shared(.journal) var sharedJournal
        $sharedJournal.withLock {
            $0 = journal
        }

        let store = TestStore(
            initialState: .init()
        ) {
            CalendarFeature()
        }
        
        await store.send(.loadEntry(.entry1)) {
            $0.destination = .entry(.entryFeature1)
        }
    }
}

// MARK: - Test Items
extension URL {
    static let entry1 = URL(fileURLWithPath: "/some/path/")
    static let entry2 = URL(fileURLWithPath: "/some/other/path/")
}

extension EntryFeature.State {
    static let entryFeature1 = EntryFeature.State(
        url: .entry1
    )
    static let entryFeature2 = EntryFeature.State(
        url: .entry2
    )
}

Checklist

  • I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

The test yields the same result whether run independently or as part of a suite.

Actual behavior

Test passes when run independently but not when run as part of a test suite.

Reproducing project

https://github.com/dominikmayer/TCA-Shared-Test-Issue/

The Composable Architecture version information

1.15.2

Destination operating system

macOS 15.0

Xcode version information

Version 16.0 (16A242d)

Swift Compiler version information

swift-driver version: 1.115 Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2)
Target: arm64-apple-macosx15.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions