I have just migrated from using ObservableObject and the @Published property wrapper to the new @Observable macro in Swift. I am wondering how I can rewrite these kind of tests. Please keep in mind that my tests cover arbitrarily complex cases with asynchronous behavior, and this is just an oversimplified example.
func testVoiceListUpdates() {
let initialExpectation = XCTestExpectation(description: "Voice list should be empty initially")
let firstExpectation = XCTestExpectation(description: "First voice should be added")
let secondExpectation = XCTestExpectation(description: "Second voice should be added")
let viewModel = MyViewModel()
let firstVoice = Voice.fixture()
let secondVoice = Voice.fixture()
viewModel.$voiceList
.sink { newValue in
switch newValue.count {
case 0:
initialExpectation.fulfill()
case 1:
if newValue.first == firstVoice {
firstExpectation.fulfill()
viewModel.addVoice(secondVoice)
}
case 2:
if newValue.last == secondVoice {
secondExpectation.fulfill()
}
default:
break
}
}
.store(in: &cancellables)
viewModel.addVoice(firstVoice)
wait(for: [initialExpectation, firstExpectation, secondExpectation], timeout: 1)
}
The point is that in the MVVM architecture all of the logic is in the ViewModel, and all of the visible behavior can be fully tested by just testing the ViewModels. How are we supposed to do this with the new @Observable macro? Any suggestions and best practices would be appreciated.
You can use the withObservationTracking(_:onChange:) function, and your test would change to something like this:
Any properties accessed in the first closure passed to
withObservationTrackingwill result in theonChangeclosure being executed, where you can schedule another observation (as theonChangeclosure is called only once).This enables monitoring multiple properties, allowing more complex scenarios, for example: