I have a set up like this:
class ViewModel: ObservableObject {
@Published var data: [Int] = []
func fetchData() {
someAPICall() { result in
data = result // Assume 10 items get inserted here
}
}
}
I write a test like so:
func testViewModel() {
let waitExpectation = expectation(description: "testVM")
let viewModel = ViewModel()
viewModel.$contentUnitViewModels
.dropFirst() // Ignore the publish that happens when the VM is initialized
.sink { data in
waitExpectation.fulfill()
XCTAssertEqual(viewModel.data.count, 10)
// viewModel.data is empty but data has the expected 10 values
}
.store(in: &cancellables)
viewModel.fetchData()
waitForExpectations(timeout: waitExpectationTimeout) { error in
XCTAssertNil(error)
}
}
The issue is that when I used the published property data
with a SwiftUI view, the data gets rendered / loaded as expected.
However, during tests, for some reason, viewModel.data
is empty and viewModel.data.count
is 0.
Funnily enough, the data
property passed within the completion handler has 10 values.
It seems that the publish
happens before the actual underlying array is set.
Is there anyway to fix this / improve my code so that I only get notified when the actual underlying array is set ?
2
Answers
The answer by Malhal (using async await) and the suggestions by Sweeper (completion handler) are the actual answers, however, this would mean me refactoring a lot of old code so I did have to work with the
@Published
property.What I did just for the test was to add a delay prior to processing the data which was sufficient for the value to be set.
This is not the ideal answer, however, wanted to share the approach I went with.
You could improve it by using task then you don’t need the object, eg
The advantage with task is it runs on appear and cancels on dissapear, which you forgot to implement in your object. Also, now the async func in the controller can be tested.