Snapshot testing Dynamic Type

Kane
Kin + Carta Created
5 min readMar 23, 2018

--

I really, really love snapshot testing.

In case you don’t know what snapshot testing is, it’s a really fast way to not only test that your view lays out correctly without needing to run the app, but also to safeguard against accidental breakages during refactoring so that newcomers onto the codebase can feel confident they’ve not broken anything.

My colleague Neil goes into more detail about where snapshot testing fits in, and what it is, so I won’t cover it too greatly here. In a nutshell, snapshot tests compare a realtime snapshot of the view (after it’s been laid out) and compares it to an existing recorded snapshot (the “test oracle”), failing the test if anything has changed.

In this post I want to show you how it’s possible to harness the power of snapshot testing to go so far as to test your view works with different text sizes when your app supports Dynamic Type, using the amazing UIFontMetrics available since iOS 11.

I’ll be unashamedly using my own snapshot testing tool that I’ve written called PixelTest, which is a Swift-first solution handling much of the layout code for you, freeing you up to write lots of awesome tests in as little code as possible.

All of this code is available on GitHub if you want to test it out yourself.

Setup

Let’s begin!

In the example project we’ve got a fairly simple view, which has a title label, detail label and message label.

How the view looks in Interface Builder

After the view is created, we can populate it with some data by calling configure(with viewModel: ExampleViewModel) which we can snapshot test very simply:

In the above example, for simplicity’s sake I’m assuming snapshot tests have already been recorded, which is why mode is set to .test. The existing recorded snapshot tests look like this:

`test_simpleData()` recorded snapshot
`test_longData()` recorded snapshot

Testing Dynamic Type

It’s common to test how a view behaves with short and long data as above, but we’re going to go a step further, and hook into UIFontMetrics to be able to also change the font size at the same time as configuring the view with a view model.

UIFontMetrics, available since iOS 11, is an amazing new addition to UIKit that does automatic font scaling for you, and even works with custom fonts. After digging about a bit I noticed that you can ask UIFontMetrics for a scaled font compatible with a specific trait collection. I then found out you can also create UITraitCollections configured with a preferred content size!

With that knowledge, we can update our view to configure the fonts with a trait collection, defaulting to the system-wide trait collection set by the user:

Looking at the code above, we’ve added an argument to configure(with: and updateFonts(with: to take a trait collection, the former with a default of the main screen’s current trait collection (which will represent the current setting set by the user system-wide). We then pass that trait collection into the scaledFont function on UIFontMetrics, which gives us back a font at the correct scale.

In regular usage throughout the app, we’ll only need to provide a view model, but in tests we can override the trait collection to force UIFontMetrics to provide a scaled font for that specific trait collection.

We can keep our old snapshot tests, which still pass (meaning I’m confident my changes to the above code haven’t broken anything), and write some new tests which will test the two extreme options of dynamic type; .extraSmall and .accessibilityExtraExtraExtraLarge:

Recording the snapshots for the above test cases results in the following snapshots:

`test_extraSmallDynamicType()`
`test_accessibilityExtraExtraExtraLargeDynamicType()`

Hmm, so this quickly shows us that our layout needs some improvement for the largest dynamic text size! We can now raise this to the team and decide what’s best to do in this case.

The power of snapshot testing means I’ve tested the layout, and found issues, without wasting any time building and running the app. This is especially powerful when you’re working on views that are embedded into a controller somewhere deep in the navigation stack of your app, which otherwise would mean you have to tediously keep navigating to it and changing the data to see how it behaves.

What’s really amazing here, is that throughout this entire post I’ve never needed to run the app, nor embed ExampleView into any view controller.

Wrapping up

Hopefully this post helps highlight just how powerful snapshot testing can be, and will help you create amazing apps that support Dynamic Type by making it super quick to test how your layout works with different font sizes.

I’ve purposely kept the examples here simple, but the complete code is available to look through if you want to better understand how this works together. Because the example project has real snapshot tests in, you’re free to change whatever you want and see how the view behaves, and whether that breaks the layout.

As this post also shamelessly plugs PixelTest, I should also point out that PixelTest is still not yet at v1, and I very much welcome feedback to improve the tool, as well as contributions!

--

--

Kane
Kin + Carta Created

Senior multiplatform engineer. If you're not a fan of equality we won't get on.