Avatar
Avatar
shimastripe
夜分遅くに失礼します。 https://github.com/koher/login-challenge-slides 以前のswift-zoominを復習していてどう解決すべきかわからず気になりご意見をお聞きできないでしょうか? 型パラメータのInjectionを用いて、@StateObject private var state: HomeViewState<UserService> ObservableObject を用意すると、Usecase層のモックが非常にやりやすくてすごく良かったのですが、 例えばViewのスナップショットテストを実現したいと思ったときに、SwiftUIのViewやUIViewControllerでこのObservableObjectのUserServiceをモックしようとすると、ViewのPropertyは具象のUserServiceに依存してしまっていて、モックが困難に感じます。 現状Propertyに型パラメータをProtocolのまま持たせることはできないと思っているのですが、Swift@5.7のanyなどが入るとこういった問題を解決できたりするのでしょうか? (edited)
僕は次の二つの方法のいずれかを用いています(スナップショットテストはしていませんが、 Xcode Preview のためにやっています)。 ① 一つは、 @StateObject を保持する View とレイアウトする View を分離し、前者が後者を利用する形にすることです。 // state 保持用の View とレイアウト用の View を分離する例 struct FooStateView: View { @StateObject private var state: FooViewState<FooService> ... var body: some View { FooView(bar: state.bar, baz: $state.baz) } } struct FooView: View { let bar: Int @Binding var baz: String ... } こうしておけば state とは独立に FooView の任意の状態を簡単に実現できるので、スナップショットテストやプレビューのためのモックが必要なくなります。↑は SwiftUI の例ですが、UIKit の場合でも、外から渡された状態を再現するだけの VC と、 state を保持して状態を伝えるだけの VC とに分離することで、同様のことが実現可能です。 ② もう一つは、公開する state のプロパティをすべて @Publsihed public var にしてしまうことです。そうすれば、スナップショットテストやプレビューの際に、任意の状態の state を簡単に作れます。 UserService 等にはダミーのテスト用実装を渡して何の仕事もさせないようにします。なお、 computed property の場合は setter を作れないので、↓のように republish する必要が生じます。 // computed property の代わりに republish が必要になる例 // Before @MainActor public final class FooViewState<FooService: FooServiceProtocol>: ObserbalbleObject { @Published public var baz: String = "" public var isQux: Bool { baz.isEmpty } ... } // After @MainActor public final class FooViewState<FooService: FooServiceProtocol>: ObserbalbleObject { @Published public var baz: String = "" @Published public var isQux: Bool = false ... init(...) { ... $baz.map(\.isEmpty).assign(to: &$isQux) // republish } ... } ①②とも一長一短で、①は実装が冗長になりがちです。②は外から触っていいものといけないもの(本来 private(set) であるべきもの)の区別が付きづらいです。テスト以外のケースで、本来メソッドを通して状態を変更しなければならないプロパティを setter で変更してしまうと、 state の状態を壊してしまう可能性があります。 @testable import を前提に、 internal(set) にするのも良いかもしれません。
🙇‍♂️ 1