在 swiftui 中使用 combine 模拟视图模型

Mocking view model in swiftui with combine

有没有办法模拟使用 SwiftUI 和 Combine 的应用程序的视图模型?我总是找到有关视图模型使用的模拟服务的文章,但从不模拟视图模型本身。

我尝试为每个视图模型创建协议。问题:@Published 包装器不能在协议中使用。似乎没有解决方案...

感谢您的帮助

如果视图模型只是一个模型,您可能根本不应该模拟它,而是模拟修改视图模型的东西。如果您的视图模型实际上拥有自我更新的功能和事物,那么您会想直接模拟它。在这种情况下,您可以使用协议来模拟视图模型。看起来有点像这样。

protocol ViewModel {
    var title: String { get set }
}

class MyViewModel: ViewModel {
    @Published var title: String = "Page title"
}

class MockViewModel: ViewModel {
    @Published var title: String = "MockPage title"
}

然而,这可能是我更愿意继承 class 而不是遵守协议的一个例子。然后我会为模拟覆盖 class 的功能。

open class ViewModel {
    @Published var title: String

    open fun getPageTitle() {
        title = "This is the page title"
    }
}

class MockViewModel: ViewModel {
    override fun getPageTitle() {
        title = "some other page title"
    }
}

任何一种方法都行得通。如果您的视图模型也具有功能,那么继承方式在您的测试套件中就不会那么冗长。

使用协议类型 @ObservableObject@StateObject 将不起作用。继承可能是一种解决方案(如 Jake 建议的那样),或者您可以使用通用解决方案。

protocol ContentViewModel: ObservableObject {
    var message: String { get set }
}

你的视图模型会很简单。

final class MyViewModel: ContentViewModel {
    
    @Published var message: String
    init(_ message: String = "MyViewModel") {
        self.message = message
    }
}

另一方面,如果使用受约束的泛型,您的视图会更加复杂。

struct ContentView<Model>: View where Model: ContentViewModel {
    @ObservedObject
    var viewModel: Model
    
    var body: some View {
        VStack {
            Text(viewModel.message)
            Button("Change message") {
                viewModel.message = ""
            }
        }
    }
}

缺点是在使用视图时必须定义泛型具体类型 --- 继承可以避免这种情况。

// your mock implementation for testing
final class MockViewModel: ContentViewModel {
    @Published var message: String = "Mock View Model"
}

let sut = ContentView<MockViewModel>(viewModel: MockViewModel())