【问题标题】:Mock a SwiftUI view from another module从另一个模块模拟 SwiftUI 视图
【发布时间】:2022-08-17 02:37:01
【问题描述】:

我正在尝试测试一个 SwiftUI 视图,该视图在其主体中具有来自另一个模块的子视图:

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\")
    }
}

我的问题是:如果我想测试 ProfileView 的生命周期而不构建实际的 AbondProfile 视图,有没有办法模拟它?如果它是一个正常的方法,我会注入一个依赖对象,但我不知道如何将它转换为结构初始化程序。

Abond 是一个 Swift 包,所以我不能修改 AbondProfile。而且我希望能够在尽可能少地更改我的视图代码的情况下对此进行测试。我正在使用 XCTest。

标签: ios swiftui xctest


【解决方案1】:

正如 David Wheeler 所说,“计算机科学中的任何问题都可以通过另一个层次的间接性来解决。”

在这种情况下,一种解决方案是通过泛型类型参数间接引用AbondProfile。我们给ProfileView添加一个类型参数来代替直接使用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
}

我们不必更改ProfileView 的当前用途 如果我们提供一个使用AbondProfile 的默认初始化器:

extension ProfileView {
    init() where Content == AbondProfile {
        self.init { AbondProfile(onSuccess: $0) }
    }
}

struct ProductionView: View {
    var body: some View {
        ProfileView() // This uses AbondProfile.
    }
}

在测试中,我们可以提供一个模拟视图:

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")
        }
    }
}

【讨论】:

    【解决方案2】:

    我接受了另一个答案,因为它是一个更合适的解决方案,但我发现它实际上可以重新定义测试文件中的结构:

    import XCTest
    import Abond
    import SwiftUI
    
    // Mock for Abond.AbondProfile
    public struct AbondProfile: View {
        static var viewDidAppearCallback: (() -> Void)?
        static var submit: (() -> Void)?
    
        public init(onSuccess: (() -> Void)? = nil) {
            AbondProfile.submit = onSuccess
        }
    
        public var body: some View {
            Text(Abond.accessToken)
                .onAppear {
                    AbondProfile.viewDidAppearCallback?()
                }
        }
    }
    
    class ProfileViewTests: BaseViewControllerTests {
        private var viewController: UIViewController?
    
        func testSucesss() {
            let viewDidAppearExpectation = XCTestExpectation(description: "View did appear")
            AbondProfile.viewDidAppearCallback = { viewDidAppearExpectation.fulfill() }
            MockApi.mockRequest(ProfileApi.getProfileAccessToken, response: ProfileAccessToken(accessToken:"accessToken_123"))
    
            initialize(viewController: UIHostingController(rootView: ProfileView()))
    
            wait(for: [viewDidAppearExpectation], timeout: 10)
            XCTAssertEqual(Abond.accessToken, "accessToken_123")
    
            AbondProfile.submit!()
            // etc.
        }
    }
    

    我知道静态变量会使测试变得脆弱——但除此之外,我很想知道是否有其他理由不这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-21
      • 1970-01-01
      • 1970-01-01
      • 2020-12-09
      • 2016-08-28
      • 2011-08-09
      • 1970-01-01
      相关资源
      最近更新 更多