10 March 2023
By: Tomas Malmsten

Frontend testing strategies - The GUI

One question that often pops up when talking about TDD is how to test frontend code. Some say just don’t. I don't think that's particularly helpful though. So this article will look at perhaps the most commonly raised objection to TDD in frontend code. How to deal with the GUI.

But first I want to dispel a common misconception about TDD. TDD is not about providing a complete set of regression tests for the application. It's actually not that much about testing, in terms of what testers do, at all. In TDD we use tests to drive the code and design. The actual testing, such as testers care about, is a side effect of this process. Just as regression tests or regression checking is a side effect. This also means that we are allowed to take some shortcuts in terms of coverage to make the code we want to drive with tests testable.

I should probably also clarify that frontend here is not just JavaScript web frontends. It's anything that interacts with people through a screen. So the patterns are just as useful for mobile and desktop as for web applications.

How to decouple the GUI

GUIs are often only testable using some kind of robot. And robots aren't that good at detecting when a button has moved a couple of pixels to much to the side (and similar things). Also, it takes ages to start robots. This makes the tests dead slow and rather flaky. And such tests makes TDD impossible.

To handle this we need teas apart the stuff that renders stuff on the screen from any decision-making needed to render this code. To our help there are several patterns. I will look closer at two commonly occurring ones. Passive View and Presentation Model.

Before we dig in to the patterns I want to clarify an important term used in both. Model. the model in these patterns represents the domain model. In other words both the data and the behaviour of the bussines logic in the application. The model, both behaviour and data, usually has a very different structure to what the view needs. The patterns below introduces ways in which we can handle the interaction between the model and the view. With any translations (mappings) needed. In a way that all interaction and state changes can be put under test without the need to test the actual view.

Passive View

This is of the variation of the pattern Model View Presenter. It's also referred to as Humble View.

There are three actors in this pattern. The model, the view and the presenter. As mentioned above the model is the domain model. The view is the graphical widgets used to render stuff on the screen. The presenter is the object we use to control the interaction between the two.

In Passive View architectures we make the view really dumb. It only renders and forwards events triggered by the users interaction with it. Nothing else.

The presenter is responsible for updating the view with data, reading data from the view and make any translations between data representations in the view and in the model. We usually do this by creating an interface between the presenter and the view. That the view will implement. This creates a seam where we can inject a test double. Completely decoupling any tests from the actual view.

The downside is that we introduce a new element, the interface. It may not seem much but given most GUI applications has quite a few different views and each view has its own presenter this quickly becomes a lot of extra elements.

Is the price of extra interfaces worth the benefit of complete decoupling? Well, that depends on your specific use case, your platform or framework, your tooling and so on. As always, it's a tradeoff.

You can find more detail on this pattern from Martin Fowler and also detailed on MSDN and from Eli Weinstock-Herman.

Presentation Model

This pattern is often also referred to as the Model View ViewModel or MVVM.

Again, we have three actors in the architecture. The view, the presentation model and the model. Again the model is the domain model. If we go with the alternative name MVVM we are talking about the model, the view and the view-model. But I'll stick with the first versions of the names.

The main difference between a Passive View and a Presentation Model is how much responsibility we put into the view. Here we allow the view to handle the interactions with the presentation model. Whereas in the Passive View it is the presenter that does this. That aside there is little difference.

However, that difference is actually quite big. That means that there is more logic we can't test. Since the data binding and updating can't be put under test by testing interactions between objects.

On the other hand we don't have to create interfaces for all the views so the number of elements in our code is reduced, and therefore complexity.

Is it OK to give up testability to lessen complexity. Again, it's a tradeoff. Several frameworks have really good support for MVVM architecture. Others don't. So if we can trust our framework or tooling perhaps it is worth it. If framework support or tooling is lacking perhaps we don't want to give up control and prefer to go with the Passive View pattern.

For more detail head over to Martin Fowler or to MSDN.

Some history

You may notice that I don't reference the MVC pattern at all above. This is because it's not the most helpful pattern to us when we want to TDD our code. It's also the oldest among the MV* patterns. Both patterns above are improvements on MVC to address some of the shortcomings of it. Even so, both patterns above have also been around for quite some time. If you are interested in the history of them you will find Derek Greer's account interesting. And as always, Martin Fowler's story on the topic is also a really good read.

For some more patterns relating to testing I also recomend the book xUnit Patterns. And relating to Humble view above especially the Humble Object pattern.

Tags: TDD Testing Frontend