Juxy is a library for unit testing XSLT stylesheets from Java. It is best suited for the projects where
both Java and XSLT are used simultaneously.
Juxy features include:
Ability to invoke individual xsl templates and pass them any parameters (you can either call or
apply templates with and without modes).
Ability to setup stylesheet parameters and global variables.
Ability to setup current node before transformation.
Integration with JUnit (Juxy provides its own TestCase class simplifying testing with JUnit,
however, JUnit is not required for writing tests, you can use any testing framework that you like).
Transparent support for document fragments returned as transformation result (result of the called
template can have more than one root node, Juxy will process such cases transparently for you).
Easy result verification with sophisticated XML assertion mechanism.
Easy validation using W3C XML Schema or a number of XPath assertions (you can use either Jaxen or JAXP XPath
engine, Juxy will detect their presence automatically).
Ability to trace execution of the sylesheets instructions.
Support for custom URIResolver.
Ant task for batch verification of XSLT stylesheets.
Table Of Contents
Examples
Please refer to javadoc for detailed API description.
You can use Juxy alone, in conjunction with JUnit, or with any other testing framework that you like.
To become familiar with Juxy, in the first example, we will show how to use Juxy without JUnit.
We will assume here, that there is a stylesheet with a template taking a list of xml tags and transforming it
to a comma separated list of strings. For example, let it be a transformation of something like:
/* First of all we must obtain a Runner instance. Runner is a class that
actually invokes transformation and returns the result. */Runner runner = RunnerFactory.newRunner();/* To setup what and how to transform we must create RunnerContext
object, which is obtained from the Runner instance. We should
provide RunnerContext with stylesheet system id (i.e. with a path
to the stylesheet we are going to test). */RunnerContext context = runner.newRunnerContext("stylesheet.xsl");/* Next we set input document into the RunnerContext. */context.setDocument("" +
"<list>" +
" <item>item 1</item>" +
" <item>item 2</item>" +
" <item>item 3</item>" +
"</list>");/* Now we are ready to start transformation. We run it by invoking
method applyTemplates() which works similar to the XSLT
instruction: <xsl:apply-templates/>.
Result of the transformation is returned as a DOM Node object.
In fact this is a DOM Document object, which in this case will
contain only one TextNode node. */Node result = runner.applyTemplates();/* To retrieve text from the returned DOM Node we can create
XPath expression. We should use XPathFactory for that. */XPathExpr xpath = XPathFactory.newXPath("text()");/* Now we are ready to evaluate the expression. */String resultText = xpath.toString(result);
Note: DOM Document in our case actually contains not well formed XML document,
which consists of one text node only. Usually it is impossible to create such a document,
however Juxy applies a simple workaround for that: internally Juxy holds transformation results
in a DocumentFragment node, but from the Runner
you will receive a proxy object, which will behave like a Document node containing all the
child nodes of the DocumentFragment node.
With JUnit it is possible to write more compact tests. For that you should extend your test cases from
JuxyTestCase class. Take a look how the same test looks with JUnit:
/* We should extend our test case from JuxyTestCase. */public class SampleTestCase extends JuxyTestCase {public void testListTransformation() {/* We should not store context in the local variable, it will be stored
automatically within the test case itself. */newContext("stylesheet.xsl");/* To obtain current RunnerContext we can call the context() method. */context().setDocument("" +
"<list>" +
" <item>item 1</item>" +
" <item>item 2</item>" +
" <item>item 3</item>" +
"</list>");/* We do not need to know about Runner here. We can simply invoke applyTemplates()
method. Runner will be instantiated automatically within JuxyTestCase. */Node result = applyTemplates();/* With JuxyTestCase verification of the result is much simpler. */xpathAssert("text()", "item 1, item 2, item 3").eval(result);
}
}
More samples are available in the Juxy samples directory.
How does it work?
Juxy works by dynamically creating XSLT stylesheet. From that stylesheet the tested stylesheet is imported via
<xsl:import/> instruction, thus all its templates and variables automatically get lower priority, so we can
easily redefine them.
Any variables and parameters specified in the RunnerContext will be placed in the generated stylesheet. If current
node was specified, then corresponding <xsl:for-each/> is added to that stylesheet.
Thus when call or apply templates method is invoked, Juxy generates new XSLT stylesheet (it will be generated
as DOM Document), places there specified variables and parameters, and inserts template matching root node ("/").
In this template the required <xsl:call-template/> or <xsl:apply-templates/> instructions are placed.
If current node was specified then <xsl:for-each/> instruction will be added as their parent. To avoid endless
loop Juxy will not insert apply-templates instruction if current node was set to "/".
Because Juxy does not use any XSLT processor specific features it should work with any XSLT processor supporting
JAXP/TRaX API. For now it is known to work with Apache Xalan and
Saxon (both Saxon 6.5.X and Saxon 8.X are supported). XSLT 2.0
templates are also supported.
Other features
Other ways to start transformation
In the examples above we invoked the transformation by calling method Runner.applyTemplates().
But it is also possible to get the results of the transformation produced by individual templates.
You can apply individual templates by specifying XPath expression and mode, or you can call
templates by their name. Refer to the Runner's Javadoc for more details.
Often template requires current node to be set up. By default, it is a root node of the input document,
but you can set your own current node using method RunnerContext.setCurrentNode(XPathExpr).
RunnerContext also allows you to setup global transformation parameters,
global variables, parameters for the template and namespaces.
Assertions
With XPath you can apply different assertions on the transformation result. However, in most cases it
is easier to compare XML fragments. You can do this with XMLComparator.assertXMLEquals(String, Node)
method, for example: XMLComparator.assertXMLEquals("<p>expected text</p>", result).
The assertXMLEquals method will compare expected document and the result node by node.
In case of any differences you will get an output similar to:
Documents differ, expected document:
<p>expected text</p>
Actual document:
<p>actual text</p>
If you are using JuxyTestCase, this method is available directly from this class.
JuxyTestCase also provides a couple of methods allowing to normalize text, i.e.
to collapse spaces and remove trailing spaces. They are useful if spaces between words are insignificant for you.
Tracing
In some situations it is hard to understand why transformation does not work as you expected.
For that purposes Juxy provides you with an ability to trace execution of the XSLT instructions.
You can enable or disable tracing by calling methods JuxyTestCase.enableTracing and
JuxyTestCase.disableTracing correspondingly (or you can call these methods from
the Runner instance). The tracing output looks like:
Tracing of the stylesheet file://some/path/stylesheet.xsl started
2: <xsl:template match="/">
3: <xsl:for-each select="//*">
4: <xsl:value-of select=".">
3: <xsl:for-each select="//*">
4: <xsl:value-of select=".">
In the tracing output you can see both, the line numbers and corresponding XSLT instructions.
There are limitations, and the most essential is that for now only instructions within templates and
template instructions itself are traced. Global variables, parameters and keys are not traced.
Another limitation is that currently tracing is supported for Saxon and Xalan only. Xalan XSLTC is not supported.
XSLT Verification
Starting from version 0.7 Juxy package contains Ant task which performs syntax verification of a number of
stylesheets at once. This task tries to compile every stylesheet (by creating JAXP Transformer object) and
reports all errors and warnings.
Juxy verifier task checks parent stylesheets only, i.e. the stylesheets which are not included or imported
from any other stylesheets, because included stylesheets can use global variables and parameters
defined in the parent and might not be compiled without parent. Juxy will automatically filter out all
imported and included stylesheets from the specified file set.
Comma- or space-separated list of files (may be specified using wildcard patterns) that must be included.
No
excludes
Comma- or space-separated list of files (may be specified using wildcard patterns) that must be excluded.
No
failonerror
Whether to fail on first error or not. True by default.
No
The following nested elements are allowed:
standard Ant <fileset/>
<catalog/> this element if appeared turns on XML catalogs resolution.
The <catalog/> element has the following attributes:
Attribute
Description
Required
catalogfiles
Comma separated paths to the catalog files.
Yes
Logging
Juxy produces some log messages which might be helpful for discovering problems.
For logging purposes Juxy uses Jakarta commons-logging API, so commons-logging jars
must be in the classpath.
XML Format
Juxy (in CVS) supports an XML format for tests. For example:
<test name="MoreThanOneElementInTheList_ApplyTemplates">
<document select="/list"><list><item>first item</item><item>second item</item>
<item>third item</item></list></document>
<apply-templates select="/list"/>
<assert-equals>
<expected>first item, second item, third item</expected>
</assert-equals>
</test>
Requirements
Juxy requires Java 1.4 and will not work with earlier versions. A number of required libraries depends on a version of Java you are using.
For Java 1.4 the following libraries are required:
A couple of words about XML parser, XPath and XSLT engine.
Juxy was tested with Xerces 2 XML parser which is included now into the Java 1.5.
Juxy does not depend on any Xerces specific functionality, so it should work with other
JAXP compliant XML parsers, which support SAX 2 Core, DOM Level 2 Core and Traversal and Range.
For XPath expressions Juxy supports both Jaxen and JAXP XPath (which appeared in Java 1.5).
Juxy will automatically detect presence of the supported engine, so if you are going to run tests
under the Java 1.5 Jaxen is no longer required.
As for XSLT engine, Juxy should work with any JAXP compliant XSLT processor which supports XSLT 1.0 specification
and JAXP DOMResult. Both Xalan and Saxon
(6.5.X and 8.X versions) were tested and work fine with Juxy. Since version 0.7.2 Juxy also supports XSLT processor bundled with Java 1.5,
however I would not recommend you to use it because of several bugs in this XSLT processor.
The following libraries are required for Ant verifier task:
Recently Juxy project was added into the demo installation of TeamCity continuous integration server.
Here you can see status of Juxy project builds provided by TeamCity:
Whom to contact?
My name is Pavel Sher, and I am the author of this project and its sole developer.
I will be glad to hear your suggestions and opinions about Juxy. Please send them
to the users mailing list or to my email: pavelsher[ at ]tigris.org.
If you found a bug, you can report it into the issues mailing list or to me directly.