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.
Introducing a Dedicated Testing
CurrentValueSubjectwhen using@ObservableA simple solution would be to introduce a
testVoiceListSubjectin our ViewModel to observe changes in thevoiceListproperty. Below is how we can adjust our ViewModel and test class accordingly:This approach works great because we get all the simplicity and performance benefits from the new
@Observablemacro, since thisCurrentValueSubjectdoesn't have any side effects like@Publishedhad, and it requires minimal setup. We assume that someday Apple will add a way to listen to properties in classes that use the@Observablemacro directly, which could provide a more integrated solution. For now, this method allows us to retain the testability of our ViewModel while adapting to the new@Observablemacro, ensuring a smooth transition as we leverage the new features in Swift.