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 中文版
如何在 SwiftUI App 中開發 Live Activities
SwiftUI 框架

如何在 SwiftUI App 中開發 Live Activities

Live Activities 首次於 iOS 16 推出,是 Apple 最令人興奮的更新之一,能讓 App 與使用者在即時互動上更有連結。它不再需要使用者不斷打開 App,Live Activities 可以讓資訊直接顯示在鎖定畫面和 Dynamic Island 上。
使用 Tool Calling 強化 Foundation Models 功能
AI

使用 Tool Calling 強化 Foundation Models 功能

在前幾篇教學中,我們介紹了 Foundation Models 在 iOS 26 中的運作方式,以及如何使用這個全新框架打造具備 AI 功能的應用。我們也介紹了 @Generable 巨集,它能輕鬆地將模型回應轉換為結構化的 Swift 類型。 現在,在這個 Foundation
活用 Foundation Models 的 @Generable 與 @Guide 製作測驗 App
AI

活用 Foundation Models 的 @Generable 與 @Guide 製作測驗 App

在前一篇教學中,我們介紹了 Foundation Models 框架,並示範了如何用它來進行基本的內容生成。那個過程相當簡單——你提供一個提示詞(prompt),等幾秒鐘,就能獲得自然語言的回應。在我們的範例中,我們建立了一個簡單的問答 App,讓使用者可以提問,App 則直接顯示生成的文字。 但如果回應變得更複雜——你需要把非結構化文字轉換為結構化的物件呢? 舉例來說,
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。