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 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。