Boutique icon indicating copy to clipboard operation
Boutique copied to clipboard

Integration Testing - Suggestion / Protocol

Open dentvii opened this issue 1 year ago • 2 comments

Hello, I would like your suggestion on how to implement integration testing. This is my current implementation of a Repository Class, which caches the data and request data from a cloud service provider. All viewModels are injected with this actor.


actor DataRepository : ObservableObject , DataRepositoryProtocol {   
    @Stored(in:.cacheTrackerStore)           var cacheTracker                 : [CacheObject]
}


The problem: the @Stored property makes the cacheTracker variable a [CacheObject], but also neatly and brilliantly exposes the $cacheTracker publisher. This cannot be implemented on a protocol, as it does not allow for the variable to have property wrappers.

protocol DataRepositoryProtocol: ObservableObject, Actor {
    @MainActor var cacheTracker                 : [CacheObject] { get }
} 

Therefore self.dataRepository.$cacheTracker.$tems is not accessible, which makes impossible to do integration tests that leverage combine and publishers.

Does anyone have any suggestion on how, keeping the actor class code mostly unchanged, while still being able to create a mockData to pass on ViewModels, in order to create integration testing? Thanks as always!

dentvii avatar Apr 15 '24 17:04 dentvii

Hey @dentvii, as I mentioned over email it's unfortunate that Swift has a language-level limitation on using property wrappers in a protocol.

Instead of using a protocol I would suggest creating a struct or class that holds onto your Store, with an initializer that takes has a Store<CacheObject> parameter you would like to use, setting it to your @Stored property like this.

public init(store: Store<CacheObject>) {
    self._cacheTracker = Stored(in: store)
}

If you do that then you should be able to use @Stored and control the mock data by passing in a mock data Store. Hope that helps!

mergesort avatar Apr 15 '24 18:04 mergesort

Thanks. Sadly that didn't resolve the issue I was having (because I was unable to init the proposed actor with a mock store on init.


final actor CacheTrackerStore: ObservableObject {
    @Stored var cacheTracker: [CacheObject]

    init(store: Store<CacheObject>) {
        self._cacheTracker = Stored(in: store)
    }
}
actor DataRepository : ObservableObject , DataRepositoryProtocol {   
    @MainActor @Published        var cacheTracker                 : CacheTrackerStore
}

init() {
        self.cacheTracker = CacheTrackerStore(store: Store<CacheObject>(storage: SQLiteStorageEngine.default(appendingPath: "CacheObject"),
                                                                             cacheIdentifier: \.id
                                                                            ))
    }

This yields a Main actor-isolated property 'cacheTracker' can not be mutated from a non-isolated context; this is an error in Swift 6. Therefore the above would not work on the long run.

My solution was to clear the DataRepository actor on the setup of the integration tests, instead of a MockDataRepository using a protocol. If anyone has a better idea please do share.

dentvii avatar Apr 20 '24 19:04 dentvii