XML schema validation
There are several possibilities to describe the structure of XML documents. The two most popular ways are DTD (Document type definition) and XSD (XML Schema definition). Once a XML document has decided to be classified using a schema definition the structure of the document has to fit the predefined rules inside the schema definition. XML document instances are valid only in case they meet all these structure rules defined in the schema definition. Currently Citrus can validate XML documents using the schema languages DTD and XSD.
XSD schema repositories
Citrus tries to validate all incoming XML messages against a schema definition in order to ensure that all rules are fulfilled. As a consequence the message receiving actions in Citrus do have to know the XML schema definition (*.xsd) file resources that belong to our project. Therefore Citrus introduces a central schema repository component which holds all available XML schema files for a project.
<citrus:schema-repository id="schemaRepository">
<citrus:schemas>
<citrus:schema id="travelAgencySchema"
location="classpath:citrus/flightbooking/TravelAgencySchema.xsd"/>
<citrus:schema id="royalArilineSchema"
location="classpath:citrus/flightbooking/RoyalAirlineSchema.xsd"/>
<citrus:reference schema="smartArilineSchema"/>
</citrus:schemas>
</citrus:schema-repository>
<citrus:schema id="smartArilineSchema"
location="classpath:citrus/flightbooking/SmartAirlineSchema.xsd"/>
As you can see the schema repository is a simple XML component defined inside the Spring application context. The repository can hold nested schema definitions defined by some identifier and a file location for the xsd schema file. Schema definitions can also be referenced by its identifier for usage in several schema repository instances.
By convention the default schema repository component is defined in the Citrus Spring application context with the id schemaRepository . Spring application context is then able to inject the schema repository into all message receiving test actions at runtime. The receiving test action consolidates the repository for a matching schema definition file in order to validate the incoming XML document structure.
The connection between incoming XML messages and xsd schema files in the repository is done by a mapping strategy which we will discuss later in this chapter. By default Citrus picks the right schema based on the target namespace that is defined inside the schema definition. The target namespace of the schema definition has to match the namespace of the root element in the received XML message. With this mapping strategy you will not have to wire XML messages and schema files manually all is done automatically by the Citrus schema repository at runtime. All you need to do is to register all available schema definition files regardless of which target namespace or nature inside the Citrus schema repository.
Important XMl schema validation is mandatory in Citrus. This means that Citrus always tries to find a matching schema definition inside the schema repository in order to perform syntax validation on incoming schema qualified XML messages. A classified XML message is defined by its namespace definitions. Consequently you will get validation errors in case no matching schema definition file is found inside the schema repository. So if you explicitly do not want to validate the XML schema for some reason you have to disable the validation explicitly in your test with schema-validation="false" .
<receive endpoint="httpMessageEndpoint">
<message schema-validation="false">
<validate>
<xpath expression="//ns1:TestMessage/ns1:MessageHeader/ns1:MessageId"
value="${messageId}"/>
<xpath expression="//ns1:TestMessage/ns1:MessageHeader/ns1:CorrelationId"
value="${correlationId}"/>
<namespace prefix="ns1" value="http://citrus.com/namespace"/>
</validate>
</message>
<header>
<element name="Operation" value="sayHello"/>
<element name="MessageId" value="${messageId}"/>
</header>
</receive>
This mandatory schema validation might sound annoying to you but in our opinion it is very important to validate the structure of the received XML messages, so disabling the schema validation should not be the standard for all tests. Disabling automatic schema validation should only apply to very special situations. So please try to put all available schema definitions to the schema repository and you will be fine.
WSDL schemas
In SOAP WebServices world the WSDL (WebService Schema Definition Language) defines the structure an nature of the XML messages exchanged across the interface. Often the WSDL files do hold the XML schema definitions as nested elements. In Citrus you can directly set the WSDL file as location of a schema definition like this:
<citrus:schema id="arilineWsdl"
location="classpath:citrus/flightbooking/AirlineSchema.wsdl"/>
Citrus is able to find the nested schema definitions inside the WSDL file in order to build a valid schema file for the schema repository. So incoming XML messages that refer to the WSDL file can be validated for syntax rules.
Schema location patterns
Setting all schemas one by one in a schema repository can be verbose and uncomfortable, especially when dealing with lots of xsd and wsdl files. The schema repository also supports location pattern expressions. See this example to see how it works:
<citrus:schema-repository id="schemaRepository">
<citrus:locations>
<citrus:location
path="classpath:citrus/flightbooking/*.xsd"/>
</citrus:locations>
</citrus:schema-repository>
The schema repository searches for all files matching the resource path location pattern and adds them as schema instances to the repository. Of course this also works with WSDL files.
Schema collections
Sometimes multiple a schema definition is separated into multiple files. This is a problem for the Citrus schema repository as the schema mapping strategy then is not able to pick the right file for validation, in particular when working with target namespace values as key for the schema mapping strategy. As a solution for this problem you have to put all schemas with the same target namespace value into a schema collection.
<citrus:schema-collection id="flightbookingSchemaCollection">
<citrus:schemas>
<citrus:schema location="classpath:citrus/flightbooking/BaseTypes.xsd"/>
<citrus:schema location="classpath:citrus/flightbooking/AirlineSchema.xsd"/>
</citrus:schemas>
</citrus:schema-collection>
Both schema definitions BaseTypes.xsd and AirlineSchema.xsd share the same target namespace and therefore need to be combined in schema collection component. The schema collection can be referenced in any schema repository as normal schema definition.
<citrus:schema-repository id="schemaRepository">
<citrus:schemas>
<citrus:reference schema="flightbookingSchemaCollection"/>
</citrus:schemas>
</citrus:schema-repository>
Schema mapping strategy
The schema repository in Citrus holds one to many schema definition files and dynamically picks up the right one according to the validated message payload. The repository needs to have some strategy for deciding which schema definition to choose. See the following schema mapping strategies and decide which of them is suitable for you.
Target Namespace Mapping Strategy
This is the default schema mapping strategy. Schema definitions usually define some target namespace which is valid for all elements and types inside the schema file. The target namespace is also used as root namespace in XML message payloads. According to this information Citrus can pick up the right schema definition file in the schema repository. You can set the schema mapping strategy as property in the configuration files:
<citrus:schema-repository id="schemaRepository"
schema-mapping-strategy="schemaMappingStrategy">
<citrus:schemas>
<citrus:schema id="helloSchema"
location="classpath:citrus/samples/sayHello.xsd"/>
</citrus:schemas>
</citrus:schema-repository>
<bean id="schemaMappingStrategy"
class="com.consol.citrus.xml.schema.TargetNamespaceSchemaMappingStrategy"/>
The sayHello.xsd schema file defines a target namespace (http://consol.de/schemas/sayHello.xsd):
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://consol.de/schemas/sayHello.xsd"
targetNamespace="http://consol.de/schemas/sayHello.xsd"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
</xs:schema>
Incoming request messages should also have the target namespace set in the root element and this is how Citrus matches the right schema file in the repository.
<HelloRequest xmlns="http://consol.de/schemas/sayHello.xsd">
<MessageId>123456789</MessageId>
<CorrelationId>1000</CorrelationId>
<User>Christoph</User>
<Text>Hello Citrus</Text>
</HelloRequest>
Root QName Mapping Strategy
The next possibility for mapping incoming request messages to a schema definition is via the XML root element QName. Each XML message payload starts with a root element that usually declares the type of a XML message. According to this root element you can set up mappings in the schema repository.
<citrus:schema-repository id="schemaRepository"
schema-mapping-strategy="schemaMappingStrategy">
<citrus:schemas>
<citrus:reference schema="helloSchema"/>
<citrus:reference schema="goodbyeSchema"/>
</citrus:schemas>
</citrus:schema-repository>
<bean id="schemaMappingStrategy"
class="com.consol.citrus.xml.schema.RootQNameSchemaMappingStrategy">
<property name="mappings">
<map>
<entry key="HelloRequest" value="helloSchema"/>
<entry key="GoodbyeRequest" value="goodbyeSchema"/>
</map>
</property>
</bean>
<citrus:schema id="helloSchema"
location="classpath:citrus/samples/sayHello.xsd"/>
<citrus:schema id="goodbyeSchema"
location="classpath:citrus/samples/sayGoodbye.xsd"/>
The listing above defines two root qname mappings - one for HelloRequest and one for GoodbyeRequest message types. An incoming message of type
Schema mapping strategy chain
Let's discuss the possibility to combine several schema mapping strategies in a logical chain. You can define more than one mapping strategy that are evaluated in sequence. The first strategy to find a proper schema definition file in the repository wins.
<citrus:schema-repository id="schemaRepository"
schema-mapping-strategy="schemaMappingStrategy">
<citrus:schemas>
<citrus:reference schema="helloSchema"/>
<citrus:reference schema="goodbyeSchema"/>
</citrus:schemas>
</citrus:schema-repository>
<bean id="schemaMappingStrategy"
class="com.consol.citrus.xml.schema.SchemaMappingStrategyChain">
<property name="strategies">
<list>
<bean class="com.consol.citrus.xml.schema.RootQNameSchemaMappingStrategy">
<property name="mappings">
<map>
<entry key="HelloRequest" value="helloSchema"/>
</map>
</property>
</bean>
<bean class="com.consol.citrus.xml.schema.TargetNamespaceSchemaMappingStrategy"/>
</list>
</property>
</bean>
So the schema mapping chain uses both RootQNameSchemaMappingStrategy and TargetNamespaceSchemaMappingStrategy in combination. In case the first root qname strategy fails to find a proper mapping the next target namespace strategy comes in and tries to find a proper schema.
Schema definition overruling
Now it is time to talk about schema definition settings on test action level. We have learned before that Citrus tries to automatically find a matching schema definition in some schema repository. There comes a time where you as a tester just have to pick the right schema definition by yourself. You can overrule all schema mapping strategies in Citrus by directly setting the desired schema in your receiving message action.
<receive endpoint="httpMessageEndpoint">
<message schema="helloSchema">
<validate>
<xpath expression="//ns1:TestMessage/ns1:MessageHeader/ns1:MessageId"
value="${messageId}"/>
<xpath expression="//ns1:TestMessage/ns1:MessageHeader/ns1:CorrelationId"
value="${correlationId}"/>
<namespace prefix="ns1" value="http://citrus.com/namespace"/>
</validate>
</message>
</receive>
<citrus:schema id="helloSchema"
location="classpath:citrus/samples/sayHello.xsd"/>
In the example above the tester explicitly sets a schema definition in the receive action (schema="helloSchema"). The attribute value refers to named schema bean somewhere in the applciation context. This overrules all schema mapping strategies used in the central schema repository as the given schema is directly used for validation. This feature is helpful when dealing with different schema versions at the same time where the schema repository can not help you anymore.
Another possibility would be to set a custom schema repository at this point. This means you can have more than one schema repository in your Citrus project and you pick the right one by yourself in the receive action.
<receive endpoint="httpMessageEndpoint">
<message schema-repository="mySpecialSchemaRepository">
<validate>
<xpath expression="//ns1:TestMessage/ns1:MessageHeader/ns1:MessageId"
value="${messageId}"/>
<xpath expression="//ns1:TestMessage/ns1:MessageHeader/ns1:CorrelationId"
value="${correlationId}"/>
<namespace prefix="ns1" value="http://citrus.com/namespace"/>
</validate>
</message>
</receive>
The schema-repository attribute refers to a Citrus schema repository component which is defined somewhere in the Spring application context.
Important In case you have several schema repositories in your project do always define a default repository (name="schemaRepository"). This helps Citrus to always find at least one repository to interact with.
DTD validation
XML DTD (Document type definition) is another way to validate the structure of a XML document. Many people say that DTD is deprecated and XML schema is the much more efficient way to describe the rules of a XML structure. We do not disagree with that, but we also know that legacy systems might still use DTD. So in order to avoid validation errors we have to deal with DTD validation as well.
First thing you can do about DTD validation is to specify an inline DTD in your expected message template.
<receive endpoint="httpMessageEndpoint">
<message schema-validation="false">
<data>
<![CDATA[
<!DOCTYPE root [
<!ELEMENT root (message)>
<!ELEMENT message (text)>
<!ELEMENT text (#PCDATA)>
]>
<root>
<message>
<text>Hello TestFramework!</text>
</message>
</root>
]]>
<data/>
</message>
</receive>
The system under test may also send the message with a inline DTD definition. So validation will succeed.
In most cases the DTD is referenced as external .dtd file resource. You can do this in your expected message template as well.
<receive endpoint="httpMessageEndpoint">
<message schema-validation="false">
<data>
<![CDATA[
<!DOCTYPE root SYSTEM
"com/consol/citrus/validation/example.dtd">
<root>
<message>
<text>Hello TestFramework!</text>
</message>
</root>
]]>
<data/>
</message>
</receive>