Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing SwiftUI body views and properties

I'm in the process of working out how to unit test SwiftUI view code.

I have the following definition:

struct ContentView : View {
    var body: some View {

        Text("Hello World")
            .font(.title)
            .fontWeight(.bold) 
    }
}

and I can test it like this:

    func testBody() {
        let cv = ContentView()

        let body = cv.body

        XCTAssertNotNil(body)

        guard let text = body as? Text else { XCTFail(); return }

        XCTAssertEqual(Text("Hello World").font(.title).fontWeight(.bold), text)
    }

however, as soon as I want to test text alignment, I run into issues:

production code:

struct ContentView : View {
    var body: some View {

        Text("Hello World")
            .font(.title)
            .fontWeight(.bold)
        .multilineTextAlignment(.leading)
    }
}

and test code:

    func testBody() {
        let cv = ContentView()

        let body = cv.body

        XCTAssertNotNil(body)

        guard let text = body as? Text else { XCTFail(); return }

        // COMPILER ERROR ON NEXT LINE
        XCTAssertEqual(Text("Hello World").font(.title).fontWeight(.bold).multilineTextAlignment(.leading), text)  
    }

...then I get the following compiler error:

Cannot convert value of type 'Text' to expected argument type '_ModifiedContent<Text, _EnvironmentKeyWritingModifier<HAlignment>>'

How do I test the alignment of the Text struct?

like image 438
Andrew Ebling Avatar asked Jun 06 '19 14:06

Andrew Ebling


1 Answers

There are two extensions for .font(_:), namely:

extension View {
    /// Sets the default font for text in this view.
    ///
    /// - Parameter font: The default font to use in this view.
    /// - Returns: A view with the default font set to the value you supply.
    public func font(_ font: Font?) -> Self.Modified<_EnvironmentKeyWritingModifier<Font?>>
}

and

extension Text {
    /// Sets the font to use when displaying this text.
    ///
    /// - Parameter font: The font to use when displaying this text.
    /// - Returns: Text that uses the font you specify.
    public func font(_ font: Font?) -> Text
}

When you execute the method .font on a Text struct, you will get back a new Text, with the applied font because it calls the overload of font(_:) (creating an overloaded method with a more specific return type is always ok). When you call the font method on, for example, a Button, the return type is:

ModifiedContent<Button<Text>, _EnvironmentKeyWritingModifier<Font?>>

Well, that's no normal Button anymore, but a wrapped pretty complex type, because it doesn't have it's own overload like Text has, so it calls the 'normal' method.

What happend when you called multilineTextAlignment on your Text instance?

extension View {

    /// Sets the alignment of multiline text in this view.
    ///
    /// - Parameter alignment: A value you use to align lines of text to the
    ///   left, right, or center.
    /// - Returns: A view that aligns the lines of multiline `Text` instances
    ///   it contains.
    public func multilineTextAlignment(_ alignment: HAlignment) -> Self.Modified<_EnvironmentKeyWritingModifier<HAlignment>>
}

There is no overload of Text, like there is for the font method. It means the return type is different than a new Text instance. Now we are stuck with a complex time.

There is, fortunately, a content property for that complex type. Your test will compile when doing this:

XCTAssertEqual(Text("Hello World").font(.title).fontWeight(.bold).multilineTextAlignment(.leading).content, text)

Noticed the content property :)?

When running the test, the body variable isn't of type Text, like you would expect, but has another very complex type, that's why your test will fail. To pass your test, do the following:

func testBody() {
    let cv = ContentView()

    let body = cv.body

    XCTAssertNotNil(body)

    // Yuk!! Ugly cast, but don't know how to fix it since yeah, it is the type of the body...
    guard let text = body as? (_ModifiedContent<Text, _EnvironmentKeyWritingModifier<HAlignment>>) else { XCTFail(); return }

    // No compile error :) and a passing test!
    XCTAssertEqual(Text("Hello World").font(.title).fontWeight(.bold).multilineTextAlignment(.leading).content, text.content)
}
like image 139
J. Doe Avatar answered Sep 16 '22 22:09

J. Doe