How to test your Twig templates and frontend (in PHP)

Philipp Scheit
4 min readJul 12, 2023

(This article does apply to other template languages as well, even for other programming languages!)

You learned to write unit tests for the backend, but is your frontend tested? Every detail?
Why not? Cause it’s hard, right? I am doing this for years and there are few approaches to do this, but nothing the thing I want to show you today in a small example.

Solution 1: Use selenium and very slow (browser) acceptance tests

Most use cypress these days. Most often you don’t need the full browser (if you dont use much javascript). You can then render your templates and do assertions on the output. These tests are called functional tests in symfony. But all of this doesn’t make it any better: it’s still only testing the html output. Either you need to fiddle with the PHP DOM (symfony functional tests) or you are in css selectors (cyprss) or even xpath selector (cypress, behat) hell!

The (better) alternative: Change your production code to make your frontend testable

Let’s say you are just starting, or you can still move things around. You have domain objects that are representing your state (in your app). Ideally, these are not Plain Old Doctrine ORM Entities and you converted them to real models (that do not have an explicit connection to the persistence layer).

Most projects I see, just take these models (or even worse: the entities) and put them into Twig templates. Twig has a really big feature set and you can do EVERYTHING in Twig.. hell even write PHP. Those who remember how PHP used in templates looked like in the year 1999 still remember: this is a pretty bad idea.

Just because Twig is pretty powerful it doesn’t mean you should use its power

The opposite is even true: Make your twig templates as DUMB as you possibly can! Remove all the logic there. Why? Cause it makes it testable.

Let’s have a look at a simple example. There is a subscription you want to display, and it has a lot of states, that are shown at the top of a dashboard. Subscription might be paid, in trial, canceled, etc.
How would the Twig template look like, if you write it without thinking about it?

Twig template before

Something like this maybe? So you do a big if-elseif-else switch in the Twig template and hope for the best. Each branch displays the state as string and an icon.

But how would you test that? …

Right.. you have no choice but to write an acceptance test! Setup a subscription in that state (uh oh.. and persist it do the db!), then call it with a browser and assert the output html.
That alone sounds like a lot of work. And then we can easily assert the state string of the subscription, but what about the icon? Parse the SVG and see what it represents. Regexp matches? … hopefully not!

Sounds like a nightmare.

And it is! I worked in a team, where our acceptance tests had to be run in a Pipeline with 8 runners in parallel and it took over half an hour to complete. Sounds familiar? Stop doing that.

Let’s think about the template first. What’s the problem here? It’s not even readable, isn’t it? And when you look closely, there are bugs. (e.g. data-test=”subscription-state” is missing on some strings, classes are inconsistent, etc.)

What about this?

Twig template after refactoring

This looks easier to read, right? The trick is to introduce more state in newly created UI models. Map the domain models you have to another set of (really simple) models. Most of the time this looks ridiculously dumb since you are only copying properties from the model to the UI-DTO.

the DTO we use in the template

But thank me later: this makes your template 100% testable in PHPUnit!

this was the test before we added the state
when we removed the not needed other flags

As you can see I use two twig functions getIcon and getBackgroundColor in the template. In the end this can be a glorified if-elseif-else for the icon or with just an array mapping background colors to state labels. The point is: this is much simpler, easy to test code as well. Cause twig functions might be a little bit more handy to test, then the twig template itself. Yes, you have to assert a bit html (if you want 100% coverage), but this isn’t as bad (and slow) as it is with functional / browser tests.

This is much simpler, is it? So always think about the objects you are passing into your templates. Add a whole new namespace of UI data transfer objects, that will make your life so much easier.

Hands up who has ever refactored an entity and forgot the twig templates cause it’s not found by phpstorm usages?
*whole room and me raises hand*

Give it a try and let me know if you have any questions!

--

--