How to Test SwiftUI Views Containing @State in ViewInspector | by Dragos Ioneanu | Apr, 2022

Picture by Sigmund on Unsplash

For an excellent getting began with SwiftUI testing, utilizing ViewInspector framework, you’ll be able to learn this or this.

One of many gadgets described within the first tutorial (and on the ViewInspector GitHub web page) is the utilization of @State within the view that you just wish to check.

Mainly, in case your view is one thing like this:

struct ContentView: View 
@State var numClicks:Int = 0

var physique: some View
VStack
Button("Click on me")
numClicks += 1
.id("Button1")
Textual content("(numClicks)")
.id("Text1")
.padding()



It is really not attainable to check the motion of clicking the button on the numClicks.
The suitable workaround is so as to add a little bit of code to the view, remodeling it mainly in :

struct ContentView: View 
@State var numClicks:Int = 0
inside let inspection = Inspection<Self>()

var physique: some View
VStack
Button("Click on me")
numClicks += 1
.id("Button1")
Textual content("(numClicks)")
.id("Text1")
.padding()

.onReceive(inspection.discover)
self.inspection.go to(self, $0)

the place Inspection is:

inside last class Inspection<V> 
let discover = PassthroughSubject<UInt, By no means>()
var callbacks: [UInt: (V) -> Void] = [:]
func go to(_ view: V, _ line: UInt)
if let callback = callbacks.removeValue(forKey: line)
callback(view)


extension Inspection: InspectionEmissary

As you’ll be able to see ContentView acquired a brand new Inspection property and the onReceive of its physique is instructed to run the go to technique of the Inspection property when information is revealed to the observed writer.

This solves the difficulty, and a functioning check case might appear like this:

func testContentView() throws
let sut = ContentView()
_ = sut.inspection.examine view in
let button = strive view.discover(viewWithId: “Button1”).button()
strive button.faucet()
XCTAssertEqual(strive view.actualView().numClicks, 1)
let textual content = strive view.discover(viewWithId: “Text1”).textual content()
let worth = strive textual content.string()
XCTAssertEqual(worth, “1”)

Nevertheless, it appears form of ugly so as to add testing ‘options’ to your manufacturing code. It could possibly make an advanced view much more difficult and it loads of redundant code that must be copy/pasted in every view that you just wish to check.

Subsequently, I attempted to search out one thing cleaner, that may add a little bit of overhead to the testing code, however depart the precise view implementation untouched.

First I applied a easy wrapper view, that might add the inspection performance wanted by ViewInspector:

public let TEST_WRAPPED_ID: String = “wrapped”struct TestWrapperView<Wrapped: View> : View
inside let inspection = Inspection<Self>()
var wrapped: Wrapped

init( wrapped: Wrapped )
self.wrapped = wrapped

var physique: some View
wrapped
.id(TEST_WRAPPED_ID)
.onReceive(inspection.discover)
self.inspection.go to(self, $0)


extension TestWrapperView: Inspectable

With this wrapper view, the testing of the view is feasible, utilizing the unique implementation of ContentView:

func testContentView() throws
let sut = TestWrapperView(wrapped: ContentView())
_ = sut.inspection.examine view in
let wrapped = strive view.discover(viewWithId: TEST_WRAPPED_ID)
let button = strive wrapped.discover(viewWithId: “Button1”).button()
strive button.faucet()
let numClicks = strive wrapped
.view(ContentView.self)
.actualView()
.numClicks
XCTAssertEqual(numClicks, 1)
let textual content = strive wrapped.discover(viewWithId: “Text1”).textual content()
let worth = strive textual content.string()
XCTAssertEqual(worth, “1”)

I hope you want this small trick, that makes the testing of SwiftUI views a bit simpler.

More Posts