How an XSpec test is put together
An XSpec file is XML, so its structure should look familiar to an XSLT developer. The main elements are x:description for the tested module, x:scenario for a test case and x:expect for the expected result.
Use x:call to call a function or a named template. Use x:context to supply XML nodes for matching template rules. The XSpec namespace is:
http://www.jenitennison.com/xslt/xspec
Call functions and templates
The following example produces a citation. One test calls the named template directly. The other supplies a book element and processes it in citation mode:
examples/03-templates/citation.xspec
<?xml version="1.0" encoding="UTF-8"?>
<x:description xmlns:x="http://www.jenitennison.com/xslt/xspec"
stylesheet="citation.xsl">
<x:scenario label="Call the citation template by name">
<x:call template="format-citation">
<x:param name="title" select="'Practical XML'"/>
<x:param name="year" select="2026"/>
</x:call>
<x:expect label="Create a citation"><cite>Practical XML (2026)</cite></x:expect>
</x:scenario>
<x:scenario label="Select the citation mode">
<x:context mode="citation" select="book">
<book year="2026"><title>Practical XML</title></book>
</x:context>
<x:expect label="Use the matching template"><cite>Practical XML (2026)</cite></x:expect>
</x:scenario>
</x:description>
Here is the corresponding stylesheet:
examples/03-templates/citation.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:template name="format-citation">
<xsl:param name="title" as="xs:string"/>
<xsl:param name="year" as="xs:integer"/>
<cite><xsl:value-of select="concat($title, ' (', $year, ')')"/></cite>
</xsl:template>
<xsl:template match="book" mode="citation">
<xsl:call-template name="format-citation">
<xsl:with-param name="title" select="string(title)"/>
<xsl:with-param name="year" select="xs:integer(@year)"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
The tests look similar, but check different things. The first checks the named template. The second also checks that the matching rule is selected and passes the data on correctly.
Named-template parameters are identified by name; function parameters are supplied in order. In a context test, pay attention to the mode. The wrong mode may run a built-in rule instead of the rule you intended to test.
A number is not the same as text
These forms look almost identical but supply different values:
<x:param select="27"/>
<x:param select="'27'"/>
<x:param>27</x:param>
select="27" supplies a number. select="'27'" supplies a string. Content between the start and end tags constructs XML content containing a text node. A function may convert some of these values, but a test should not depend on that happening by accident. Use select for numbers and strings when the type matters.
For larger inputs, href loads a file. An additional select can choose part of it:
<x:param href="catalog.xml" select="/catalog"/>
<x:expect href="published.xml" select="/catalog/book"/>
Here, the function receives the catalog element. The result is expected to be the book elements from another file. The paths are relative to the respective test file. Therefore, when copying an example, the folder structure must also be preserved.
Compare a result or check its properties
Sometimes the exact output is known: AA, or a particular XML element. Sometimes a property matters: is the string non-empty? Is it uppercase? XPath expressions in test can check those properties.
The following shared scenarios check a non-empty uppercase string:
examples/08-organization/shared.xspec
<x:scenario xmlns:x="http://www.jenitennison.com/xslt/xspec"
xmlns:xs="http://www.w3.org/2001/XMLSchema" label="A non-empty uppercase string" shared="yes">
<x:expect label="Return a non-empty string" result-type="xs:string"
test="string-length($x:result) gt 0"/>
<x:expect label="Use uppercase characters" result-type="xs:string"
test="$x:result eq upper-case($x:result)"/>
</x:scenario>
result-type checks the expected type first. If a function suddenly returns something other than a string, the test should identify that instead of failing obscurely inside a string-specific expression.
An XML comparison checks trees, not the bytes in a saved file. Attribute order and file indentation are different concerns from a missing element. Spaces inside text can still matter, though. XSpec has specific rules for whitespace-only nodes in descriptions. Separate input and expected files are useful when spacing matters, because indenting the test description should not accidentally change the data.
Compare enough to cover the requirement. A check that merely finds some output is too weak when the complete structure needs to be right.

