Published on

Another Way To Test That a Thymeleaf Context is Being Populated Correctly

Authors

In one of my previous posts I discussed how to use the translator pattern to more easily test that the Thymeleaf context is being populated correctly. The reason I adopted this approach was due to it being impossible to mock the Thymeleaf Context object as the setVariable method is final along with a number of other methods in the API (which makes sense as extending the context and interfering with how setContext works could break Thymeleaf). Today I was working on some older code that used Thymeleaf and after looking more carefully at the Thymeleaf Context's API I came up with another approach.

What I prefer about this approach is that it does not result in a class explosion of translators as you can easily test it using this approach. Lets start by looking at the Thymeleaf Context class. This class extends an Abstract class called AbstractContext. The method we are interested in is the setVariable method. Ideally it would be great to be able to intercept this to test that our context is being populated correctly. But this method is final meaning that most mocking frameworks will not work as they need to be able to extend the object they are mocking. Looking at more of this class's API I found the getVariables method. This is also final but returns all the variables that have been populated in the context - this is exactly what we need! To take advantage of this we either need to constructor inject the Context into the class we are testing so that we can call context.getVariables() or we need to make the context inside the class we are testing accessible to tests using a protected or package private accessor.

Using this approach is advantageous if there are many objects that would need to be translated to Thymleaf as we do not need tonnes of translator classes just so that we can test that context is being populated correctly. The translators approach is better in the situations where we want to have common translations which can live in an interface with default methods or an abstract class and in situations where we do not want to have to expose the Context or inject it into our object. Either way both approaches work, it is just a matter of weighting the trade offs of either to decide which is best for a given situation.