An open API service indexing awesome lists of open source software.

https://github.com/thymeleaf/thymeleaf-testing

Thymeleaf testing infrastructure
https://github.com/thymeleaf/thymeleaf-testing

Last synced: 6 months ago
JSON representation

Thymeleaf testing infrastructure

Awesome Lists containing this project

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'
```