# thymeleaf-testing **Repository Path**: mirrors_thymeleaf/thymeleaf-testing ## Basic Information - **Project Name**: thymeleaf-testing - **Description**: Thymeleaf testing infrastructure - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: 3.1-master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-08-18 - **Last Updated**: 2025-12-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README Thymeleaf Testing Library ========================= ------------------------------------------------------------- **[Please make sure to select the branch corresponding to the version of Thymeleaf you are using]** Status ------ This is an auxiliary testing library, not directly a part of the Thymeleaf core but part of the project, developed and supported by the [Thymeleaf Team](http://www.thymeleaf.org/team.html). Current versions: * **3.0.4.RELEASE** - for Thymeleaf 3.0 (requires 3.0.4+) * **2.1.4.RELEASE** - for Thymeleaf 2.1 (requires 2.1.2+) License ------- This software is licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). Requirements (3.0.x) -------------------- * Thymeleaf **3.0.4+** * Attoparser **2.0.3.RELEASE+** Maven info ---------- * groupId: `org.thymeleaf` * artifactId: `thymeleaf-testing` Features -------- * Works as an independent library, **callable from multiple testing frameworks** like e.g. JUnit. * **Tests only the view layer**: template processing and its result. * Includes **benchmarking** facilities: all tests are timed, times are aggregated. * Highly extensible and configurable * Versatile testing artifacts: test sequences, iteration, concurrent execution... * Based on interfaces. Out-of-the-box *standard test resolution* allows: * Easy specification of tests as simple text files, sequences as folders. * Advanced test configuration. * Test inheritance. * *Lenient* result matching, ignoring excess unneeded whitespace etc. * **Spring Framework** and **Spring Security** integration. ------------------------------------------------------------------------------ ## Usage ## The testing framework can be used with just two lines of code: ```java final TestExecutor executor = new TestExecutor(); executor.execute("classpath:test"); ``` Note how here we are only specifying the name of the *testable* to be resolved: `"classpath:test"`, which is a folder called `test` in classpath. (more on testables later). But anyway this is only two lines, and therefore we are accepting some defaults, namely: * Dialects. By default only the *Standard Dialect* will be enabled. * Resolvers. By default the *standard test resolution* mechanism will be used (more on it later). * Reporters. By default a console reporter will be used. Let's see the whole `TestExecutor` configuration process: ```java final List dialects = ... final ITestableResolver resolver = ... final ITestReporter reporter = ... final TestExecutor executor = new TestExecutor(); executor.setDialects(dialects); executor.setTestableResolver(resolver); executor.setReporter(reporter); executor.execute("classpath:test"); ``` The meaning and working of the *dialects* property is pretty obvious and straightforward. As for the *resolvers* and *reporters*, we will see more on them in next sections. ## API ## The Thymeleaf testing framework is based on interfaces, and therefore defines an API that specifies the diverse artifacts involved in the testing process: Test artifacts at the `org.thymeleaf.testing.templateengine.testable` package: | Interface | Description |Base Impl| Default Impl| |----------------------------|-------------|------------------|-------------| |`ITestable` | Implemented by objects designed for being *tested*, be it a simple test or an aggregating structure of any kind. Every other interface in this package extends this one. | `AbstractTestable` | | |`ITest` | Represents tests, the basic unit for testing and simplest `ITestable` implementation. Tests are in charge not only of containing test data, but also of evaluating/checking test results. | `AbstractTest` | `Test` | |`ITestSequence` | Represents sequences of tests, test structures or any combination of theses (sequences of objects implementing the `ITestable` interface). | | `TestSequence` | |`ITestIterator` | Represents objects capable of iterating (executing a number of times) other test structures. | | `TestIterator` | |`ITestParallelizer` | Represents objects capable of using several threads for executing the same test structure in each thread, concurrently. | | `TestParallelizer` | |`ITestResult` | Represents the results of executing a test and evaluating its results. | | `TestResult` | Interfaces at the `org.thymeleaf.testing.templateengine.resolver` package: | Interface | Description | |----------------------------|-------------| |`ITestableResolver` | Implemented by objects in charge of *resolving testables*, this is, of creating the `ITestable` objects and structures that will be executed. A *standard test resolution* implementation is provided out of the box that builds these testable structures from text files and their containing folders in disk. | Interfaces at the `org.thymeleaf.testing.templateengine.resource` package: | Interface | Description | |----------------------------|-------------| |`ITestResource` | Implemented by objects representing test resources like testable artifact locations, test input text, test output text, etc. | |`ITestResourceResolver` | Implemented by objects in charge of resolving test resources, used by *testable resolvers*. For example, a *testable resolver* (`ITestableResolver`) will use an `ITestResourceResolver` implementation for converting the resource name `"classpath:test"` into a valid `ITestResource` object. | Interfaces at the `org.thymeleaf.testing.templateengine.report` package: | Interface | Description | |----------------------------|-------------| |`ITestReporter` | Implemented by objects in charge of reporting the results of executing tests, sequences, etc. along with their associated execution times. | Interfaces at the `org.thymeleaf.testing.templateengine.context` package: | Interface | Description | |----------------------------|-------------| |`ITestContext` | Implemented by objects representing the context (variables, locale, etc.) to be used for executing tests. | |`IProcessingContextBuilder` | Implemented by objects in charge of creating the `IContext` to be used for test excecution from the resolved `ITestContext`. | Interfaces at the `org.thymeleaf.testing.templateengine.messages` package: | Interface | Description | |----------------------------|-------------| |`ITestMessages` | Implemented by objects representing the set of externalized (or *internationalized*) messages to be used for executing tests. | In addition to these interfaces, this testing API also includes the `org.thymeleaf.testing.templateengine.engine.TestExecutor` class, in charge of executing the test structures. ## Test Reporters ## Test Reporters implement the `org.thymeleaf.testing.templateengine.report.ITestReporter` interface and allow the engine to report when a test has been executed, the execution result, and also the execution time (aggregated in the case of a structure). Out of the box, thymeleaf-testing provides two implementations at the `org.thymeleaf.testing.templateengine.report` package: * `AbstractTextualTestReporter`, an abstract text-based implementation suitable for easily creating reporters that output text. * `ConsoleTestReporter`, extending the former, which writes these *text report items* to the console. Console reporting looks like this: ``` [2013-04-19 02:23:29][KE1OMC][main] [sequence:start][maintests] [2013-04-19 02:23:29][KE1OMC][main] [test:end][text.test][175729614][OK] Test executed OK. Time: 175729614ns (175ms). [2013-04-19 02:23:29][KE1OMC][main] [test:end][utext.test][3365839][OK] Test executed OK. Time: 3365839ns (3ms). [2013-04-19 02:23:29][KE1OMC][main] [sequence:end][maintests][2][2][179095453] Tests OK: 2 of 2. Sequence executed in 179095453ns (179ms) ``` It's easy to create new reporters that could write test results to different formats like CSV, Excel, etc. or even write results to a database. ### Integrating with JUnit ### The easiest way to integrate with JUnit is to use JUnit tests to launch sets of tests, using afterwards JUnit's assertion mechanism to check results: ```java final TestExecutor executor = new TestExecutor(); executor.execute("mytestset"); Assert.assertTrue(executor.getReporter().isAllOK()); ``` ...or the equivalent, more convenient: ```java final TestExecutor executor = new TestExecutor(); executor.execute("mytestset"); Assert.assertTrue(executor.isAllOK()); ``` Note that this *test set* can be a single test: ```java final TestExecutor executor = new TestExecutor(); executor.execute("mytestset/onetest.thtest"); Assert.assertTrue(executor.isAllOK()); ``` You can reuse your test executor by resetting its reporter after each execution: ```java final TestExecutor executor = new TestExecutor(); executor.execute("mytestset/onetest.thtest"); Assert.assertTrue(executor.isAllOK()); executor.getReporter().reset(); ``` ...or the equivalent, more convenient: ```java final TestExecutor executor = new TestExecutor(); executor.execute("mytestset/onetest.thtest"); Assert.assertTrue(executor.isAllOK()); executor.reset(); ``` So you can use your executor for executing several *test sets* (or single tests) in the same JUnit test method: ```java final TestExecutor executor = new TestExecutor(); ... executor.execute("mytestset/onetest.thtest"); Assert.assertTrue(executor.isAllOK()); executor.reset(); ... executor.execute("mytestset/twotest.thtest"); Assert.assertTrue(executor.isAllOK()); executor.reset(); ... executor.execute("anothertestset"); Assert.assertTrue(executor.isAllOK()); executor.reset(); ``` Note that each execution of `executor.execute(...)` will create its own `TemplateEngine` instance and resolvers, an so no templates will be cached from one execution to the next. ## Testable Resolvers ## Standard test resolution is provided by means of an implementation of the `org.thymeleaf.testing.templateengine.resolver.ITestableResolver` interface called `org.thymeleaf.testing.templateengine.resolver.StandardTestableResolver`. ### The Standard Resolution mechanism ### The standard test resolution mechanism works like this: * Tests are specified in text files, following a specific directive-based format. * Folders can be used for grouping tests into sequences, iterators or parallelizers. * Test ordering and sequencing can be configured through the use of *index files*. Let's see each topic separately. #### Test file format #### A test file is a text file with a name ending in `.thtest` It can look like this: ``` %TEMPLATE_MODE HTML5 # ------------ separator comment ----------- %CONTEXT onevar = 'Goodbye,' # ------------------------------------------ %MESSAGES one.msg = Crisis # ------------------------------------------ %INPUT Hello, World! # ------------------------------------------ %OUTPUT Goodbye, Crisis ``` We can see there that tests are configured by means of *directives*, and that these directives are specified in the form of `%DIRECTIVENAME`. The available directives are: *Test Configuration:* | Name | Description | |----------------------------|-------------| |`%NAME` | Name of the test, in order to make it identifiable in reports/logs. This is *optional*. If not specified, the file name will be used as test name. | |`%CONTEXT` | Context variables to be made available to the tested template. These variables should be specified in the form of *properties* (same syntax as Java `.properties` files), and **property values are parsed and executed as OGNL expressions**. Specifying context variables is *optional* and they can be inherited from parent tests.
You can read more about the specification of context variables below.| |`%MESSAGES` | Default (no locale-specific) externalized/internationalized messages to be made available to the tested template. These variables should be specified in the form of *properties* (same syntax as Java `.properties` files). Specifying messages is *optional* and they can be inherited from parent tests. | |`%MESSAGES[es]` | Same as `%MESSAGES`, but specifying messages for a specific locale: `es`, `en_US`, `gl_ES`, etc. | *Test input:* | Name | Description | |----------------------------|-------------| |`%INPUT` | Test input, in the form of an HTML template or fragment. A resource name can also be specified between parenthesis, like `%INPUT (file:/home/user/myproject/src/main/resources/templates/mytemplate.html)`. This parameter is *required*. | |`%INPUT[qualif]` | Additional inputs can be specified by adding a *qualifier* to its name. These additional inputs can be used as external template fragments in `th:include="qualif"`, `th:substituteby="qualif :: frag"`, etc. | |`%FRAGMENT` | Fragment specification (in the same format as used in `th:include` attributes) to be applied on the test input before processing. *Optional*. | |`%TEMPLATE_MODE` | Template mode to be used: `HTML5`, `XHTML`, etc. | |`%TEMPLATE_MODE[qualif]` | Additional template modes can be specified for additional inputs (matching those specified with `%INPUT[qualif]`. | |`%CACHE` | Whether template cache should be `on` or `off`. If cache is *on*, the input for this test will be parsed only the first time it is processed.| *Test expected output:* | Name | Description | |----------------------------|-------------| |`%OUTPUT` | Test output to be expected, if we expect template execution to finish successfully. Either this or the `%EXCEPTION` directive must be specified. A resource name can also be specified between parenthesis, like `%OUTPUT (file:/home/user/myproject/src/test/resources/results/mytemplate-res.html)` | |`%EXACT_MATCH` | Whether *exact matching* should be used. By default, *lenient matching* is used, which means excess whitespace (*ignorable whitespace*) will not be taken into account for matching test results. Setting this flag to `true` will perform exact *character-by-character* matching. | |`%EXCEPTION` | Exception to be expected, if we expect template execution to raise an exception. Either this or the `%OUTPUT` directive must be specified. | |`%EXCEPTION_MESSAGE_PATTERN`| Pattern (in `java.util.regex.Pattern` syntax) expected to match the message of the exception raised during template execution. This directive needs the `%EXCEPTION` directive to be specified too. | *Inheritance:* | Name | Description | |----------------------------|-------------| |`%EXTENDS` | Test specification (in a format understandable by the implementations of `ITestableResolver` and `ITestResourceResolver` being used) from which this test must inherit all its directives, overriding only those that are explicitly specified in the current test along with this `%EXTENDS` directive.
Examples: `%EXTENDS classpath:test/bases/base1.test` | Also, any line starting by `#` in a test file will be considered **a comment** and simply ignored. ##### More on resource resolution ##### The standard mechanism for resource resolution allows you to: * Specify resources in classpath: `classpath:tests/mytest.thtest` * Specify resources in the filesystem: `file:/home/myuser/tests/mytest.thtest` or `file:C\Users\myser\tests\mytest.thtest`. Also, in some scenarios (like the `%EXTENDS` directive in test files) resource resolution can be relative to the current resource: ``` %EXTENDS ../../base-tests/base.thtest ``` Note that, when using resource resolution in an `%INPUT` or `%OUTPUT` directive, resource names must be specified between parenthesis: ``` %INPUT (file:/home/user/myproject/src/main/resources/templates/mytemplate.html) ``` ##### More on context specification ##### As already said, context is specified like: ```properties %CONTEXT onevar = 'Goodbye!' twovar = 'Hello!' ``` And those literals are specified between commas because all context values are in fact **OGNL** expressions, so we could in fact use previous variables in new ones: ```properties %CONTEXT onevar = 'Hello, ' twovar = onevar + 'World!' ``` We can also create objects, and set its properties: ```properties %CONTEXT user = new com.myapp.User() user.firstName = 'John' user.lastName = 'Apricot' ``` Also maps: ```properties %CONTEXT user = #{ 'firstName' : 'John', 'lastName' : 'Apricot' } ``` We can set request parameters (multivalued), request attributes, session attributes and servlet context attributes using the `param`, `request`, `session` and `application` prefixes: ```properties %CONTEXT session.userLogin = 'japricot' param.selection = 'admin' param.selection = 'manager' ``` Utility objects like `#strings`, `#dates`, `#lists`, etc. can be used: ```properties %CONTEXT request.timestamp = #calendars.createNow() ``` Finally, note that **context variables are inherited** when a test is set as an extension of another one by means of the `%EXTENDS` directive. ##### Selecting locale for execution ##### The locale to be used for execution can be selected by giving value to a context variable called `locale`: ```properties %CONTEXT locale = 'gl_ES' ``` #### Test folder format #### The folders that contain tests will themselves be resolved as test structures, and their names will be used to indicate the existence of *iterations* or *parallelizers*. Imagine we have this folder structure at our classpath: /tests | +->/warmup | | | +->testw1.thtest | | | +->testw2.thtest | +->/expressions-iter-10 | +->expression1.thtest | +->/expression-stress-parallel-3 | +->expression21.thtest | +->expression22.thtest When we ask the standard test resolver to resolve that folder called `tests` (with `"classpath:tests"`), it will create the following *testable artifact* structure: * A *Test Sequence* called `tests`, containing: * A *Test Sequence* called `warmup` containing: * Two tests: `testw1.thtest` and `testw2.thtest`. * A *Test Iterator* called `expressions`, iterated 10 times, containing: * One test: `expression1.thtest`. * A *Test Parallelizer* called `expression-stress`, executed by 3 concurrent threads, containing: * Two tests: `expression21.thtest` and `expression22.thtest`. So, as can be extracted from the example above: * In general, folders will be resolved as *test sequence* * Folders with a name ending in `-iter-X` will be resolved as a *test iterator* iterating `X` times. * Folders with a name ending in `-parallel-X` will be resolved as a *test parallelizer* executing its contents with `X` concurrent threads. #### Test index files #### Index files are files with a name ending with `.thindex`. They allow developers to specify which tests and in which order they want to be executed, this is, in practice, create *a test sequence*. They also allow the specification of iteration or parallelization without having to change the name of a folder. Example contents for a `sample.thindex` file: ``` exp.thtest include.thtest text.thtest [iter-20] test2 [parallel-3] ``` According to the above index, the `text.thtest` file will be executed in third position, 20 times. And the `test2` folder will be considered a parallelizer, just as if it was called `test2-parallel-3` instead. Also, note that when a folder includes a special file named `folder.thindex`, this will be considered to specify the sequence in which the folder files have to be executed, even if this `folder.thindex` file isn't explicitly called. ### Extending the standard test resolution mechanism ### The standard resolution mechanism can be extended in several ways, by means of a series of *setter* methods in the `StandardTestableResolver` class which allow developers to configure how each step of test resolution is performed: | Setter | Description | |----------------------------|-------------| |`setTestReader(IStandardTestReader)` | Specifies the implementation that will be in charge of reading test files and return its raw data. Default implementation is the `StandardTestReader` class. | |`setTestEvaluator(IStandardTestEvaluator)` | Specifies the implementation that will be in charge of evaluating the *raw data* returned by the test reader into the different values that will be used for building the test object. Default implementation is `StandardTestEvaluator`. | |`setTestBuilder(IStandardTestBuilder)` | Specifies the implementation that will be in charge of actually building test objects from the values evaluated by the *test evaluator* in the previous step. Default implementation is `StandardTestBuilder`. | |`setTestResourceResolver(ITestResourceResolver)` | Specifies the implementation that will be in charge of actually resolving resources (including testable artifacts, `%EXTENDS` directives, `.thindex` entries, etc.). | ## Spring integration ## In order to execute thymeleaf tests using the **SpringStandard** dialect in its entirety, we need to activate certain Spring mechanisms that support some Spring-integrated processors included in this dialect (like `th:field`). Initialization would look like this: ```java final List dialects = new ArrayList(); dialects.add(new SpringStandardDialect()); final SpringWebProcessingContextBuilder springPCBuilder = new SpringWebProcessingContextBuilder(); final TestExecutor executor = new TestExecutor(); executor.setProcessingContextBuilder(springPCBuilder); executor.setDialects(dialects); executor.execute("tests"); ``` Pay special attention to that instantiation of `org.thymeleaf.testing.templateengine.context.web.SpringWebProcessContextBuilder`. That is the class which will activate the needed Spring mechanisms. This Spring-based context builder will try to initialize a Spring application context from an `applicationContext.xml` file present in the classpath. The name of this file can be overridden or even set to `null` if we do not wish to initialize any beans: ```java final IProcessingContextBuilder springPCBuilder = new SpringWebProcessingContextBuilder(); springPCBuilder.setApplicationContextConfigLocation("classpath:springConfig/spring.xml"); ``` ### Model binding ### If we want to test a page including bound model objects like, for example, a *form-backing bean* (or *command*), we just have to declare and initialize it: ```properties %CONTEXT user = new my.company.User() user.name = 'John' user.surname = 'Apricot' ``` #### Initializing bindings: property editors #### The `SpringWebProcessContextBuilder` class can be overridden if we need to initialize bindings in our own way, for example for registering *property editors*. Let's see an example of this extension: ```java public class STSMWebProcessingContextBuilder extends SpringWebProcessingContextBuilder { public STSMWebProcessingContextBuilder() { super(); } protected void initBinder( final String bindingVariableName, final Object bindingObject, final ITest test, final DataBinder dataBinder, final Locale locale, final Map variables) { final String dateformat = test.getMessages().computeMessage(locale, "date.format", null); final SimpleDateFormat sdf = new SimpleDateFormat(dateformat); sdf.setLenient(false); dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, false)); } } ``` ### Spring Security ### The testing library can also be integrated with Spring Security. This allows the use of the `thymeleaf-extras-springsecurity3` dialect and testing template rendering in different authentication/authorization scenarios. Example of usage: ```java final SpringSecurityWebProcessingContextBuilder processingContextBuilder = new SpringSecurityWebProcessingContextBuilder(); processingContextBuilder.setApplicationContextConfigLocation( "classpath:springsecurity/applicationContext-security.xml"); final TestExecutor executor = new TestExecutor(); executor.setProcessingContextBuilder(processingContextBuilder); executor.setDialects( Arrays.asList(new IDialect[] { new SpringStandardDialect(), new SpringSecurityDialect()})); executor.execute("springsecurity"); ``` By default, authentication will be specified in a user/password basis, by means of the `j_username` and `j_password`. ```properties %CONTEXT j_username = 'ted' j_password = 'demo' ```