本篇原文(標題: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 編輯小姐。