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?
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With