My SwiftUI app consists of different views with their own view models which handle data fetching etc. This works fine in the simulator, but I'd really like to be able to use previews and have each view appear in it's different state (loading, success, error etc.)
Here's a simple example of how a page might be built:
import SwiftUI
import Observation
@Observable
class TestViewModel {
enum State {
case loading
case success
}
private(set) var state = State.loading
func loadData() async {
// Make async network request here and update state
}
}
struct TestView: View {
@State private var viewModel = TestViewModel()
var body: some View {
Group {
switch viewModel.state {
case .loading: Text("Loading...")
case .success: Text("Success!")
}
}.task {
await viewModel.loadData()
}
}
}
#Preview {
TestView()
}
How would I go about mocking the view model/network response for previews?
You can add an init
and then inject another view model for the preview that is either a sub class of your current view model or you can create a protocol that your view model and any mock object conforms to.
Below is an example using the protocol approach
protocol DataLoading: Observable {
var state: TestViewModel.State { get }
func loadData() async -> Void
}
@Observable
class TestViewModel: DataLoading {
//...
}
struct TestView: View {
@State private var viewModel: DataLoading
init(viewModel: DataLoading = TestViewModel()) {
self.viewModel = viewModel
}
//...
}
And then you can create a mock like this
@Observable
class MockViewModel: DataLoading {
private(set) var state = TestViewModel.State.loading
func loadData() async {
try? await Task.sleep(for: .milliseconds(300))
state = .success
}
}
If you do this then I would strongly suggest you move the enum declaration outside of the view model declaration