I'm trying to test a SwiftUI view that has a subview from another module in its body:
import SwiftUI
import Abond
struct ProfileView: PresentableView, LoadedView {
@State var isLoading = true
public var body: some View {
Load(self) {
AbondProfile(onSuccess: self.onSubmitSuccess)
}
}
func load() -> Binding<Bool> {
ProfileApi.getProfileAccessToken() { result in
switch result {
case .success(let response):
Abond.accessToken = response.accessToken
case .failure(let error):
print("error getting token")
}
isLoading = false
}
return $isLoading
}
func onSubmitSuccess() {
print("success")
}
}
My question is: if I want to test the lifecycle of ProfileView
without the actual AbondProfile
view being built, is there a way to mock that? If it were a normal method I would inject a dependency object, but I don't know how to translate that to a struct initializer.
Abond is a Swift Package, so I can't modify AbondProfile. And I'd prefer to be able to test this with as little change to my view code as possible. I'm using XCTest.
As David Wheeler said, “Any problem in computer science can be solved with another level of indirection.”
In this case, one solution is to refer to AbondProfile
indirectly, through a generic type parameter. We add a type parameter to ProfileView
to replace the direct use of AbondProfile
:
struct ProfileView<Content: View>: PresentableView, LoadedView {
@State var isLoading = true
@ViewBuilder var content: (_ onSuccess: @escaping () -> Void) -> Content
public var body: some View {
Load(self) {
content(onSubmitSuccess)
}
}
blah blah blah
}
We don't have to change current uses of ProfileView
if we provide a default initializer that uses AbondProfile
:
extension ProfileView {
init() where Content == AbondProfile {
self.init { AbondProfile(onSuccess: $0) }
}
}
struct ProductionView: View {
var body: some View {
ProfileView() // This uses AbondProfile.
}
}
And in a test, we can provide a mock view:
struct TestView: View {
var body: some View {
ProfileView { onSuccess in
Text("a travesty of a mockery of a sham of a mockery of a travesty of two mockeries of a sham")
}
}
}