เคล็ดลับ SwiftUI ที่เรียบง่ายแต่ทรงพลัง

เพื่อการเริ่มต้นการทดสอบ SwiftUI ที่ดีโดยใช้ ViewInspector framework คุณสามารถอ่าน "this" หรือ "this" ได้

หนึ่งในรายการที่อธิบายไว้ในบทช่วยสอนแรก (และบนหน้า 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) }
    }
}

โดยที่การตรวจสอบคือ:

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 ได้รับคุณสมบัติการตรวจสอบใหม่และ onReceive ของเนื้อหาได้รับคำสั่งให้เรียกใช้วิธีการเยี่ยมชมของคุณสมบัติการตรวจสอบเมื่อมีการเผยแพร่ข้อมูลไปยังผู้เผยแพร่ที่สังเกตเห็น

วิธีนี้ช่วยแก้ปัญหาได้ และกรณีทดสอบการทำงานอาจมีลักษณะดังนี้:

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

อย่างไรก็ตาม การเพิ่ม 'คุณสมบัติ' การทดสอบลงในโค้ดที่ใช้งานจริงของคุณดูน่าเกลียด มันสามารถทำให้มุมมองที่ซับซ้อนซับซ้อนยิ่งขึ้น และมีโค้ดซ้ำซ้อนจำนวนมากที่ต้องคัดลอก/วางในแต่ละมุมมองที่คุณต้องการทดสอบ

ดังนั้นฉันจึงพยายามค้นหาสิ่งที่สะอาดกว่า ซึ่งจะเพิ่มค่าใช้จ่ายเล็กน้อยให้กับโค้ดการทดสอบ แต่ปล่อยให้การใช้งานมุมมองจริงไม่ถูกแตะต้อง

ก่อนอื่น ฉันใช้งานมุมมอง wrapper แบบธรรมดา ซึ่งสามารถเพิ่มฟังก์ชันการตรวจสอบที่จำเป็นสำหรับ ViewInspector:

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{}

ด้วยมุมมองแบบ wrapper นี้ การทดสอบมุมมองจึงเป็นไปได้ โดยใช้การใช้งานดั้งเดิมของ 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 ง่ายขึ้นเล็กน้อย