SwiftUI 框架

在 SwiftUI 中 利用 ViewInspector 框架測試包含 @State 的視圖

這個小技巧可以簡化測試 SwiftUI 視圖的步驟,讓你了解如何利用 ViewInspector 框架進行 SwiftUI 測試。
在 SwiftUI 中 利用 ViewInspector 框架測試包含 @State 的視圖
在 SwiftUI 中 利用 ViewInspector 框架測試包含 @State 的視圖
In: SwiftUI 框架
本篇原文(標題:How to Test SwiftUI Views Containing @State in ViewInspector)刊登於作者 Medium,由 Dragos Ioneanu 所著,並授權翻譯及轉載。

你可以先參閱這篇這篇文章,來了解利用 ViewInspector 框架進行 SwiftUI 測試。

在第一篇教學文章及 ViewInspector 的 GitHub 頁面中,就有提到如何在想要測試的視圖中使用 @State

假設我們的視圖是這樣的:

struct ContentView: View {
   @State var numClicks:Int = 0
 
   var body: some View {
      VStack{
         Button("Click me"){
            numClicks += 1
         }.id("Button1")
         Text("\(numClicks)")
           .id("Text1")
           .padding()
   
      }
   }
}

我們其實是無法在 numClicks 測試單擊按鈕的動作。

要解決這個問題,我們就要在視圖中添加一些程式碼,將其轉換為:

struct ContentView: View {
   @State var numClicks:Int = 0
   internal let inspection = Inspection<Self>()
 
   var body: some View {
      VStack{
         Button("Click me"){
            numClicks += 1
         }.id("Button1")
         Text("\(numClicks)")
           .id("Text1")
           .padding()
   
       }.onReceive(inspection.notice) { 
            self.inspection.visit(self, $0) }
    }
}

而 Inspection 就在這裡:

internal final class Inspection<V> {
   let notice = PassthroughSubject<UInt, Never>()
   var callbacks: [UInt: (V) -> Void] = [:]
   func visit(_ view: V, _ line: UInt) {
      if let callback = callbacks.removeValue(forKey: line) {
         callback(view)
      }
   }
}
extension Inspection: InspectionEmissary {}

從上面的程式碼可見,ContentView 有一個新的 Inspection 屬性 (property),當數據 publish 到 noticed publisher 時,就會指示 body 的 onReceive 執行 Inspection 屬性的 visit 方法。

這樣問題就解決了,我們可以這樣編寫一個測試範例:

func testContentView() throws{
   let sut = ContentView()
    _ = sut.inspection.inspect { view in
       let button = try view.find(viewWithId: “Button1”).button()
       try button.tap()
       XCTAssertEqual(try view.actualView().numClicks, 1)
       let text = try view.find(viewWithId: “Text1”).text()
       let value = try text.string()
       XCTAssertEqual(value, “1”)
    }
 }

不過,把測試「功能」添加到 production code 不太好看,可能會讓複雜的視圖變得更複雜,而且會需要在每個要測試的視圖中,複製/貼上大量多餘的程式碼。

因此,我嘗試尋找更簡潔的方法。這個方法可能會讓 testing code 多了幾層,但就不用變更實際的視圖實作。

首先,讓我們實作一個簡單的包裝器 (wrapper) 視圖,這樣可以添加 ViewInspector 需要的 Inspection 功能:

public let TEST_WRAPPED_ID: String = "wrapped"

struct TestWrapperView<Wrapped: View> : View{
   internal let inspection = Inspection<Self>()
   var wrapped: Wrapped
 
   init( wrapped: Wrapped ){
       self.wrapped = wrapped
   }
 
   var body: some View {
      wrapped
        .id(TEST_WRAPPED_ID)
        .onReceive(inspection.notice) { 
           self.inspection.visit(self, $0) 
        }
    }
}

extension TestWrapperView: Inspectable{}

有了這個包裝器視圖,我們就可以使用原本實作的 ContentView 來測試視圖:

func testContentView() throws{
   let sut = TestWrapperView(wrapped: ContentView())
   _ = sut.inspection.inspect { view in
       let wrapped = try view.find(viewWithId: TEST_WRAPPED_ID)
       let button = try wrapped.find(viewWithId: “Button1”).button()
       try button.tap()
       let numClicks = try wrapped
                         .view(ContentView.self)
                         .actualView()
                         .numClicks
       XCTAssertEqual(numClicks, 1)
       let text = try wrapped.find(viewWithId: “Text1”).text()
       let value = try text.string()
       XCTAssertEqual(value, “1”)
    }
 }

這個小技巧可以簡化測試 SwiftUI 視圖的步驟,希望大家喜歡這篇文章。

本篇原文(標題:How to Test SwiftUI Views Containing @State in ViewInspector)刊登於作者 Medium,由 Dragos Ioneanu 所著,並授權翻譯及轉載。
譯者簡介:Kelly Chan-AppCoda 編輯小姐。
作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
更多來自 AppCoda 中文版
如何使用 Swift 整合 Google Gemini AI
SwiftUI 框架

如何使用 Swift 整合 Google Gemini AI

在即將到來的 WWDC,Apple 預計將會發佈一個本地端的大型語言模型 (LLM)。 接下來的 iOS SDK 版本將讓開發者更輕易地整合 AI 功能至他們的應用程式中。然而,當我們正在等待 Apple 推出自家的生成 AI 模型時,其他公司(如 OpenAI
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。