Upload
others
View
6
Download
0
Embed Size (px)
Citation preview
0
1
2
3
4
5
6
7
8
9
10
10.1
10.2
10.3
10.4
10.5
10.6
10.7
11
12
13
13.1
13.2
13.3
13.4
13.5
13.6
TableofContentsIntroduction
Preface
Changesnew
Introduction
Setup
Testcase
Testvariables
Run
Configuration
Endpoints
Validation
Xml
Schema
Json
Xhtml
Plaintext
Binary
Gzip
Xpath
JsonPath
Actions
Send
Receive
Database
Sleep
Java
Timeout
CitrusReferenceGuide
2
13.7
13.8
13.9
13.10
13.11
13.12
13.13
13.14
13.15
13.16
13.17
13.18
13.19
13.20
13.21
13.22
13.23
13.24
13.25
14
15
16
16.1
16.2
16.3
16.4
16.5
16.6
16.7
16.8
Echo
Stoptime
Createvariables
Trace
Transform
Groovy
Fail
Input
Load
Wait
PurgeJMSqueues
Purgechannels
Purgeendpoints
Assert
Catch
Antrun
Manageserver
Stoptimer
Genericaction
Templates
Testbehaviors
Containers
Sequential
Conditional
Parallel
Iterate
Repeat
RepeatOnError
Timer
Custom
CitrusReferenceGuide
3
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Finally
Jms
Http
HttpWebsockets
Soap
Ftp
Messagechannel
File
Camel
Vertx
Arquillian
Docker
Kubernetes
Ssh
Rmi
Jmx
Cucumber
Zookeeper
Restdocs
Selenium
Endpointcomponent
Endpointadapter
Functions
ValidationMatchers
Datadictionary
Testactors
Testsuite
Metainfo
Messagetracing
CitrusReferenceGuide
4
47
48
48.1
49
49.1
49.2
49.3
49.4
49.5
49.6
49.7
49.8
49.9
49.10
Reporting
Samples
FlightBookingSample
Appendix
Changes2.6
Changes2.5
Changes2.4
Changes2.3
Changes2.2
Changes2.1
Changes2.0
Changes1.4
Changes1.3
Changes1.2
CitrusReferenceGuide
5
CitrusFramework-ReferenceDocumentation
AuthorsChristophDeppisch,MartinMaher
Version2.7.1
Copyright©2017ConSolSoftwareGmbH
www.citrusframework.org
CitrusReferenceGuide
6Introduction
Preface
Integrationtestingcanbeveryhard,especiallywhenthereisnosufficienttoolsupport.UnittestingisflavoredwithfantastictoolsandAPIslikeJUnit,TestNG,EasyMock,Mockitoandsoon.Thesetoolssupportyouinwritingautomatedtests.Atesterwhoisinchargeofintegrationtestingmaylackoftoolsupportforautomatedtestingespeciallywhenitcomestosimulatemessaginginterfaces.
Inatypicalenterpriseapplicationscenariothetestteamhastodealwithdifferentmessaginginterfacesandvarioustransportprotocols.Withoutsufficienttoolsupporttheautomatedintegrationtestingofmessage-basedinteractionsbetweeninterfacepartnersisexhaustingandsometimesbarelypossible.
Thetesterisforcedtosimulateseveralinterfacepartnersinanend-to-endintegrationtest.Thefirstthingthatcomestoourmindismanualtesting.Nodoubtmanualtestingisfast.Inlongtermperspectivemanualtestingistimeconsumingandcausessevereproblemsregardingmaintainabilityastheyareerrorproneandnotrepeatable.
TheCitrusframeworkgivesacompletetestautomationtoolforintegrationtestingofenterpriseapplications.Youcantestyourmessageinterfacestootherapplicationsasclientandserver.EverytimeacodechangeappliesallautomatedCitrustestsensurethestabilityofinterfacesandmessagecommunication.
RegressiontestingandcontinuousintegrationisveryeasyasCitrusfitsintoyourbuildlifecylceasusualJavaunittest.YoucanuseCitruswithJUnitorTestNGinordertointegratewithyourapplicationbuild.
WithpowerfulvalidationcapabilitiesforvariousmessageformatslikeXML,CSVorJSONCitrusisdesignedtoprovidefullyautomatedintegrationtestsforend-to-endusecases.Citruseffectivelycomposescomplexmessagingusecaseswithresponsegeneration,errorsimulation,databaseinteractionandmore.
ThisdocumentationprovidesareferenceguidetoallfeaturesoftheCitrustestframework.Itgivesadetailedpictureofeffectiveintegrationtestingwithautomatedintegrationtestenvironments.Sincethisdocumentisconsideredtobeunderconstruction,pleasedonothesitatetogiveanycommentsorrequeststoususingouruserorsupportmailinglists.
CitrusReferenceGuide
7Preface
What'snewinCitrus2.7?!Citrus2.7isusingJava8!TheCitrussourcesarecompiledwithJava8whichmeansthatfromnowonyouneedatleastJava8runtimetoworkwithCitrus.WiththisJava8baseCitrusisproudtowelcometwonewcrewmembersforsupportingSeleniumandKubernetesintests.Notenoughwehavethefollowingfeaturesincludedinthebox.
Java8
CitrusisnowusingJava8.ThisismainlybecauseweneedtomoveoninusinglatestversionsofSpringFramework,ApacheCamelandsoon.IfyouarestillstuckonJava7youcannotupdateto2.7astheCitrussourcesarecompiledwithJava8.PleasecontactusincaseyoureallycannotupdatetoJava8inyourproject.WecanthinkofaminorbugfixversionwithCitrus2.6basethatstillsupportsJava7runtime.OnthebrightsidewecannowusethefullpowerofLambdaexpressionsandotherJava8featuresinCitruscodebase.
Kubernetessupport
CitrusisnowabletointeractwithKubernetesremoteAPIinordertomanagepods,servicesandotherresourcesontheKubernetesplatform.TheKubernetesclientisbasedontheFabric8JavaclientthatinteractswiththeKubernetesAPIviaRESTservices.SoyoucanaccessKubernetesresourceswithinCitrusinordertochangeorvalidatetheresourcestateforcontainerizedtesting.Thisisveryusefulwhendealingwithcontainerapplicationenvironmentsaspartoftheintegrationtests.PleasestaytunedforblogpostsandtutorialsamplesonhowCitruscanhelpyoutestMicroserviceswithDockerandKubernetes.Thebasicusageisdescribedinsectionkubernetes.
Seleniumsupport
UserinterfaceandbrowsertestinghasnotbeenafocuswithinCitrusintegrationtestinguntilnowthatwecanintegratewiththefamousSeleniumUItestinglibrary.Thankstothegreatcontributionsmadebythecommunity-especiallybyvdsrd@github-wecanuseSeleniumbasedactionsandfeaturesdirectlyinaCitrustestcase.TheCitrusJavaandXMLDSLbothprovidecomfortableaccesstotheSeleniumAPIinordertosimulate
CitrusReferenceGuide
8Changesnew
userinteractionwithinabrowser.ThemixofuserbasedactionsandCitrusmessagingtransportsimulationgivescompletenewwaysofhandlingcomplexintegrationscenarios.ReadmoreaboutthisinchapterSelenium.
Environmentbasedbefore/aftersuite
Youcanenable/disablebeforeandaftersuiteactionsbasedonoptionalenvironmentorsystemproperties.Userscangivepropertynamesorpropertyvaluesthatarecheckedbeforeexecution.Onlyincasetheenvironmentpropertychecksdopasstheactionsareexecutedbefore/afterthetestsuiterun.
WsAddressingheadercustomization
WehaveimprovedtheheadercustomizationoptionswhenusingSOAPWSAddressingfeature.YoucannowoverwritethedefaultWSAddressingheaderspertestactioninadditiontodefiningtheheadersonclientendpointcomponentlevel.
JsonPathdatadictionary
Jsondatadictionarywasbasedonasimpledotnotatedsyntax.NowyoucanalsousemorecomplexJsonPathexpressionsinordertooverwriteelementsinJsonmessagesbasedonthedatadictionarysettingsinCitrus.Readmoreaboutthatinchapterdata-dictionary.
JavaDSLtestbehavior
TestbehaviorsinJavaDSLrepresenttemplatesinXMLDSL.ThebehaviorencapsulatesasetoftestactionstoagroupthatcanbeappliedtomultipleJavaDSLtests.ThisenablesyoutocombinecommontestactionsinJavaDSLwithmorecomfortablereuseoftestactiondefinitions.Seechaptertest-behaviorshowtousethat.
Autoselectmessagetype
DefaultmessagetypeforvalidationtasksinCitrushasbeenXML.BasedonthismessagetypetherespectivemessagevalidatorimplementationappliesforXML,JSON,plaintextandsoon.Youcannowchangethisdefaultmessagetypebysettingasystemproperty(citrus.default.message.type).AlsoCitrusimprovedtheautoselectalgorithmwhenthedefaultmessagetypeisobviouslynotapplicable.WhenamessagearrivesinCitrusthereceivingactiontriestofindoutwhichmessagevalidatorfitsbestaccordingto
CitrusReferenceGuide
9Changesnew
themessagepayload.XMLmessagecontentisautomaticallyidentifiedby<>characters.JSONmessagepayloadsareidentifiedbyor[]charactersforobjectsandarrayrepresentations.ThiswayCitrustriestofindthebestmatchingmessagevalidatorfortheincomingmessage.BeforethatCitrushasalwaysbeenusingthedefaultmessagetypeXML.
Readaboutdifferentmessagevalidatorsinvalidation.
DefaultCucumbersteps
TheCitrusCucumberextensionnowdefinesdefaultstepdefinitionsforHttp,DockerandSelenium.ThesedefaultstepsarereadyforusageinanyCucumberCitrusfeaturespecification.YoucanloadthedefaultstepsasadditionalgluepackagesinyourCucumberoptions.Afterthatyouarereadytogoforusingthedefaultstepsdirectlyinfeaturespecificationfiles.WiththeextensionsyoucanperformDockerandSeleniumcommandsveryeasy.AlsoyoucandescribetheHttpRESTclient-servercommunicationinBDDstyle.Readmoreaboutthisincucumber.
Refactoring
DeprecatedAPIsandclassesthatcoexistedalongtimearenowremoved.Ifyourprojectisusingonofthesedeprecatedclassesyoumayrunintocompiletimeerrors.PleasehavealookattheCitrusAPIJavaDocsanddocumentationinordertofindouthowtousethenewAPIsandclassesthatreplacedtheolddeprecatedstuff.
Bugfixes
Bugsarepartofoursoftwaredevelopersworldandfixingthemispartofyourdailybusiness,too.FindingandsolvingissuesmakesCitrusbettereveryday.ForadetailedlistingofallbugfixespleaserefertothecompletechangeslogofeachreleaseinJIRA(http://www.citrusframework.org/changes-report.html).
CitrusReferenceGuide
10Changesnew
IntroductionNowadaysenterpriseapplicationsusuallycommunicatewithdifferentpartnersoverlooselycoupledmessaginginterfaces.Theinteractionandtheinterfacecontractneedstobetestedinintegrationtesting.
Inatypicalintegrationtestscenarioweneedtosimulatethecommunicationpartnersovervarioustransports.Howcanwetestusecasescenariosthatincludeseveralinterfacepartnersinteractingwitheachother?Howcansomebodyensurethatthesoftwarecomponentsworkcorrectlyregardingtheinterfacecontract?Howcansomebodyrunintegrationtestcasesinanautomatedreproducibleway?Citrustriestoanswerthesequestions!
Overview
Citrusaimstostronglysupportyouinsimulatinginterfacepartnersacrossdifferentmessagingtransports.YoucaneasilyproduceandconsumemessageswithawiderangeofprotocolslikeHTTP,JMS,TCP/IP,FTP,SMTPandmore.Theframeworkisabletobothactasaclientandserver.IneachcommunicationstepCitrusisabletovalidatemessagecontentstowardssyntaxandsemantics.
InadditiontothattheCitrusoffersawiderangeoftestactionstotakecontroloftheprocessflowduringatest(e.g.iterations,systemavailabilitychecks,databaseconnectivity,parallelism,delaying,errorsimulation,scriptingandmanymore).
ThebasicgoalinCitrustestcasesistodescribeawholeusecasescenarioincludingseveralinterfacepartnersthatexchangemanymessageswitheachother.ThecompositionofcomplexmessageflowsinasingletestcasewithseveralteststepsisoneofthemajorfeaturesinCitrus.
ThetestcasedescriptioniseitherdoneinXMLorJavaandcanbeexecutedmultipletimesasautomatedintegrationtest.WithJUnitandTestNGintegrationCitruscaneasilybeintegratedintoyourbuildlifecycleprocess.DuringatestCitrussimulatesallsurroundinginterfacepartners(clientorserver)withoutanycodingeffort.Witheasydefinitionofexpectedmessagecontent(headerandpayload)forXML,CSV,SOAP,JSONorplaintextmessagesCitrusisabletovalidatetheincomingdatatowardssyntaxandsemantics.
CitrusReferenceGuide
11Introduction
Usagescenarios
IfyouareinchargeofanenterpriseapplicationinamessagebasedsolutionwithmessageinterfacestoothersoftwarecomponentsyoushoulduseCitrus.IncaseyourprojectinteractswithothersoftwareoverdifferentmessagingtransportsandincaseyouneedtosimulatetheseinterfacepartnersonclientorserversideyoushoulduseCitrus.Incaseyouneedtocontinuouslycheckthesoftwarestabilitynotonlyonaunittestingbasisbutalsoinanend-to-endintegrationscenarioyoushoulduseCitrus.Bugfixing,releaseorregressiontestingisveryeasywithCitrus.IncaseyouarestrugglingwithcodestabilityandfeeluncomfortableregardingyournextsoftwarereleaseyoushoulddefinitelyuseCitrus.
ThistestsetupistypicalforaCitrususecase.Insuchatestscenariowehaveasystemundertest(SUT)withseveralmessageinterfacestootherapplicationslikeyouwouldhavewithanenterpriseservicebusforinstance.AclientapplicationinvokesservicesontheSUTapplication.TheSUTislinkedtoseveralbackendapplicationsovervariousmessagingtransports(hereSOAP,JMS,andHttp).Interimmessagenotificationsandfinalresponsesaresentbacktotheclientapplication.Thisgeneratesabunchofmessagesthatareexchangedthroughouttheapplicationsinvolved.
IntheautomatedintegrationtestCitrusneedstosendandreceivethosemessagesoverdifferenttransports.Citrustakescareofallinterfacepartners(ClientApplication,Backend1,Backend2,Backend3)andsimulatestheirbehaviorbysendingproperresponsemessagesinordertokeepthemessageflowalive.
Eachcommunicationstepcomeswithmessagevalidationandcomparisonagainstanexpectedmessagetemplate(e.g.XMLorJSONdata).BesidesmessagingactionsCitrusisalsoabletoperformarbitraryothertestactions.Citrusisabletoperformadatabasequerybetweenrequestsasanexample.
CitrusReferenceGuide
12Introduction
TheCitrustestcaserunsfullyautomatedasaJavaapplication.InfactaCitrustestcaseisnothingbutaJUnitorTestNGtestcase.Stepbystepthewholeusecasescenarioisperformedlikeinarealproductionenvironment.TheCitrustestisrepeatableandisincludedintothesoftwarebuildprocess(e.g.usingMavenorANT)likeanormalunittestcasewoulddo.Thisgivesyoufullyautomatedintegrationteststoensureinterfacestability.
ThefollowingreferenceguidewalksthroughallCitruscapabilitiesandshowshowtosetupagreatintegrationtestwithCitrus.
CitrusReferenceGuide
13Introduction
SetupThischapterdiscusseshowtogetstartedwithCitrus.Itdealswiththeinstallationandsetupoftheframework,soyouarereadytostartwritingtestcasesafterreadingthischapter.
UsuallyyouwoulduseCitrusasadependencylibraryinyourproject.InMavenyouwouldjustaddCitrusasatest-scopeddependencyinyourPOM.WhenusingANTyoucanalsorunCitrusasnormalJavaapplicationfromyourbuild.xml.AsCitrustestsarenothingbutnormalunittestsyoucouldalsouseJUnitorTestNGanttaskstoexecutetheCitrustestcases.
ThischapterdescribestheCitrusprojectsetuppossibilities,chooseoneofthemthatfitsbesttoincludeCitrusintoyourproject.
UsingMaven
Citrususeshttp://maven.apache.org/internallyasaprojectbuildtoolandprovidesextendedsupportforMavenprojects.Mavenwilleaseupyourlifeasitmanagesprojectdependenciesandprovidesextendedbuildlifecyclesandconventionsforcompiling,testing,packagingandinstallingyourJavaproject.ThereforeitisrecommendedtousetheCitrusMavenprojectsetup.IncaseyoualreadyuseMavenitismostsuitableforyoutoincludeCitrusasatest-scopeddependency.
AsMavenhandlesallprojectdependenciesautomaticallyyoudonotneedtodownloadanyCitrusprojectartifactsinadvance.IfyouarenewtoMavenpleaserefertotheofficialMavendocumentationtofindouthowtosetupaMavenproject.
UseCitrusMavenarchetype
IfyoustartfromscratchorincaseyouwouldliketohaveCitrusoperatinginaseparateMavenmoduleyoucanusetheCitrusMavenarchetypetocreateanewMavenproject.ThearchetypewillsetupabasicCitrusprojectstructurewithbasicsettingsandfiles.
mvnarchetype:generate-DarchetypeCatalog=http://citrusframework.org
Choosearchetype:1:http://citrusframework.org->citrus-archetype(BasicarchetypeforCitrusintegrationtestproject)Chooseanumber:1
CitrusReferenceGuide
14Setup
DefinevalueforgroupId:com.consol.citrus.samplesDefinevalueforartifactId:citrus-sampleDefinevalueforversion:1.0-SNAPSHOTDefinevalueforpackage:com.consol.citrus.samples
InthesampleaboveweusedtheCitrusarchetypecataloglocatedontheCitrushomepage.CitrusarchetypesarealsoavailableinMavencentralrepository.Socanalsojustuse"mvnarchetype:generate".AsthelistofdefaultarchetypesavailableinMavencentralisverylongyoumightwanttofilterthelistwith"citrus"andyouwillgetjustafewpossibilitiestochoosefrom.
Weloadthearchetypeinformationfrom"http://citrusframework.org"andchoosetheCitrusbasicarchetype.Nowyouhavetodefineseveralvaluesforyourproject:thegroupId,theartifactId,thepackageandtheprojectversion.Afterthatwearedone!MavencreatedaCitrusprojectstructureforuswhichisreadyfortesting.Youshouldseethefollowingbasicprojectfolderstructure.
citrus-sample|+src||+main|||+java|||+resources||+citrus|||+java|||+resources|||+testspom.xml
TheCitrusprojectisabsolutelyreadyfortesting.WithMavenwecanbuild,package,installandtestourprojectrightawaywithoutanyadjustments.Trytoexecutethefollowingcommands:
mvnintegration-testmvnintegration-test-Dtest=MyFirstCitrusTest
NoteIfyouneedadditionalassistanceinsettingupaCitrusMavenprojectpleasevisitourMavensetuptutorialonhttp://www.citrusframework.org/tutorials.html.
AddCitrustoexistingMavenproject
CitrusReferenceGuide
15Setup
IncaseyoualreadyhaveaproperMavenprojectyoucanalsointegrateCitruswithit.JustaddtheCitrusprojectdependenciesinyourMavenpom.xmlasadependencylikefollows.
WeaddCitrusastest-scopedprojectdependencytotheprojectPOM(pom.xml)
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-core</artifactId><version>2.7.1</version><scope>test</scope></dependency>
IncaseyouwouldliketousetheCitrusJavaDSLalsoaddthisdependencytotheproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-java-dsl</artifactId><version>2.7.1</version><scope>test</scope></dependency>
AddthecitrusMavenplugintoyourproject
<plugin><groupId>com.consol.citrus.mvn</groupId><artifactId>citrus-maven-plugin</artifactId><version>2.7.1</version><configuration><author>DonaldDuck</author><targetPackage>com.consol.citrus</targetPackage></configuration></plugin>
NowthatwehaveaddedCitrustoourMavenprojectwecanstartwritingnewtestcaseswiththeCitrusMavenplugin:
mvncitrus:create-test
OnceyouhavewrittentheCitrustestcasesyoucanexecutethemautomaticallyinyourMavensoftwarebuildlifecylce.Thetestswillbeincludedintoyourprojectsintegration-testphaseusingtheMavensurefireplugin.Hereisasamplesurefireconfigurationfor
CitrusReferenceGuide
16Setup
Citrus.
<plugin><artifactId>maven-surefire-plugin</artifactId><version>2.4.3</version><configuration><skip>true</skip></configuration><executions><execution><id>citrus-tests</id><phase>integration-test</phase><goals><goal>test</goal></goals><configuration><skip>false</skip></configuration></execution></executions></plugin>
TheCitrussourcedirectoriesaredefinedastestsourceslikefollows:
<testSourceDirectory>src/it/java</testSourceDirectory><testResources><testResource><directory>src/it/java</directory><includes><include>**</include></includes><excludes><exclude>*.java</exclude></excludes></testResource><testResource><directory>src/it/tests</directory><includes><include>**/*</include></includes><excludes></excludes></testResource></testResources>
NoweverythingissetupandyoucancalltheusualMaveninstallgoal(mvncleaninstall)inordertobuildyourproject.TheCitrusintegrationtestsareexecutedautomaticallyduringthebuildprocess.BesidesthatyoucancalltheMavenintegration-
CitrusReferenceGuide
17Setup
testphaseexplicitlytoexecuteallCitrustestsoraspecifictestbyitsname:
mvnintegration-testmvnintegration-test-Dtest=MyFirstCitrusTest
NoteIfyouneedadditionalassistanceinsettingupaCitrusMavenprojectpleasevisitourMavensetuptutorialonhttp://www.citrusframework.org/tutorials.html.
UsingAnt
Antisaverypopularwaytocompile,test,packageandexecuteJavaprojects.TheApacheprojecthaseffectivelybecomeastandardinbuildingJavaprojects.YoucanrunCitrustestcaseswithAntasCitrusisnothingbutaJavaapplication.ThissectiondescribesthestepstosetupaproperCitrusAntproject.
Preconditions
BeforewestartwiththeCitrussetupbesuretomeetthefollowingpreconditions.Thefollowingsoftwareshouldbeinstalledonyourcomputer,inordertousetheCitrusframework:
Java8orhigher
InstalledJDKplusJAVA_HOMEenvironmentvariablesetupandpointingtoyourJavainstallationdirectory
JavaIDE(optional)
AJavaIDEwillhelpyoutomanageyourCitrusproject(e.g.creatingandexecutingtestcases).YoucanusetheanyJavaIDE(e.g.EclipseorIntelliJIDEA)butalsoanyconvenientXMLEditortowritenewtestcases.
Ant1.8orhigher
Ant(http://ant.apache.org/)willruntestsandcompileyourCitruscodeextensionsifnecessary.
Download
FirstofallweneedtodownloadthelatestCitrusreleasearchivefromtheofficialwebsitehttp://www.citrusframework.org
CitrusReferenceGuide
18Setup
Citruscomestoyouasazippedarchiveinoneofthefollowingpackages:
citrus-x.x-releasecitrus-x.x-src
ThereleasepackageincludestheCitrusbinariesaswellasthereferencedocumentationandsomesampleapplications.
IncaseyouwanttogetintouchwithdevelopinganddebuggingCitrusyoucanalsogowiththesourcearchivewhichgivesyouthecompleteCitrusJavacodesources.ThewholeCitrusprojectisalsoaccessibleforyouonhttp://github.com/christophd/citrus.ThisopengitrepositoryonGitHubenablesyoutobuildCitrusfromscratchwithMavenandcontributecodechanges.
Installation
AfterdownloadingtheCitrusarchivesweextractthoseintoanappropriatelocationonthelocalstorage.WeareseekingfortheCitrusprojectartifactscomingasnormalJavaarchives(e.g.citrus-core.jar,citrus-ws.jar,etc.)
YouhavetoincludethoseCitrusJavaarchivesaswellasalldependencylibrariestoyourApacheAntJavaclasspath.Usuallyyouwouldcopyalllibrariesintoyourproject'slibdirectoryanddeclarethoselibrariesintheAntbuildfile.AsthisapproachcanbeverytimeconsumingIrecommendtouseadependencymanagementAPIsuchasApacheIvywhichgivesyouautomaticdependencyresolutionlikethatfromMaven.Inparticularthiscomesinhandywithallthe3rdpartydependenciesthatwouldberesolvedautomatically.
NomatterwhatapproachyouareusingtosetuptheApacheAntclasspathseethefollowingsampleAntbuildscriptwhichusestheCitrusprojectartifactsincombinationwiththeTestNGAnttaskstorunthetests.
<projectname="citrus-sample"basedir="."default="citrus.run.tests"xmlns:artifact="antlib:org.apache.maven.artifact.ant"
<propertyfile="src/it/resources/citrus.properties"/>
<pathid="maven-ant-tasks.classpath"path="lib/maven-ant-tasks-2.1.3.jar"/><typedefresource="org/apache/maven/artifact/ant/antlib.xml"uri="antlib:org.apache.maven.artifact.ant"classpathref="maven-ant-tasks.classpath"/>
<artifact:pomid="citrus-pom"file="pom.xml"/><artifact:dependenciesfilesetId="citrus-dependencies"pomRefId="citrus-pom"/>
CitrusReferenceGuide
19Setup
<pathid="citrus-classpath"><pathelementpath="src/it/java"/><pathelementpath="src/it/resources"/><pathelementpath="src/it/tests"/><filesetrefid="citrus-dependencies"/></path>
<taskdefresource="testngtasks"classpath="lib/testng-6.8.8.jar"/>
<targetname="compile.tests"><javacsrcdir="src/it/java"classpathref="citrus-classpath"/><javacsrcdir="src/it/tests"classpathref="citrus-classpath"/></target>
<targetname="create.test"description="Createsanewemptytestcase"><inputmessage="Entertestname:"addproperty="test.name"/><inputmessage="Entertestdescription:"addproperty="test.description"/><inputmessage="Enterauthor'sname:"addproperty="test.author"defaultvalue="$default.test.author"<inputmessage="Enterpackage:"addproperty="test.package"defaultvalue="$default.test.package"<inputmessage="Enterframework:"addproperty="test.framework"defaultvalue="testng"/>
<javaclassname="com.consol.citrus.util.TestCaseCreator"><classpathrefid="citrus-classpath"/><argline="-name$test.name-author$test.author-description$test.description-package$test.package-framework$test.framework"</java></target>
<targetname="citrus.run.tests"depends="compile.tests"description="RunsallCitrustests"<testngclasspathref="citrus-classpath"><classfilesetdir="src/it/java"includes="**/*.class"/></testng></target>
<targetname="citrus.run.single.test"depends="compile.tests"description="Runsasingletestbyname"<touchfile="test.history"/><loadpropertiessrcfile="test.history"/>
<echomessage="Lasttestexecuted:$last.test.executed"/><inputmessage="Entertestnameorleaveemptyforlasttestexecuted:"addproperty="testclass"
<propertyfilefile="test.history"><entrykey="last.test.executed"type="string"value="$testclass"/></propertyfile>
<testngclasspathref="citrus-classpath"><classfilesetdir="src/it/java"includes="**/$testclass.class"/></testng></target>
</project>
CitrusReferenceGuide
20Setup
NoteIfyouneeddetailedassistanceforbuildingCitruswithAntdoalsovisitourtutorialssectiononhttp://www.citrusframework.org.ThereyoucanfindatutorialwhichdescribestheCitrusJavaprojectsetupwithAntfromscratch.
CitrusReferenceGuide
21Setup
TestcasesNowletusstartwritingtestcases!AtestcaseinCitrusdescribesallstepsforacertainusecaseinonesinglefile.TheCitrustestholdsasequenceoftestactions.Eachactionrepresentsaveryspecialpurposesuchassendingorreceivingamessage.Typicallywithmessage-basedenterpriseapplicationsthesendingandreceivingofmessagesrepresentthemainactionsinsideatest.
HoweveryouwilllearnthatCitrusismorethanjustasimpleSOAPclientforinstance.Eachtestcasecanholdcomplexactionssuchasconnectingtothedatabase,transformingdata,addingloopsandconditionalsteps.WiththedefaultCitrusactionsetyoucanaccomplishverycomplexusecaseintegrationtests.Laterinthisguidewewillbrieflydiscussallavailabletestactionsandlearnhowtousevariousmessagetransportswithinthetest.Fornowwewillconcentrateonthebasictestcasestructure.
ThefigureabovedescribesatypicaltestactionsequenceinCitrus.Alistofsendingandreceivingtestactionscomposingatypicaltestcasehere.EachactionreferencesapredefinedCitrusendpointcomponentthatwearegoingtotalkaboutlateron.
Sohowdowedefinethosetestcases?IngeneralCitrusspecifiestestcasesasJavaclasses.WithTestNGorJUnityoucanexecutetheCitrustestswithinyourJavaruntimeasyouwoulddowithinunittesting.YoucancodetheCitrustestinasingleJavaclass
CitrusReferenceGuide
22Testcase
doingassertionsandusingSpring'sdependencyinjectionmechanisms.
IfyouarenotfamiliartowritingJavacodeyoucanalsowriteCitrustestsasXMLfiles.WhatevertestlanguageyouchooseforCitrusthewholetestcasedescriptiontakesplaceinonesinglefile(JavaorXML).ThischapterwillintroducethecustomXMLschemalanguageaswellastheJavadomainspecificlanguagesoyouwillbeabletowriteCitrustestcasesnomatterwhatknowledgebaseyoubelongto.
WritingtestcasesinXML
Putsimply,aCitrustestcaseisnothingbutasimpleSpringXMLconfigurationfile.TheSpringframeworkhasbecomeastateoftheartdevelopmentframeworkforenterpriseJavaapplications.AsyouworkwithCitrusyouwillalsolearnhowtousetheSpringIoc(Inversionofcontrol)containerandtheconceptsofdependencyinjection.SoletushavealookatthepureSpringXMLconfigurationsyntaxfirst.YouarefreetowritefullycompatibletestcasesfortheCitrusframeworkjustusingthissyntax.
Springbeandefinitionsyntax
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
<beanname="MyFirstTest"class="com.consol.citrus.TestCase"><propertyname="variableDefinitions"><!--variablesofthistestgohere--></property><propertyname="actions"><!--actionsofthistestgohere--></property></bean></beans>
CitruscanexecutetheseSpringbeandefinitionsasnormaltestcases-noproblem,butthepureSpringXMLsyntaxisveryverboseandprobablynotthebestwaytodescribeatestcaseinCitrus.InparticularyouhavetoknowalotofCitrusinternalssuchasJavaclassnamesandpropertynames.Inadditiontothatastestscenariosgetmorecomplexthetestcasesgrowinsize.Soweneedamoreeffectiveandcomfortablewayofwritingtests.ThereforeCitrusprovidesacustomXMLschemadefinitionforwritingtestcaseswhichismuchmoreadequateforourtestingpurpose.
CitrusReferenceGuide
23Testcase
ThecustomXMLschemaaimstoreachtheconvenienceofdomainspecificlanguages(DSL).LetushavealookattheCitrustestdescribingXMLlanguagebyintroducingafirstverysimpletestcasedefinition:
XMLDSL
<spring:beansxmlns="http://www.citrusframework.org/schema/testcase"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:spring="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/testcasehttp://www.citrusframework.org/schema/testcase/citrus-testcase.xsd">
<testcasename="MyFirstTest"><description>Firstexampleshowingthebasictestcasedefinitionelements!</description><variables><variablename="text"value="HelloTestFramework"/></variables><actions><echo><message>$text</message></echo></actions></testcase></spring:beans>
Wedoneedthe<spring:beans>rootelementastheXMLfileisreadbytheSpringIoCcontainer.InsidethisrootelementtheCitrusspecificnamespacedefinitionstakeplace.
Thetestcaseitselfgetsamandatorynamethatmustbeuniquethroughoutalltestcasesinaproject.Youwillreceiveerrorswhenusingduplicatetestnames.ThetestnamehastofollowthecommonJavanamingconventionsandrulesforJavaclasses.Thismeansnamesmustnotcontainanywhitespacecharactersbutcharacterslike'-','.','_'aresupported.Forexample,TestFeature_1isvalidbutTestFeature1isnotasitcontainswhitespacecharacterslikespaces.
NowthatwehaveanXMLdefinitionthatdescribesthestepsofourtestweneedaJavaexecutableforthetest.TheJavaexecutableisneededfortheframeworkinordertorunthetest.SeethefollowingsampleJavaclassthatrepresentsasimpleCitrusJavatest:
importorg.testng.annotations.Test;
CitrusReferenceGuide
24Testcase
importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.testng.AbstractTestNGCitrusTest;
@TestpublicclassMyFirstTestextendsAbstractTestNGCitrusTest
@CitrusXmlTest(name="MyFirstTest")publicvoidmyFirstTest()
ThesampleaboveisaJavaclassthatrepresentsavalidCitrusJavaexecutable.TheJavaclasshasnoprogramminglogicasweuseaXMLtestcasehere.TheJavaclasscanalsobegeneratedusingtheCitrusMavenplugin.TheJavaclassextendsfrombasicsuperclassAbstractTestNGCitrusTestandthereforeusesTestNGasunittestframework.CitrusalsosupportsJUnitasunittestframework.Readmoreaboutthisinrun-testngandrun-junit.
UptonowitisimportanttounderstandthatCitrusalwaysneedsaJavaexecutabletestclass.IncaseweusetheXMLtestrepresentationtheJavapartisgeneric,canbegeneratedandcontainsnoprogramminglogic.TheXMLtestdefinesallstepsandisourprimarytestcasedefinition.
WritingtestcasesinJava
BeforewegointomoredetailsontheattributesandactionsthattakeplacewithinatestcasewejusthavealookathowtowritetestcaseswithpureJavacode.CitrusworkswithJavaandusesthewellknownJUnitandTestNGframeworkbenefitsthatyoumaybeusedtoasatester.ManyusersmayprefertowriteJavacodeinsteadoftheverboseXMLsyntax.ThereforeyouhaveanotherpossibilityforwritingCitrustestsinpureJava.
WhenusingtheCitrusJavaDSLweneedtoincludeaspecialMavendependencymoduletoourprojectthatprovidestheneededAPI.
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-java-dsl</artifactId><version>2.7.1</version><scope>test</scope></dependency>
CitrusingeneraldifferencesbetweentwowaysoftestcasesinJava.Thesearetest-designersandtest-runnersthatwedealwitheachinthenexttwosections.
CitrusReferenceGuide
25Testcase
JavaDSLtestdesigner
ThefirstwayofdefiningaCitrustestinJavaisthetest-designer.TheJavaDSLforatestdesignerworkssimilartotheXMLapproach.Thewholetestcaseisbuiltwithalltestactionsfirst.ThenthewholetestcaseisexecutedasawholeCitrustest.ThisishowtodefineaCitrustestwithdesignerJavaDSLmethods:
JavaDSLdesigner
importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.testng.TestNGCitrusTestDesigner;
@TestpublicclassMyFirstTestDesignerextendsTestNGCitrusTestDesigner@CitrusTest(name="MyFirstTest")publicvoidmyFirstTest()description("Firstexampleshowingthebasictestcasedefinitionelements!");
variable("text","HelloTestFramework");
echo("$text");
CitrusprovidesabaseJavaclasscom.consol.citrus.dsl.testng.TestNGCitrusTestDesignerthatprovidesallcapabilitiesforyouinformofbuilderpatternmethods.Justusethe@CitrusTestannotationontopofthetestmethod.Citruswillusethemethodnameasthetestnamebydefault.Asyoucanseeintheexampleaboveyoucanalsocustomizethetestnamewithinthe@CitrusTestannotation.Thetestmethodbuildsalltestactionsusingthetestbuilderpattern.Thedefinedtestactionswillthenbecalledlateronduringtestruntime.
Thedesigntimeruntimedifferenceintest-designerisreallyimportanttobeunderstood.YoucanmixtheCitrusJavaDSLexecutionwithotherJavacodewithcertainlimitations.Wewillexplainthislateronwhenintroducingthetest-runner.
ThisisthebasictestJavaclasspatternusedinCitrus.Youasatesterwithdevelopmentbackgroundcaneasilyextendthispatternforcustomizedlogic.AgainifyouarecomingwithoutcodingexperiencedonotworrythisJavacodeisoptional.YoucandoexactlythesamewiththeXMLsyntaxonlyasshownbefore.ThetestdesignerJavaDSLismuchmorepowerfulthoughasyoucanusethefullJavaprogramminglanguagewithclassinheritanceandmethoddelegation.
CitrusReferenceGuide
26Testcase
Wehavementionedthatthetest-designerwillbuildthecompletetestcaseindesigntimewithallactionsfirstbeforeexecutionofthewholetestcasetakesplaceatruntimeofthetest.ThisapproachhastheadvantagethatCitrusknowsalltestactionsinatestbeforeexecution.OntheotherhandyouarelimitedinmixingJavaDSLmethodcallsandnormalJavacode.Thefollowingexampleshouldclarifythingsalittlebit.
JavaDSLdesigner
importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.testng.TestNGCitrusTestDesigner;
@TestpublicclassLoggingTestDesignerextendsTestNGCitrusTestDesignerprivateLoggingServiceloggingService=newLoggingService();
@CitrusTest(name="LoggingTest")publicvoidloggingTest()echo("BeforeloggingServicecall");
loggingService.log("Nowcalledcustomloggingservice");
echo("AfterloggingServicecall");
InthisexampletestcaseaboveweuseaninstanceofacustomLoggingServiceandcallsomeoperationlog()inthemiddleofourJavaDSLtest.NowdevelopersmightexpecttheloggingservicecalltobedoneinthemiddleoftheJavaCitrustestcasebutifwehavealookattheloggingoutputofthetestwegetatotaldifferentresult:
Expectedoutput
INFOCitrus|STARTINGTESTLoggingTestINFOEchoAction|BeforeloggingServicecallINFOLoggingService|NowcalledcustomloggingserviceINFOEchoAction|AfterloggingServicecallINFOCitrus|TESTSUCCESSLoggingTest
Actualoutput
INFOLoggingService|NowcalledcustomloggingserviceINFOCitrus|STARTINGTESTLoggingTestINFOEchoAction|BeforeloggingServicecallINFOEchoAction|AfterloggingServicecall
CitrusReferenceGuide
27Testcase
INFOCitrus|TESTSUCCESSLoggingTest
SoifweanalysetheactualloggingoutputweseethattheloggingservicewascalledevenbeforetheCitrustestcasedidstartitsaction.Thisistheresultoftest-designerbuildingupthewholetestcasefirst.Thedesignercollectsalltestactionsfirstininternalmemorycacheandtheexecutesthewholetestcase.SothecustomservicecallontheLoggingServiceisnotpartoftheCitrusJavaDSLtestandthereforeisexecutedimmediatelyatdesigntime.
Wecanfixthiswiththefollowingtest-designercode:
JavaDSLdesigner
importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.testng.TestNGCitrusTestDesigner;
@TestpublicclassLoggingTestDesignerextendsTestNGCitrusTestDesignerprivateLoggingServiceloggingService=newLoggingService();
@CitrusTest(name="LoggingTest")publicvoidloggingTest()echo("BeforeloggingServicecall");
action(newAbstractTestAction()doExecute(TestContextcontext)loggingService.log("Nowcalledcustomloggingservice"););
echo("AfterloggingServicecall");
NowweplacedtheloggingServicecallinsideacustomTestActionimplementationandthereforethispieceofcodeispartoftheCitrusJavaDSLandfollowingfromthatpartoftheCitrustestexecution.Nowwiththatfixwegettheexpectedloggingoutput:
INFOCitrus|STARTINGTESTLoggingTestINFOEchoAction|BeforeloggingServicecallINFOLoggingService|NowcalledcustomloggingserviceINFOEchoAction|AfterloggingServicecallINFOCitrus|TESTSUCCESSLoggingTest
CitrusReferenceGuide
28Testcase
NowthisisnoteasytounderstandandpeopledidstrugglewiththisseparationofdesigntimeandruntimeofaCitrusJavaDSLtest.ThisiswhywehaveimplementedanewJavaDSLbaseclasscalledtest-runnerthatwedealwithinthenextsection.Beforewecontinuewehavetomentionthatthetest-designerapproachdoesalsoworkforJUnit.AlthoughwehaveonlyseenTestNGsamplecodeinthissectioneverythingisworkingexactlythesamewaywithJUnitframework.Justusethebaseclasscom.consol.citrus.dsl.junit.JUnit4CitrusTestDesignerinstead.
ImportantNeitherTestNGCitrusTestDesignernorJUnit4CitrusTestDesignerimplementationisthreadsafeforparalleltestexecution.Thisissimplybecausethebaseclassisholdingstatetothecurrenttestdesignerinstanceinordertodelegatemethodcallstothisinstance.Thereforeparalleltestmethodexecutionisnotavailable.Fortunatelywehaveaddedathreadsafebaseclassimplementationthatusesresourceinjection.Readmoreaboutthisintestcase-resource-injection.
JavaDSLtestrunner
Thenewtestrunnerconceptsolvestheissuesthatmaycomealongwhenworkingwiththetestdesigner.Wehavealreadyseenasimpleexamplewherethetestdesignerrequiresstrictseparationofdesigntimeandruntime.Thetestrunnerimplementationexecuteseachtestactionimmediately.ThischangestheprerequisitesinsuchthatthetestactionJavaDSLmethodcallscanbemixedwithusualJavacodestatements.Thetheexamplethatwehaveseenbeforeinatestrunnerimplementation:
JavaDSLrunner
importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.testng.TestNGCitrusTestRunner;
@TestpublicclassLoggingTestRunnerextendsTestNGCitrusTestRunnerprivateLoggingServiceloggingService=newLoggingService();
@CitrusTest(name="LoggingTest")publicvoidloggingTest()echo("BeforeloggingServicecall");
loggingService.log("Nowcalledcustomloggingservice");
echo("AfterloggingServicecall");
CitrusReferenceGuide
29Testcase
WiththenewtestrunnerimplementationasbaseclassweareabletomixJavaDSLmethodcallsandnormalJavacodestatementinourtestinanunlimitedway.ThisexampleabovewillalsocreatetheexpectedloggingoutputasallJavaDSLmethodcallsareexecutedimmediately.
INFOCitrus|STARTINGTESTLoggingTestINFOEchoAction|BeforeloggingServicecallINFOLoggingService|NowcalledcustomloggingserviceINFOEchoAction|AfterloggingServicecallINFOCitrus|TESTSUCCESSLoggingTest
Incontrarytothetestdesignerthetestrunnerimplementationwillnotbuildthecompletetestcasebeforeexecution.EachtestactionisexecutedimmediatelyasitiscalledwithJavaDSLbuildermethods.Thiscreatesamorenaturalwayofcodingtestcasesasyouarealsoabletouseiterations,trycatchblocks,finallysectionsandsoon.
IntheexampleshereTestNGwasusedasunitframework.OfcoursetheexactsameapproachcanalsoapplytoJUnitframework.Justusethebaseclasscom.consol.citrus.dsl.junit.JUnit4CitrusTestRunnerinstead.Feelfreetochoosethebaseclassfortest-designerortest-runnerasyoulike.Youcanalsomixthosetwoapproachesinyourproject.CitrusisabletohandlebothwaysofJavaDSLcodeinaproject.
ImportantTheTestNGCitrusTestRunnerandJUnit4CitrusTestRunnerimplementationisnotthreadsafeforparalleltestexecution.Thisissimplybecausethebaseclassisholdingstatetothecurrenttestrunnerinstanceinordertodelegatemethodcallstothisinstance.Thereforeparalleltestmethodexecutionisnotavailable.Fortunatelywehaveaddedathreadsafebaseclassimplementationthatusesresourceinjection.Readmoreaboutthisintestcase-resource-injection.
Designer/Runnerinjection
Intheprevioussectionswehaveseenthedifferentapproachesfortestdesignerandrunnerimplementations.Uptonowthedecisionwhichimplementationtousewasmadebyextendingoneofthebaseclasses:
com.consol.citrus.dsl.testng.TestNGCitrusTestRunnercom.consol.citrus.dsl.testng.TestNGCitrusTestDesignercom.consol.citrus.dsl.junit.JUnit4CitrusTestRunnercom.consol.citrus.dsl.junit.JUnit4CitrusTestDesigner
CitrusReferenceGuide
30Testcase
ThesefourclassesrepresentthedifferentdesignerandrunnerimplementationsforTestNGorJUnit.NowCitrusalsoprovidesaresourceinjectionmechanismforbothdesignerandrunnerimplementations.Theclassesusingthisfeatureare:
com.consol.citrus.dsl.testng.TestNGCitrusTestcom.consol.citrus.dsl.junit.JUnit4CitrusTest
Sowhatisthedealwiththat?Itissimplewhenlookingatafirstexampleusingresourceinjection:
@TestpublicclassInjectionTestextendsJUnit4CitrusTest
@CitrusTest(name="JUnit4DesignerTest")publicvoiddesignerTest(@CitrusResourceTestDesignerdesigner)designer.echo("Nowworkingondesignerinstance");
@CitrusTest(name="JUnit4RunnerTest")publicvoidrunnerTest(@CitrusResourceTestRunnerrunner)runner.echo("Nowworkingonrunnerinstance");
ThedesignerorrunnerinstanceisinjectedasCitrusresourcetothetestmethodasparameter.Thiswaywecanmixdesignerandrunnerinasingletest.Butthisisnottherealmotivationfortheresourceinjection.Theclearadvantageofthisapproachwithinjecteddesignerandrunnerinstancesissupportformultithreading.IncaseyouwanttoexecutetheCitrustestsinparallelusingmultiplethreadsyouneedtousethisapproach.Thisisbecausetheusualdesignerandrunnerbaseclassesarenotthreadsafe.ThisJUnit4CitrusTestbaseclassisbecausetheresourcesinjectedarenotkeptasstateinthebaseclass.
ThisisourfirstCitrusresourceinjectionusecase.Theframeworkisabletoinjectotherresources,too.Findoutmoreaboutthisinthenextsections.
Testcontextinjection
WhenrunningatestcaseinCitruswemakeuseofbasicframeworkcomponentsandcapabilities.Oneofthesecapabilitiesistousetestvariables,functionsandvalidationmatchers.Uptothispointwehavenotlearnedaboutthesethings.Theywillbedescribedintheupcomingchaptersandsectionsinmoredetail.RightnowIwanttotalkaboutresourceinjectioninCitrus.
CitrusReferenceGuide
31Testcase
AllthesefeaturementionedaboveareboundtosomeimportantCitruscomponent:theCitrustestcontext.Thetestcontextholdsallvariablesandisabletoresolvefunctionsandmatchers.Ingeneralyouasatesterwillnotneedexplicitaccesstothiscomponentastheframeworkisworkingwithitbehindthescenes.IncaseyouneedsomeaccessforadvancedoperationswiththeframeworkCitrusprovidesaresourceinjection.Letshavealookatthissothingsaregettingmoreclear.
publicclassResourceInjectionITextendsJUnit4CitrusTestDesigner
@Test@CitrusTestpublicvoidresourceInjectionIT(@CitrusResourceTestContextcontext)context.setVariable("myVariable","somevalue");echo("$myVariable");
Asyoucanseewehaveaddedamethodparameteroftypecom.consol.citrus.context.TestContexttothetestmethod.Theannotation@CitrusResourcetellsCitrustoinjectthisparameterwiththeaccordinginstanceoftheobjectforthistest.Nowwehaveeasyaccesstothecontextandallitscapabilitiessuchasvariablemanagement.
OfcoursethesameapproachworkswithTestNG,too.AsTestNGalsoprovidesresourceinjectionmechanismswehavetomakesurethatthedifferentresourceinjectionapproachesdonotinterferewitheachother.SowetellTestNGtonotinjectthisparameterbydeclaringitas@OptionalforTestNG.InadditiontothatweneedtointroducetheparametertoTestNGwiththe@Parametersannotation.OtherwiseTestNGwouldcomplainaboutnotknowingthisparameter.ThefinaltestmethodwithCitrusresourceinjectionlookslikefollows:
publicclassResourceInjectionITextendsTestNGCitrusTestDesigner
@Test@Parameters("context")@CitrusTestpublicvoidresourceInjectionIT(@Optional@CitrusResourceTestContextcontext)context.setVariable("myVariable","somevalue");echo("$myVariable");
CitrusReferenceGuide
32Testcase
Somemoreannotationsneededbuttheresultisthesame.WehaveaccesstotheCitrustestcontext.OfcourseyoucancombinetheresourceinjectionfordifferentCitruscomponents.Justaddmoresome@CitrusResourceannotatedmethodparameterstothetestmethod.
JavaDSLtestbehaviors
WhenusingtheJavaDSLtheconceptofbehaviorsisagoodwaytoreusetestactionblocks.Byputtingtestactionstoatestbehaviorwecaninstantiateandapplythebehaviortodifferenttestcasesmultipletimes.Themechanismisexplainedbestwhenhavingasimplesample:
publicclassFooBehaviorextendsAbstractTestBehaviorpublicvoidapply()variable("foo","test");
echo("fooBehavior");
publicclassBarBehaviorextendsAbstractTestBehaviorpublicvoidapply()variable("bar","test");
echo("barBehavior");
Thelistingaboveshowstwotestbehaviorsthataddveryspecifictestactionsandtestvariablestothetestcase.AsyoucanseethetestbehaviorisabletousethesameJavaDSLactionmethodsasanormaltestcasewoulddo.Insidetheapplymethodblockwedefinethebehaviorstestlogic.Nowoncethisisdonewecanusethebehaviorsinatestcaselikethis:
@CitrusTestpublicvoidbehaviorTest()description("ThisisabehaviorTest");author("Christoph");status(TestCaseMetaInfo.Status.FINAL);
variable("var","test");
applyBehavior(newFooBehavior());
echo("Successfullyappliedbarbehavior");
CitrusReferenceGuide
33Testcase
applyBehavior(newBarBehavior());
echo("Successfullyappliedbarbehavior");
ThebehaviorisappliedtothetestcasebycallingtheapplyBehaviormethod.Asaresultthebehavioriscalledaddingitslogicatthispointofthetestexecution.Thesamebehaviorcannowbecalledinmultipletestcasessowehaveareusablesetoftestactions.
Description
Inthetestexamplesthatwehaveseensofaryoumayhavenoticedthatatestercangiveadetailedtestdescription.Thetestcasedescriptionclarifiesthetestingpurposeandperspectives.Thedescriptionshouldgiveashortintroductiontotheintendedusecasescenariothatwillbetested.Theusershouldgetafirstimpressionwhatthetestcaseisallaboutaswellasspecialinformationtounderstandthetestscenario.Youcanusefreetextinyourtestdescriptionnolimittothenumberofcharacters.ButbeawareoftheXMLvalidationrulesofwellformedXMLwhenusingtheXMLtestsyntax(e.g.specialcharacterescaping,useofCDATAsectionsmayberequired)
TestActions
Nowwegetclosetothemainpartofwritinganintegrationtest.ACitrustestcasedefinesasequenceofactionsthatwilltakeplaceduringthetest.Actionsbydefaultareexecutedsequentiallyinthesameorderastheyaredefinedinthetestcasedefinition.
XMLDSL
<actions><action>[...]</action><action>[...]</action></actions>
Allactionshaveindividualnamesandpropertiesthatdefinetherespectivebehavior.Citrusoffersawiderangeoftestactionsfromscratch,butyouarealsoabletowriteyourowntestactionsinJavaorGroovyandexecutethemduringatest.actionsgivesyouabriefdescriptionofallavailableactionsthatcanbepartofatestcaseexecution.
CitrusReferenceGuide
34Testcase
Theactionsarecombinedinfreesequencetoeachothersothatthetesterisabletodeclareaspecialactionchaininsidethetest.Theseactionscanbesendingorreceivingmessages,delayingthetest,validatingthedatabaseandsoon.Step-by-stepthetestproceedsthroughtheactionchain.Incaseonesingleactionfailsbyreasonthewholetestcaseisredanddeclarednotsuccessful.
Finallytestsection
Javadevelopersmightbefamiliarwiththeconceptofdoingsomethinginthefinallycodesection.Thefinallysectioncontainsalistoftestactionsthatwillbeexecutedguaranteedattheveryendofthetestcaseeveniferrorsdidoccurduringtheexecutionbefore.Thisistherightplacetotidyupthingsthatwerepreviouslycreatedbythetestlikecleaningupthedatabaseforinstance.Thefinallysectionisdescribedinmoredetailinfinally.Howeverhereisthebasicsyntaxinsideatest.
XMLDSL
<finally><echo><message>Dofinally-regardlessofwhathashappenedbefore</message></echo></finally>
JavaDSLdesigner
@CitrusTestpublicvoidsampleTest()echo("HelloTestFramework");
doFinally(echo("Dofinally-regardlessofanyerrorbefore"));
JavaDSLrunner
@CitrusTestpublicvoidsampleTest()echo("HelloTestFramework");
doFinally().actions(echo("Dofinally-regardlessofanyerrorbefore")
CitrusReferenceGuide
35Testcase
);
Testmetainformation
Theusercanprovidesomeadditionalinformationaboutthetestcase.Themeta-infosectionattheverybeginningofthetestcaseholdsinformationlikeauthor,statusorcreationdate.Indetailthemetainformationisspecifiedlikethis:
XMLDSL
<testcasename="metaInfoTest"><meta-info><author>ChristophDeppisch</author><creationdate>2008-01-11</creationdate><status>FINAL</status><last-updated-by>ChristophDeppisch</last-updated-by><last-updated-on>2008-01-11T10:00:00</last-updated-on></meta-info><description>...</description><actions>...</actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidsampleTest()description("ThisisaTest");author("Christoph");status(Status.FINAL);
echo("HelloCitrus!");
Thestatusallowsfollowingvalues:DRAFT,READY_FOR_REVIEW,DISABLED,FINAL.Themeta-datainformationtoatestisquiteimportanttogivethereaderafirstinformationaboutthetest.Itisalsopossibletogeneratetestdocumentationusingthismeta-datainformation.Thebuilt-inCitrusdocumentationgeneratesHTMLorExceldocumentsthatlistalltestswiththeirmetadatainformationanddescription.
CitrusReferenceGuide
36Testcase
NoteTestswiththestatusDISABLEDwillnotbeexecutedduringatestsuiterun.SosomeonecanjuststartaddingplannedtestcasesthatarenotfinishedyetinstatusDRAFT.Incaseatestisnotrunnableyetbecauseitisnotfinished,someonemaydisableatesttemporarilytoavoidcausingfailuresduringatestrun.UsingthesedifferentstatusesonecaneasilysetuptestplansandreviewtheprogressoftestcoveragebycomparingthenumberofDRAFTteststothoseintheFINALstate.
NowyouknowthepossibilitieshowtowriteCitrustestcasesinXMLorJava.Pleasechoosewhatevercodelanguagetypeyouwant(Java,XML,Springbeansyntax)inordertowriteCitrustestcases.DevelopersmaychooseJava,testerswithoutcodingexperiencemayrunbestwiththeXMLsyntax.WeareconstantlyworkingonevenmoretestwritinglanguagesupportsuchasGroovy,Scala,Xtext,andsoon.IngeneralyoucanmixthedifferentlanguagetypesjustasyoulikewithinyourCitrusprojectwhichgivesyouthebestofflexibility.
CitrusReferenceGuide
37Testcase
TestvariablesTheusageoftestvariablesisacoreconceptwhenwritinggoodmaintainabletests.Thekeyidentifiersofatestcaseshouldbeexposedastestvariablesattheverybeginningofatest.Thiswayhardcodedidentifiersandmultipleredundantvaluesinsidethetestcanbeavoidedfromscratch.Asatesteryoudefinealltestvariablesattheverybeginningofyourtest.
XMLDSL
<variables><variablename="text"value="HelloTestFramework"/><variablename="customerId"value="123456789"/></variables>
JavaDSLdesignerandrunner
variable("text","HelloTestFramework");variable("customerId","123456789");
Theconceptoftestvariablesisessentialwhenwritingcomplextestswithlotsofidentifiersandsemanticdata.Testvariablesarevalidforthewholetestcase.Youcanreferencethemseveraltimesusingacommonvariableexpression"$variable-name".Itisgoodpracticetoprovideallimportantentitiesastestvariables.Thismakesthetesteasiertomaintainandmoreflexible.Allessentialentitiesandidentifiersarepresentrightatthebeginningofthetest,whichmayalsogivetheopportunitytoeasilycreatetestvariantsbysimplychangingthevariablevaluesforothertestscenarios.
Thenameofthevariableisarbitrary.Feelfreetospecifyanynameyoucanthinkof.OfcourseyouneedtobecarefulwithspecialcharactersandreservedXMLentitieslike'&','<','>'.IfyouarefamiliarwithJavaoranyotherprogramminglanguagesimplythinkofthenamingrulesthereandyouwillbefinewithworkingonCitrusvariables,too.Thevalueofavariablecanbeanycharactersequence.ButagainbeawareofspecialXMLcharacterslike"<"thatneedtobeescaped("<")whenusedinvariablevalues.
Theadvantageofvariablesisobvious.Oncedeclaredthevariablescanbereferencedmanytimesinthetest.Thismakesitveryeasytovarydifferenttestcasesbyadjustingthevariablesfordifferentmeans(e.g.usedifferenterrorcodesintestcases).
CitrusReferenceGuide
38Testvariables
Globalvariables
Thelastsectiontoldustousevariablesastheyareveryusefulandextendthemaintainabilityoftestcases.Nowthateverytestcasedefineslocalvariablesyoucanalsodefineglobalvariables.Theglobalvariablesarevalidinalltestsbydefault.Thisisagoodopportunitytodeclareconstantvaluesforalltests.AsthesevariablesareglobalweneedtoaddthosetothebasicSpringapplicationcontextfile.ThefollowingexampledemonstrateshowtoaddglobalvariablesinCitrus:
<citrus:global-variables><citrus:variablename="projectName"value="CitrusIntegrationTesting"/><citrus:variablename="userName"value="TestUser"/></citrus:global-variables>
WeaddtheSpringbeancomponenttotheapplicationcontextfile.Thecomponentreceivesalistofname-valuevariableelements.Youcanreferencetheglobalvariablesinyourtestcasesasusual.
Anotherpossibilitytosetglobalvariablesistoloadthosefromexternalpropertyfiles.Thismaygiveyoumorepowerfulglobalvariableswithuserspecificpropertiesforinstance.Seehowtoloadpropertyfilesasglobalvariablesinthisexample:
<citrus:global-variables><citrus:filepath="classpath:global-variable.properties"/></citrus:global-variables>
Wehavejustaddedafilepathreferencetotheglobalvariablescomponent.Citrusloadsthepropertyfilecontentasglobaltestvariables.Youcanmixpropertyfileandname-valuepairvariabledefinitionsintheglobalvariablescomponent.
NoteTheglobalvariablescanhavevariableexpressionsandCitrusfunctions.Itispossibletousepreviouslydefinedglobalvariablesasvaluesofnewvariables,likeinthisexample:
user=Citrusgreeting=Hello$user!date=citrus:currentDate('yyyy-MM-dd')
CreatevariableswithCDATA
CitrusReferenceGuide
39Testvariables
WhenusingthXMLtestcaseDSLwecannothaveXMLvariablevaluesoutofthebox.ThiswouldinterferewiththeXMLDSLelementsdefinedintheCitrustestcaseXSDschema.YoucanuseCDATAsectionswithinthevariablevalueelementinordertodothisthough.
<variables><variablename="persons"><value><data><![CDATA[<persons><person><name>Theodor</name><age>10</age></person><person><name>Alvin</name><age>9</age></person></persons>]]></data></value></variable></variables>
ThatishowyoucanuseXMLvariablevaluesintheXMLDSL.IntheJavaDSLwedonothavetheseproblems.
CreatevariableswithGroovy
Youcanalsouseascripttocreatevariablevalues.Thisisextremelyhandywhenyouhaveverycomplexvariablevalues.JustcodeasmallGroovyscriptforinstanceinordertodefinethevariablevalue.Asmallsampleshouldgiveyoutheideahowthatworks:
<variables><variablename="avg"><value><scripttype="groovy">
</script></value>
CitrusReferenceGuide
40Testvariables
</variable><variablename="sum"><value><scripttype="groovy">
</script></value></variable></variables>
Weusethescriptcoderightinsidethevariablevaluedefinition.Thevalueofthevariableistheresultofthelastoperationperformedwithinthescript.Forlongerscriptcodetheuseof<![CDATA[]]>sectionsisrecommended.
CitrususesthejavaxScriptEnginemechanisminordertoevaluatethescriptcode.BydefaultGroovyissupportedinanyCitrusproject.SoyoucanaddadditionalScriptEngineimplementationstoyourprojectandsupportotherscripttypes,too.
Escapingvariablesexpression
Thetestvariablesexpressionsyntax"$variable-name"ispreservedtoevaluatetoatestvariablewithinthecurrenttestcontext.Howeverthesamesyntaxmaybepartofamessagecontentasis.Soyouneedtosomehowescapethesyntaxfrombeeinginterpretedastestvariablesyntax.Youcandothisbyusingthevariableexpressionescaping//charactersequencewrappingtheactualvariablenamelikethis
Thisisaescapedvariableexpression$//escaped//andshouldnotleadtounknownvariableexceptionswithinCitrus.
Theescapedexpression$//escaped//abovewillresultinthestring$escapedwhereescapedisnottreatedasatestvariablenamebutasanormalstringinthemessagepayload.ThiswayyouareabletohavethesamevariablesyntaxinamessagecontentwithoutinterferingwiththeCitrusvariableexpressionsyntax.AsaresultCitruswillnotcomplainaboutnotfindingthetestvariableescapedinthecurrentcontext.Thevariablesyntaxescapingcharacters//areautomaticallyremovedwhentheexpressionisprocessedbyCitrus.Sowewillgetthefollowingresultafterprocessing.
Thisisaescapedvariableexpression$escapedandshouldnotleadtounknownvariableexceptionswithinCitrus.
CitrusReferenceGuide
41Testvariables
CitrusReferenceGuide
42Testvariables
RunningtestsCitrustestcasesarenothingbutJavaclassesthatgetexecutedwithinaJavaruntimeenvironment.EachCitrustestthereforerelatestoaJavaclassrepresentingaJUnitorTestNGunittest.AsoptionaladdonaCitrustestcanhaveaXMLtestdeclarationfile.ThisisforthoseofyouthatdonotwanttocodeinJava.InthiscasetheXMLpartholdsallactionstotellCitruswhatshouldhappeninthetestcase.TheJavapartwillthenjustberesponsiblefortestexecutionandisnotlikelytobechangedatall.InthefollowingsectionsweconcentrateontheJavapartandthetestexecutionmechanism.
IfyoucreatenewtestcasesinCitrus-forinstanceviaMavenpluginorANTbuildscript-Citrusgeneratesbothpartsinyourtestdirectory.Forexample:ifyoucreateanewtestnamedMyFirstCitrusTestyouwillfindthesetwofilesasaresult:
src/it/tests/com/consol/citrus/MyFirstCitrusTest.xmlsrc/it/java/com/consol/citrus/MyFirstCitrusTest.java
NoteIfyouprefertojustwriteJavacodeyoucanthrowawaytheXMLpartimmediatelyandcontinueworkingwiththeJavapartonly.IncaseyouarefamiliarwithwritingJavacodeyoumayjustskipthetesttemplategenerationviaMavenorANTandpreferablyjustcreatenewCitrusJavatestclassesonyourown.
Withthecreationofthistestwehavealreadymadeaveryimportantdecision.Duringcreation,Citrusasksyouwhichexecutionframeworkshouldbeusedforthistest.Therearebasicallythreeoptionsavailable:testngandjunit.
SowhyisCitrusrelatedtoUnittestsalthoughitisintendedtobeaframeworkforintegrationtesting?Theanswertothisquestionisquitesimple:ThisisbecauseCitruswantstobenefitfrombothJUnitandTestNGforJavatestexecution.BoththeJUnitandTestNGJavaAPIsoffervariouswaysofexecutionandbothframeworksarewidelysupportedbyothertools(e.g.continuousbuild,buildlifecycle,developmentIDE).
Usersmightalreadyknowoneoftheseframeworksandthechancesaregoodthattheyarefamiliarwithatleastoneofthem.EverythingyoucandowithJUnitandTestNGtestcasesyoucandowithCitrustestsalso.IncludethemintoyourMavenbuildlifecycle.ExecutetestsfromyourIDE(Eclipse,IDEAorNetBeans).Includethemintoa
CitrusReferenceGuide
43Run
continuousbuildtool(e.g.Jenkins).GeneratetestexecutionreportsandtestcoveragereportswithSonar,Coberturaandsoon.ThepossibilitieswithJUnitandTestNGareamazing.
SoletushaveacloserlookattheCitrusTestNGandJUnitintegration.
RunwithTestNG
TestNGstandsfornextgenerationtestingandhashadagreatinfluenceinaddingJavaannotationstotheunittestcommunity.CitrusisabletogenerateTestNGJavaclassesthatareexecutableastestcases.SeethefollowingstandardtemplatethatCitruswillgeneratewhenhavingnewtestcases:
packagecom.consol.citrus.samples;
importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusXmlTest;importcom.consol.citrus.testng.AbstractTestNGCitrusTest;
/***TODO:Description**@authorUnknown*/@TestpublicclassSampleITextendsAbstractTestNGCitrusTest@CitrusXmlTest(name="SampleIT")publicvoidsampleTest()
IfyouarefamiliarwithTestNGyouwillseethatthegeneratedJavaclassisnothingbutanormalTestNGtestclass.WejustextendabasicCitrusTestNGclasswhichenablestheCitrustestexecutionfeaturesforus.BesidesthatwehaveausualTestNG@Testannotationplacedonourclasssoallmethodsinsidetheclasswillbeexecutedasseparatetestcase.
ThegoodnewsisthatwecanstillusethefantasticTestNGfeaturesinourtestclass.Youcanthinkofparalleltestexecution,testgroups,setupandteardownoperationsandsoon.Justtogiveanexamplewecansimplyaddatestgrouptoourtestlikethis:
@Test(groups="long-running")
FormoreinformationonTestNGpleasevisittheofficialhomepage,whereyoufindacompletereferencedocumentation.
CitrusReferenceGuide
44Run
YoumighthavenoticedthattheexampleaboveloadstestcasesfromXML.Thisiswhyweareusingthe@CitrusXmlTestannotation.AgainthisapproachisforpeoplethatwanttowritenoJavacode.ThetestlogicisthenprovidedintheXMLtestdefinition.WediscussXMLtestsinCitrusinmoredetailinrun-xml-tests.NextletshavealookataTestNGJavaDSLtest.
WhenwritingtestsinpureJavawehaveprettymuchtheexactsamelogicthatappliestoexecutingCitrustestcases.TheCitrustestextendsfromaTestNGbaseclassandusesthenormal@Testannotationsonmethodorclasslevel.HereisashortsampleTestNGJavaclassforthis:
importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.testng.TestNGCitrusTestDesigner;
@TestpublicclassMyFirstTestDesignerextendsTestNGCitrusTestDesigner@CitrusTest(name="MyFirstIT")publicvoidmyFirstTest()description("Firstexampleshowingthebasictestcasedefinitionelements!");
variable("text","HelloTestFramework");
echo("$test");
YouseetheclassisquitesimilartotheXMLtestvariation.NowweextendaCitrustestdesignerclasswhichenablestheJavaDSLfeaturesinadditiontotheTestNGtestexecutionforus.Thebasic@TestannotationforTestNGhasnotchanged.WestillhaveausualTestNGclasswiththepossibilityofseveralmethodseachrepresentingaseparateunittest.
Nowwhathaschangedisthe@CitrusTestannotation.NowtheCitrustestlogicisplaceddirectlyasthemethodbodywithusingtheJavadomainspecificlanguagefeatures.TheXMLCitrustestpartisnotnecessaryanymore.IfyouarewonderingaboutthedesignersuperclassandtheJavaDSLmethodsforaddingthetestlogictoyourtestpleasebepatientwewilllearnmoreabouttheJavaDSLfeaturesinthisreferenceguidelateron.
UptonowwejustconcentrateontheTestNGintegrationthatisquiteeasyisn'tit.
UsingTestNGDataProviders
CitrusReferenceGuide
45Run
TestNGasaframeworkcomeswithlotsofgreatfeaturessuchasdataproviders.Dataprovidersexecuteatestcaseseveraltimes.Eachtestexecutiongetsaspecificparametervalue.WithCitrusyoucanusethosedataproviderparametersinsidethetestasvariables.SeethenextlistingonhowtouseTestNGdataprovidersinCitrus:
publicclassDataProviderITextendsAbstractTestNGCitrusTest
@CitrusXmlTest@CitrusParameters("message")@Test(dataProvider="messageDataProvider")publicvoidDataProviderIT(ITestContexttestContext)
@DataProviderpublicObject[][]messageDataProvider()returnnewObject[][]"HelloWorld!","HalloWelt!","HiCitrus!",;
AbovetestcasemethodisannotatedwithTestNGdataprovidercalledmessageDataProvider.Inthesameclassyoucanwritethedataproviderthatreturnsalistofparametervalues.TestNGwillexecutethetestcaseseveraltimesaccordingtotheprovidedparameterlist.Eachexecutionisshippedwiththerespectiveparametervalue.Accordingtothe@CitrusParameterannotationthetestwillhaveatestvariablecalledmessagethatisaccessibleasusual.
RunwithJUnit
JUnitisaverypopularunittestframeworkforJavaapplicationswidelyacceptedandwidelysupportedbymanytools.IngeneralCitrussupportsbothJUnitandTestNGastestexecutionframeworks.AlthoughtheTestNGcustomizationfeaturesareslightlymorepowerfulthanthoseofferedbyJUnityouasaCitrususershouldbeabletousetheframeworkofyourchoice.Thecompletesupportforexecutingtestcaseswithpackagescansandmultipleannotatedmethodsisgivenforbothframeworks.IfyouchoosejunitasexecutionframeworkCitrusgeneratesaJavafilethatlookslikethis:
packagecom.consol.citrus.samples;
importorg.junit.Test;
CitrusReferenceGuide
46Run
importcom.consol.citrus.annotations.CitrusXmlTest;importcom.consol.citrus.junit.AbstractJUnit4CitrusTest;
/***TODO:Description**@authorUnknown*/publicclassSampleITextendsAbstractJUnit4CitrusTest@Test@CitrusXmlTest(name="SampleIT")publicvoidsampleTest()
JUnitandTestNGasframeworksrevealslightdifferences,buttheideaisthesame.WeextendabaseJUnitCitrustestclassandhaveonetomanytestmethodsthatloadtheXMLCitrustestcasesforexecution.AsyoucanseethetestclasscanholdseveralannotatedtestmethodsthatgetexecutedasJUnittests.ThefinethinghereisthatwearestillabletouseallJUnitfeaturessuchasbefore/aftertestactionsorenable/disabletests.
TheJavaJUnitclassesaresimplyresponsibleforloadingandexecutingtheCitrustestcases.CitrustakescareonloadingtheXMLtestasafilesystemresourceandtosetuptheSpringapplicationcontext.Thetestisexecutedandsuccess/failurestateisreportedexactlylikeausualJUnitunittestwoulddo.ThisalsomeansthatyoucanexecutethisCitrusJUnitclasslikeeveryotherJUnittest,especiallyoutofanyJavaIDE,withMaven,withANTandsoon.ThismeansthatyoucaneasilyincludetheCitrustestexecutionintoyousoftwarebuildinglifecycleandcontinuousbuild.
TipSonowweknowbothTestNGandJUnitsupportinCitrus.Whichframeworkshouldsomeonechoose?Tobehonest,thereisnoeasyanswertothisquestion.Thebasicfeaturesareequivalent,butTestNGoffersbetterpossibilitiesfordesigningmorecomplextestsetupwithtestgroupsandtasksbeforeandafteragroupoftests.ThisiswhyTestNGisthedefaultoptioninCitrus.Butintheendyouhavetodecideonyourownwhichframeworkfitsbestforyourproject.
Thefirstexampleseenhereisusing@CitrusXmlTestannotationinordertoloadaXMLfileastest.TheJavapartisthenjustanemptyenvelopeforexecutingthetestwithJUnit.ThisapproachisforthoseofyouthatarenotfamiliarwithJavaatall.YoucanfindmoreinformationonloadingXMLfilesasCitrustestsinrun-xml-tests.SecondlyofcoursewealsohavethepossibilitytousetheCitrusJavaDSLwithJUnit.Seethefollowingexampleonhowthislookslike:
CitrusReferenceGuide
47Run
packagecom.consol.citrus.samples;
importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.JUnit4CitrusTestDesigner;importorg.junit.Test;
/***TODO:Description**@authorUnknown*/publicclassSampleITextendsJUnit4CitrusTestDesigner
@Test@CitrusTestpublicvoidEchoSampleIT()variable("time","citrus:currentDate()");echo("HelloCitrus!");echo("CurrentTimeis:$time");
@Test@CitrusTest(name="EchoIT")publicvoidechoTest()echo("HelloCitrus!");
TheJavaDSLtestcaselooksquitefamiliaraswealsousetheJUnit4@Testannotationinordertomarkourtestforunittestexecution.Inadditiontothatweadda@CitrusTestannotationandextendfromabasicJUnit4CitrustestdesignerwhichenablestheJavadomainspecificlanguagefeatures.TheCitrustestlogicgoesdirectlytothemethodblock.ThereisnoneedforaXMLtestfileanymore.
Asyoucanseethe@CitrusTestannotationsupportsmultipletestmethodsinonesingleclass.EachtestispreparedandexecutedseparatelyjustasyouknowitfromJUnit.YoucandefineanexplicitCitrustestnamethatisusedinCitrustestreports.Ifnoexplicittestnameisgiventhetestmethodnamewillbeusedasatestname.
IfyouneedtoknowmoredetailsaboutthetestdesignerandonhowtousetheCitrusJavaDSLjustcontinuewiththisreferenceguide.Wewilldescribethecapabilitiesindetaillateron.
RunningXMLtests
CitrusReferenceGuide
48Run
Nowwealsousethe@CitrusXmlTestannotationintheJavaclass.ThisannotationmakesCitrussearchforaXMLfilethatrepresentstheCitrustestwithinyourclasspath.LateronwewillalsodiscussanotherCitrusannotation(@CitrusTest)whichstandsfordefiningtheCitrustestjustwithJavadomainspecificlanguagefeatures.FornowwecontinuetodealwiththeXMLCitrustestexecution.
ThedefaultnamingconventionrequiresaXMLfilewiththetestsnameinthesamepackagethattheJavaclassisplacedin.InthebasicexampleabovethismeansthatCitrussearchesforaXMLtestfileincom/consol/citrus/samples/SampleIT.xml.YoutellCitrustosearchforanotherXMLfilebyusingthe@CitrusXmlTestannotationproperties.Followingannotationpropertiesarevalid:
name:Listoftestcasenamestoexecute.NamesalsodefineXMLfilenamestolookfor(.xmlfileextensionisnotneededhere).packageName:CustompackagelocationfortheXMLfilestoloadpackageScan:ListofpackagesthatareautomaticallyscannedforXMLtestfilestoexecute.ForeachXMLfilefoundseparatetestisexecuted.NotethatthisperformsaJavaClasspathpackagescansoallXMLfilesinpackageareassumedtobevalidCitrusXMLtestcases.InordertominimizetheamountofaccidentallyloadedXMLfilesthescanwillonlyloadXMLfileswith*/Test.xmland*/IT.xmlfilenamepattern.
YoucanalsomixthevariousCitrusXmlTestannotationpatternsinasingleJavaclass.SoweareabletohaveseveraltestcasesinonesingleJavaclass.EachannotatedmethodrepresentsoneormoreCitrusXMLtestcases.Sethefollowingexampletoseewhatthisisabout.
@TestpublicclassSampleITextendsAbstractTestNGCitrusTest@CitrusXmlTest(name="SampleIT")publicvoidsampleTest()
@CitrusXmlTest(name="SampleIT","AnotherIT")publicvoidmultipleTests()
@CitrusXmlTest(name="OtherIT",packageName="com.other.testpackage")publicvoidotherPackageTest()
@CitrusXmlTest(packageScan="com.some.testpackage","com.other.testpackage")publicvoidpackageScanTest()
CitrusReferenceGuide
49Run
Youarefreetocombinethesetestannotationsasyoulikeinyourclass.AsthewholeJavaclassisannotatedwiththeTestNG@Testannotationeachmethodgetsexecutedautomatically.CitruswillalsotakecareonexecutingeachXMLtestcaseasaseparateunittest.SothetestreportswillhavetheexactnumberofexecutedtestsandtheJUnit/TestNGtestreportsdohavetheexacttestoutlineforfurtherusage(e.g.incontinuousbuildreports).
NoteWhentestexecutiontakesplaceeachtestmethodannotationisevaluatedinsequence.XMLtestcasesthatmatchseveraltimes,forinstancebyexplicitnamereferenceandapackagescanwillbeexecutedseveraltimesrespectively.
Thebestthingaboutusingthe@CitrusXmlTestannotationisthatyoucancontinuetousethefabulousTestNGcapabilities(e.g.testgroups,invocationcount,threadpools,dataproviders,andsoon).
SonowwehaveseenhowtoexecuteaCitrusXMLtestwithTestNG.
CitrusReferenceGuide
50Run
ConfigurationYouhaveseveraloptionsincustomizingtheCitrusprojectconfiguration.Citrususesdefaultsettingsthatcanbeoverwrittentosomeextend.AsaframeworkCitrusinternallyworkswiththeSpringIoCcontainer.SoCitruswillstartaSpringapplicationcontextandregisterseveralcomponentsasSpringbeans.Youcancustomizethebehaviorofthesebeansandyoucanaddcustomsettingsbysettingsystemproperties.
CitrusSpringXMLapplicationcontext
CitrusstartsaSpringapplicationcontextandaddssomedefaultSpringbeancomponents.BydefaultCitruswillloadsomeinternalSpringJavaconfigclassesdefiningthosebeancomponents.Atsomepointyoumightaddsomecustombeanstothatbasicapplicationcontext.ThisiswhyCitruswillsearchforcustomSpringapplicationcontextfilesinyourproject.Theseareautomaticallyloaded.
BydefaultCitruslooksforcustomXMLSpringapplicationcontextfilesinthislocation:classpath*:citrus-context.xml.Soyoucanaddafilenamedcitrus-context.xmltoyourprojectclasspathandCitruswillloadallSpringbeansautomatically.
ThelocationofthisfilecanbecustomizedbysettingaSystempropertycitrus.spring.application.context.SoyoucancustomizetheXMLSpringapplicationcontextfilelocation.TheSystempropertyissettablewithMavensurefireandfailsafepluginforinstanceorviaJavabeforetheCitrusframeworkgetsloaded.
SeethefollowingsampleXMLconfigurationwhichisanormalSpringbeanXMLconfiguration:
<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"
<citrus:schema-repositoryid="schemaRepository"/>
</beans>
CitrusReferenceGuide
51Configuration
NowyoucanaddsomeSpringbeansandyoucanusetheCitrusXMLcomponentssuchasschema-repositoryforaddingcustombeansandcomponentstoyourCitrusproject.CitrusprovidesseveralnamespacesforcustomSpringXMLcomponents.Thesearedescribedinmoredetailintherespectivechaptersandsectionsinthisreferenceguide.
TipYoucanalsouseimportstatementsinthisSpringapplicationcontextinordertoloadotherconfigurationfiles.SoyouarefreetomodularizeyourconfigurationinseveralfilesthatgetloadedbyCitrus.
CitrusSpringJavaconfig
UsingXMLSpringapplicationcontextconfigurationisthedefaultbehaviorofCitrus.HoweversomepeoplemightpreferpureJavacodeconfiguration.YoucandothatbyaddingaSystempropertycitrus.spring.java.configwithacustomSpringJavaconfigclassasvalue.
System.setProperty("citrus.spring.java.config",MyCustomConfig.class.getName())
CitruswillloadtheSpringbeanconfigurationsinMyCustomConfig.classasJavaconfigthen.SeethefollowingexampleforcustomSpringJavaconfiguration:
importcom.consol.citrus.TestCase;importcom.consol.citrus.report.*;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;
@ConfigurationpublicclassMyCustomConfig
@Bean(name="customTestListener")publicTestListenercustomTestListener()returnnewPlusMinusTestReporter();
privatestaticclassPlusMinusTestReporterextendsAbstractTestListenerimplementsTestReporter
/**Logger*/privateLoggerlog=LoggerFactory.getLogger(CustomBeanConfig.class);
privateStringBuildertestReport=newStringBuilder();
@OverridepublicvoidonTestSuccess(TestCasetest)
CitrusReferenceGuide
52Configuration
testReport.append("+");
@OverridepublicvoidonTestFailure(TestCasetest,Throwablecause)testReport.append("-");
@OverridepublicvoidgenerateTestResults()log.info(testReport.toString());
@OverridepublicvoidclearTestResults()testReport=newStringBuilder();
YoucanalsomixXMLandJavaconfigurationsoCitruswillloadbothconfigurationtotheSpringbeanapplicationcontextonstartup.
Citrusapplicationproperties
TheCitrusframeworkreferencessomebasicSystempropertiesthatcanbeoverwritten.ThepropertiesareloadedfromJavaSystemandarealsosettableviapropertyfile.Justaddapropertyfilenamedcitrus-application.propertiestoyourprojectclasspath.Thispropertyfilecontainscustomizedsettingssuchas:
citrus.spring.application.context=classpath*:citrus-custom-context.xmlcitrus.spring.java.config=com.consol.citrus.config.MyCustomConfigcitrus.file.encoding=UTF-8citrus.default.message.type=XMLcitrus.xml.file.name.pattern=/**/*Test.xml,/**/*IT.xml
Citrusloadstheseapplicationpropertiesatstartup.AllpropertiesarealsosettablewithJavaSystemproperties.Thelocationofthecitrus-application.propertiesiscustomizablewiththeSystempropertycitrus.application.config.
System.setProperty("citrus.application.config","custom/path/to/citrus-application.properties"
CitrusReferenceGuide
53Configuration
Atthemomentyoucanusethesepropertiesforcustomization:
citrus.spring.application.context:FilelocationforSpringXMLconfigurationscitrus.spring.java.config:ClassnameforSpringJavaconfigcitrus.file.encoding:DefaultfileencodingusedinCitruswhenreadingandwritingfilecontentcitrus.default.message.type:Defaultmessagetypeforvalidatingpayloadscitrus.xml.file.name.pattern:FilenamepatternsusedforXMLtestfilepackagescan
CitrusReferenceGuide
54Configuration
EndpointsInoneofthepreviouschapterswehavediscussedthebasictestcasestructureasweintroducedvariablesandtestactions.Thesectioncontainsalistoftestactionsthattakeplaceduringthetestcase.Eachtestactionisexecutedinsequentialorderbydefault.Citrusoffersseveralbuilt-intestactionsthattheusercanchoosefromtoconstructacomplextestingworkflowwithouthavingtocodeeverythingfromscratch.InparticularCitrusaimstoprovideallthetestactionsthatyouneedaspredefinedcomponentsreadyforyoutouse.Thegoalistominimizethecodingeffortforyousoyoucanconcentrateonthetestlogicitself.
ExactlythesameapproachisusedinCitrustoprovideready-to-useendpointcomponentforconnectingtodifferentmessagetransports.Thereareseveralwaysinanenterpriseapplicationtoexchangemessageswithsomeotherapplication.WehavesynchronousinterfaceslikeHttpandSOAPWebServices.WehaveasynchronousmessagingwithJMSorfiletransferFTPinterfaces.
Citrusprovidesendpointcomponentsasclientandservertoconnectwiththesetypicalmessagetransports.SoyouasatestermustnotcareabouthowtosendamessagetoaJMSqueue.TheCitrusendpointsareconfiguredintheSpringapplicationcontextandreceiveendpointspecificpropertieslikeendpointuriorportsormessagetimeoutsasconfiguration.
ThenextfigureshowsatypicalmessagesendingendpointcomponentinCitrus:
Theendpointproducerpublishesmessagestoadestination.ThisdestinationcanbeaJMSqueue/topic,aSOAPWebServiceendpoint,aHttpURL,aFTPfolderdestinationandsoon.Theproducerjusttakesapreviouslydefinedmessagedefinition(headerandpayload)andsendsittothemessagedestination.
SimilartothatCitrusdefinestheseveralendpointconsumercomponentstoconsumemessagesfromdestinations.ThiscanbeasimplesubscriptiononmessagechannelsandJMSqueues/topics.IncaseofSOAPWebServicesandHttpGET/POSTthingsare
CitrusReferenceGuide
55Endpoints
morecomplicatedaswehavetoprovideaservercomponentthatclientscanconnectto.Wewillhandleserverrelatedcommunicationinmoredetaillateron.Fornowtheendpointconsumercomponentinitsmostsimplewayisdefinedlikethis:
ThisisallyouneedtoknowaboutCitrusendpoints.WehavementionedthattheendpointsaredefinedintheSpringapplicationcontext.Let'shaveasimpleexamplethatshowsthebasicidea:
<citrus-jms:endpointid="helloServiceEndpoint"destination-name="Citrus.HelloService.Request.Queue"connection-factory="myConnectionFacotry"/>
ThisisasimpleJMSendpointcomponentinCitrus.TheendpointXMLbeandefinitionfollowsacustomXMLnamespaceanddefinesendpointspecificpropertiesliketheJMSdestinationnameandtheJMSconnectionfactory.Theendpointidisasignificantpropertyasthetestcaseswillreferencethisendpointwhensendingandreceivingmessagesbyitsidentifier.
Inthenextsectionsyouwilllearnhowatestcaseusesthoseendpointcomponentsforproducingandconsumingmessages.
Sendmessageswithendpoints
Theactioninatestcasepublishesmessagestoadestination.Theactualmessagetransportconnectionisdefinedwiththeendpointcomponent.Thetestcasesimplydefinesthemessagecontentsandreferencesapredefinedmessageendpointcomponentbyitsidentifier.EndpointspecificconfigurationsarecentralizedintheSpringbeanapplicationcontextwhilemultipletestcasescanreferencetheendpointtoactuallypublishtheconstructedmessagetoadestination.ThereareseveralmessageendpointimplementationsinCitrusavailablerepresentingdifferenttransportprotocolslikeJMS,SOAP,HTTP,TCP/IPandmanymore.
Againthetypeoftransporttouseisnotspecifiedinsidethetestcasebutinthemessageendpointdefinition.Theseparationofconcerns(testcase/messagesendertransport)givesusagoodflexibilityofourtestcases.Thetestcasedoesnotknowanythingaboutconnectionfactories,queuenamesorendpointuri,connectiontimeoutsandsoon.The
CitrusReferenceGuide
56Endpoints
transportinternalsunderneathasendingtestactioncanchangeeasilywithoutaffectingthetestcasedefinition.WewillseelaterinthisdocumenthowtocreatedifferentmessageendpointsforvarioustransportsinCitrus.Fornowweconcentrateonconstructingthemessagecontenttobesent.
Weassumethatthemessage'spayloadwillbeplainXMLformat.CitrususesXMLasthedefaultdataformatformessagepayloaddata.ButCitrusisnotlimitedtoXMLmessageformatthough;youcanalwaysdefineothermessagedataformatssuchasJSON,plaintext,CSV.AsXMLisstillaverypopularmessageformatinenterpriseapplicationsandmessage-basedsolutionarchitectureswehavethisasadefaultformat.AnywayCitrusworksbestonXMLpayloadsandyouwillseealotofexamplecodeinthisdocumentusingXML.Finallyletushavealookatafirstexamplehowasendingactionisdefinedinthetest.
XMLDSL
<testcasename="SendMessageTest"><description>Basicsendmessageexample</description>
<actions><sendendpoint="helloServiceEndpoint"><message><payload><TestMessage><Text>Hello!</Text></TestMessage></payload></message><header><elementname="Operation"value="sayHello"/></header></send></actions></testcase>
Nowletshaveacloserlookatthesendingaction.The'endpoint'attributemightcatchyourattentionfirst.ThisattributereferencesthemessageendpointinCitrusconfigurationbyitsidentifier.Aspreviouslymentionedthemessageendpointdefinitionlivesinaseparateconfigurationfileandcontainstheactualmessagetransportsettings.Inthisexamplethe"helloServiceEndpoint"isreferencedwhichisaJMSendpointforsendingoutmessagestoaJMSqueueforinstance.
CitrusReferenceGuide
57Endpoints
Thetestcaseisnotawareofanytransportdetails,becauseitdoesnothaveto.Theadvantagesareobvious:Ontheonehandmultipletestcasescanreferencethemessageendpointdefinitionforbetterreuse.Secondlytestcasesareindependentofmessagetransportdetails.Soconnectionfactories,usercredentials,endpointurivaluesandsoonarenotpresentinthetestcase.
Inotherwordsthe"endpoint"attributeofthe<send>elementspecifieswhichmessageendpointdefinitiontouseandthereforewherethemessageshouldgoto.OnceagainallavailablemessageendpointsareconfiguredinaseparateCitrusconfigurationfile.Besuretoalwayspicktherightmessageendpointtypeinordertopublishyourmessagetotherightdestination.
IfyoudonotliketheXMLlanguageyoucanalsousepureJavacodetodefinethesametest.InJavayouwouldalsomakeuseofthemessageendpointdefinitionandreferencethisinstance.ThesametestasshownaboveinJavaDSLlookslikethis:
JavaDSLdesigner
importorg.testng.ITestContext;importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.testng.TestNGCitrusTestDesigner;
@TestpublicclassSendMessageTestDesignerextendsTestNGCitrusTestDesigner
@CitrusTest(name="SendMessageTest")publicvoidsendMessageTest()description("Basicsendmessageexample");
send("helloServiceEndpoint").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>").header("Operation","sayHello");
InsteadofusingtheXMLtagsforsendweusemethodsfromTestNGCitrusTestDesignerclass.Thesamemessageendpointisreferencedwithinthesendmessageaction.ThepayloadisconstructedasplainJavacharactersequencewhichisabitverbose.Wewillseelateronhowwecanimprovethis.Fornowitisimportanttounderstandthecombinationofsendtestactionandamessageendpoint.
CitrusReferenceGuide
58Endpoints
TipItisgoodpracticetofollownamingconventionswhendefiningnamesformessageendpoints.Theintendedpurposeofthemessageendpointaswellasthesending/receivingactorshouldbeclearwhenchoosingthename.ForinstancemessageEndpoint1,messageEndpoint2willnotgiveyoumuchhintstothepurposeofthemessageendpoint.
ThisisbasicallyhowtosendmessagesinCitrus.Thetestcaseisresponsibleforconstructingthemessagecontentwhilethepredefinedmessageendpointholdstransportspecificsettings.Testcasesreferenceendpointcomponentstopublishmessagestotheoutsideworld.Thisisjustthestartofaction.Citrussupportsawholepackageofotherwayshowtodefineandmanipulatethemessagecontents.Readmoreaboutmessagesendingactionsinactions-send.
Receivemessageswithendpoints
Nowwehavealookatthemessagereceivingpartinsidethetest.Asimpleexampleshowshowitworks.
XMLDSL
<receiveendpoint="helloServiceEndpoint"><message><payload><TestMessage><Text>Hello!</Text></TestMessage></payload></message><header><elementname="Operation"value="sayHello"/></header></receive>
Ifwerecapthesendactionofthepreviouschapterwecanidentifysomecommonmechanismsthatapplyforbothsendingandreceivingactions.Thetestactionalsousestheendpointattributeforreferencingapredefinedmessageendpoint.Thistimewewanttoreceiveamessagefromtheendpoint.AgainthetestisnotawareofthetransportdetailssuchasJMSconnections,endpointuri,andsoon.Themessageendpointcomponentencapsulatesthisinformation.
CitrusReferenceGuide
59Endpoints
BeforewegointodetailonvalidatingthereceivedmessagewehaveaquicklookattheJavaDSLvariationforthereceiveaction.ThesamereceiveactionasabovelookslikethisinJavaDSL.
JavaDSLdesigner
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>").header("Operation","sayHello");
Thereceiveactionwaitsforamessagetoarrive.Thewholetestexecutionisstoppedwhilewaitingforthemessage.Thisisimportanttoensurethestepbysteptestworkflowprocessing.Ofcourseyoucanspecifymessagetimeoutssothereceiverwillonlywaitagivenamountoftimebeforeraisingatimeouterror.Followingfromthattimeoutexceptionthetestcasefailsasthemessagedidnotarriveintime.Citrusdefinesdefaulttimeoutsettingsforallmessagereceivingtasks.
AtthispointyouknowthetwomostimportanttestactionsinCitrus.Sendingandreceivingactionswillbecomethemaincomponentsofyourintegrationtestswhendealingwithlooselycoupledmessagebasedcomponentsinaenterpriseapplicationenvironment.Itisveryeasytocreatecomplexmessageflows,meaningasequenceofsendingandreceivingactionsinyourtestcase.Youcanreplicateusecasesandtestyourmessageexchangewithextendedmessagevalidationcapabilities.Seeactions-receiveforamoredetaileddescriptiononhowtovalidateincomingmessagesandhowtoexpectmessagecontentsinatestcase.
Localmessagestore
Allmessagesthataresentandreceivedduringatestcasearestoredinalocalmemorystorage.Thisisbecausewemightwanttoaccessthemessagecontentlateroninatestcase.Wecandosobyusingmessagestorefunctionsforloadingmessagesthathavebeenexchangedearlierinthetest.WhenstoringamessageinthelocalstorageCitrususesamessagenameasidentifierkey.Thismessagenameislateronusedtoaccessthemessage.Youcandefinethemessagenameinanysendorreceiveaction:
XMLDSL
CitrusReferenceGuide
60Endpoints
<receiveendpoint="helloServiceEndpoint"><messagename="helloMessage"><payload><TestMessage><Text>Hello!</Text></TestMessage></payload></message><header><elementname="Operation"value="sayHello"/></header></receive>
JavaDSLdesigner
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").name("helloMessage").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>").header("Operation","sayHello");
ThereceiveoperationabovesetthemessagenametohelloMessage.Themessagereceivedisautomaticallystoredinthelocalstoragewiththatname.Youcanaccessthemessagecontentforinstancebyusingafunction:
<echo><message>citrus:message(helloMessage.payload())</message></echo>
ThefunctionloadsthehelloMessageandprintsthepayloadinformationwiththeechotestaction.IncombinationwithXpathorJsonPathfunctionsthismechanismisagoodwaytoaccesstheexchangedmessagecontentslaterinatestcase.
NoteThestorageisforbothsentandreceivedmessagesinatestcase.Thestorageispertestcaseandcontainsallsentandreceivedmessages.
Whennoexplicitmessagenameisgiventhelocalstoragewillconstructadefaultmessagename.Thedefaultnameisbuiltfromtheaction(sendorreceive)plustheendpointusedtoexchangethemessage.Forinstance:
CitrusReferenceGuide
61Endpoints
send(helloEndpoint)receive(helloEndpoint)
ThenamesabovewouldbegeneratedbyasendandreceiveoperationontheendpointnamedhelloEndpoint.
ImportantThemessagestoreisnotabletohandlemultiplemessageofthesamenameinonetestcase.Somessageswithidenticalnameswilloverwriteexistingmessagesinthelocalstorage.
NowwehaveseenthebasicendpointconceptinCitrus.Theendpointcomponentsrepresenttheconnectionstothetestboundarysystems.Thisishowwecanconnecttothesystemundertestformessageexchange.Andthisisourmaingoalwiththisintegrationtestframework.Wewanttoprovideeasyaccesstocommonmessagetransportsonclientandserversidesothatwecantestthecommunicationinterfacesonarealmessagetransportexchange.
CitrusReferenceGuide
62Endpoints
MessagevalidationWhenCitrusreceivesamessagefromexternalapplicationsitistimetoverifythemessagecontent.Thismessagevalidationincludessyntaxrulesaswellassemanticvaluesthatneedtobecomparedtoanexpectedbehavior.Citrusprovidespowerfulmessagevalidationcapabilities.Eachincomingmessageisvalidatedwithsyntaxandsemantics.Thetesterisabletodefineexpectedmessageheadersandpayloads.Citrusmessagevalidatorimplementationswillcomparethemessagesandreportdifferencesastestfailure.WiththeupcomingsectionswehaveacloserlookatmessagevalidationofXMLmessageswithXPathandXMLschemavalidationandfurthermessageformatslikeJSONandplaintext.
JavaDSLvalidationcallbacks
TheJavaDSLofferssomeadditionalvalidationtricksandpossibilitieswhendealingwithmessagesthataresentandreceivedoverCitrus.Oneofthemisthevalidationcallbackfunctionality.Withthisfeatureyoucanmarshal/unmarshalmessagepayloadsandcodevalidationstepsonJavaobjects.
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive(bookResponseEndpoint).validationCallback(newMarshallingValidationCallback<AddBookResponseMessage>()@Overridepublicvoidvalidate(AddBookResponseMessageresponse,MessageHeadersheaders)Assert.isTrue(response.isSuccess()););
BydefaultthevalidationcallbackneedssomeXMLunmarshallerimplementationfortransformingtheXMLpayloadtoaJavaobject.CitruswillautomaticallysearchfortheunmarshallerbeaninyourSpringapplicationcontextifnothingspecificisset.Ofcourseyoucanalsosettheunmarshallerinstanceexplicitly.
JavaDSLdesigner
CitrusReferenceGuide
63Validation
@AutowiredprivateUnmarshallerunmarshaller;
@CitrusTestpublicvoidreceiveMessageTest()receive(bookResponseEndpoint).validationCallback(newMarshallingValidationCallback<AddBookResponseMessage>(unmarshaller)@Overridepublicvoidvalidate(AddBookResponseMessageresponse,MessageHeadersheaders)Assert.isTrue(response.isSuccess()););
ObviouslyworkingonJavaobjectsismuchmorecomfortablethanusingtheXMLStringconcatenation.Thisiswhyyoucanalsousethisfeaturewhensendingmessages.
JavaDSLdesigner
@AutowiredprivateMarshallermarshaller;
@CitrusTestpublicvoidsendMessageTest()send(bookRequestEndpoint).payload(createAddBookRequestMessage("978-citrus:randomNumber(10)"),marshaller).header(SoapMessageHeaders.SOAP_ACTION,"addBook");
privateAddBookRequestMessagecreateAddBookRequestMessage(Stringisbn)AddBookRequestMessagerequestMessage=newAddBookRequestMessage();Bookbook=newBook();book.setAuthor("Foo");book.setTitle("FooTitle");book.setIsbn(isbn);book.setYear(2008);book.setRegistrationDate(Calendar.getInstance());requestMessage.setBook(book);returnrequestMessage;
TheexampleabovecreatesaAddBookRequestMessageobjectandputsthisaspayloadtoasendaction.IncombinationwithamarshallerinstanceCitrusisabletocreateaproperXMLmessagepayloadthen.
CitrusReferenceGuide
64Validation
Customizemessagevalidators
IntheprevioussectionswehavealreadyseensomeexamplesonhowtooverwritedefaultmessagevalidatorimplementationsinCitrus.BydefaultallmessagevalidatorscanbeoverwrittenbyplacingaSpringbeanofthesameidtotheSpringapplicationcontext.ThedefaultimplementationsofCitrusare:
defaultXmlMessageValidator:com.consol.citrus.validation.xml.DomXmlMessageValidatordefaultXpathMessageValidator:com.consol.citrus.validation.xml.XpathMessageValidatordefaultJsonMessageValidator:com.consol.citrus.validation.json.JsonTextMessageValidatordefaultJsonPathMessageValidator:com.consol.citrus.validation.json.JsonPathMessageValidatordefaultPlaintextMessageValidator:com.consol.citrus.validation.text.PlainTextMessageValidatordefaultMessageHeaderValidator:com.consol.citrus.validation.DefaultMessageHeaderValidatordefaultBinaryBase64MessageValidator:com.consol.citrus.validation.text.BinaryBase64MessageValidatordefaultGzipBinaryBase64MessageValidator:com.consol.citrus.validation.text.GzipBinaryBase64MessageValidatordefaultXhtmlMessageValidator:com.consol.citrus.validation.xhtml.XhtmlMessageValidatordefaultGroovyXmlMessageValidator:com.consol.citrus.validation.script.GroovyXmlMessageValidatordefaultGroovyTextMessageValidator:com.consol.citrus.validation.script.GroovyScriptMessageValidatordefaultGroovyJsonMessageValidator:com.consol.citrus.validation.script.GroovyJsonMessageValidator
Overwritingasinglemessagevalidatorwithacustomimplementationisthenveryeasy.JustaddyourcustomSpringbeantotheapplicationcontextusingoneofthesedefaultbeanidentifiers.IncaseyouwanttochangethemessagevalidatorgangbyaddingorremovingamessagevalidatorimplementationcompletelyyoucanplaceamessagevalidatorcomponentintheSpringapplicationcontext.
<citrus:message-validators><citrus:validatorref="defaultXmlMessageValidator"/>
CitrusReferenceGuide
65Validation
<citrus:validatorref="defaultXpathMessageValidator"/><citrus:validatorref="defaultGroovyXmlMessageValidator"/><citrus:validatorref="defaultPlaintextMessageValidator"/><citrus:validatorref="defaultMessageHeaderValidator"/><citrus:validatorref="defaultBinaryBase64MessageValidator"/><citrus:validatorref="defaultGzipBinaryBase64MessageValidator"/><citrus:validatorclass="com.consol.citrus.validation.custom.CustomMessageValidator"/><citrus:validatorref="defaultJsonMessageValidator"/><citrus:validatorref="defaultJsonPathMessageValidator"/><citrus:validatorref="defaultGroovyJsonMessageValidator"/><citrus:validatorref="defaultGroovyTextMessageValidator"/><citrus:validatorref="defaultXhtmlMessageValidator"/></citrus:message-validators>
ThelistingaboveaddsacustommessagevalidatorimplementationtothesequenceofmessagevalidatorsinCitrus.Wereferencedefaultmessagevalidatorsandaddaimplementationoftypecom.consol.citrus.validation.custom.CustomMessageValidator.Thecustomimplementationclasshastoimplementthebasicinterfacecom.consol.citrus.validation.MessageValidator.NowCitruswilltrytomatchthecustomimplementationtoincomingmessagetypesandoccasionallyexecutethemessagevalidatorlogic.ThisishowyoucanaddandchangethebasicmessagevalidatorregistryinCitrus.Youcanaddcustomimplementationsfornewmessageformatsveryeasy.
Thesameapproachappliesincaseyouwanttoremoveamessagevalidatorimplementationbybanningitcompletely.Justdeletetheentryinthemessagevalidatorregistrycomponent:
<citrus:message-validators><citrus:validatorref="defaultJsonMessageValidator"/><citrus:validatorref="defaultJsonPathMessageValidator"/><citrus:validatorref="defaultGroovyJsonMessageValidator"/><citrus:validatorref="defaultGroovyTextMessageValidator"/><citrus:validatorref="defaultMessageHeaderValidator"/></citrus:message-validators>
TheCitrusmessagevalidatorcomponentdeletedalldefaultimplementationsexceptofthosedealingwithJSONmessageformat.NowCitrusisonlyabletovalidateJSONmessages.BecarefulasthecompleteCitrusprojectwillbeaffectedbythischange.
CitrusReferenceGuide
66Validation
XMLmessagevalidation
XMLisaverycommonmessageformatespeciallyintheSOAPWebServicesandJMSmessagingworld.CitrusprovidesXMLmessagevalidatorimplementationsthatareabletocompareXMLmessagestructures.ThevalidatorwillnoticedifferencesintheXMLmessagestructureandsupportsXMLnamespaces,attributesandXMLschemavalidation.ThedefaultXMLmessagevalidatorimplementationisactivebydefaultandcanbeoverwrittenwithacustomimplementationusingthebeaniddefaultXmlMessageValidator.
<beanid="defaultXmlMessageValidator"class="com.consol.citrus.validation.xml.DomXmlMessageValidator"
ThedefaultXMLmessagevalidatorisverypowerfulwhenitcomestocompareXMLstructures.Thevalidatorsupportsnamespaceswithdifferentprefixesandattributesalswellasnamespacequalifiedattributes.Seethefollowingsectionsforadetaileddescriptionofallcapabilities.
XMLpayloadvalidation
OnceCitrushasreceivedamessagethetestercanvalidatethemessagecontentsinvariousways.Firstofallthetestercancomparethewholemessagepayloadtoapredefinedcontrolmessagetemplate.
Thereceivingactionoffersfollowingelementsforcontrolmessagetemplates:
<payload>:DefinesthemessagepayloadasnestedXMLmessagetemplate.Thewholemessagepayloadisdefinedinsidethetestcase.
<data>:DefinesaninlineXMLmessagetemplateasnestedCDATA.SlightlydifferenttothepayloadvariationaswedefinethewholemessagepayloadinsidethetestcaseasCDATAsection.
<resource>:DefinesanexpectedXMLmessagetemplateviaexternalfileresources.Thistimethepayloadisloadedatruntimefromtheexternalfile.
Bothwaysinlinepayloaddefinitionorexternalfileresourcegiveusacontrolmessagetemplatethatthetestcaseexpectstoarrive.Citrususesthiscontroltemplateforextendedmessagecomparison.Allelements,namespaces,attributesandnodevalues
CitrusReferenceGuide
67Xml
arevalidatedinthiscomparison.WhenusingXMLmessagepayloadsCitruswillnavigatethroughthewholeXMLstructurevalidatingeachelementanditscontent.SamewithJSONpayloads.
Onlyincasereceivedmessageandcontrolmessageareequaltoeachotherasexpectedthemessagevalidationwillpass.IncasedifferencesoccurCitrusgivesdetailederrormessagesandthetestcasefails.
Thecontrolmessagetemplateisnotnecessarilyverystatic.CitrussupportsvariouswaystoadddynamicmessagecontentontheonesideandontheothersideCitruscanignoresomeelementsthatarenotpartofmessagecomparison(e.g.whengeneratedcontentortimestampsarepartofthemessagecontent).Thetestercanenrichtheexpectedmessagetemplatewithtestvariablesorignoreexpressionssowegetamorerobustvalidationmechanism.Wewilltalkaboutthisinthenextsectionstocome.
WhenusingtheCitrusJavaDSLyouwillfaceaverbosemessagepayloaddefinition.ThisisbecauseJavadoesnotsupportmultilinecharactersequencevaluesasStrings.WehavetouseverboseStringconcatenationwhenconstructingXMLmessagepayloadcontentsforinstance.Inadditiontothatreservedcharacterslikequotesmustbeescapedandlinebreaksmustbeexplicitlyadded.AlltheseimpedimentsletmesuggesttouseexternalfileresourcesinJavaDSLwhendealingwithlargecomplexmessagepayloaddata.Hereisanexample:
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceServer").payload(newClassPathResource("com/consol/citrus/message/data/TestRequest.xml")).header("Operation","sayHello").header("MessageId","$messageId");
XMLheadervalidation
Nowthatwehavevalidatedthemessagepayloadinvariouswayswearenowinterestedinvalidatingthemessageheader.Thisissimpleasyouhavetodefinetheheadernameandthecontrolvaluethatyouexpect.Justaddthefollowingheadervalidationtoyourreceivingaction.
XMLDSL
CitrusReferenceGuide
68Xml
<header><elementname="Operation"value="GetCustomer"/><elementname="RequestTag"value="$requestTag"/></header>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceServer").header("Operation","sayHello").header("MessageId","$messageId");
Messageheadersarerepresentedasname-valuepairs.Eachexpectedheaderelementidentifiedbyitsnamehastobepresentinthereceivedmessage.Inadditiontothattheheadervalueiscomparedtothegivencontrolvalue.IfaheaderentryisnotfoundbyitsnameorthevaluedoesnotfitaccordinglyCitruswillraisevalidationerrorsandthetestcasewillfail.
NoteSometimesmessageheadersmaynotapplytothename-valuepairpattern.ForexampleSOAPheaderscanalsocontainXMLfragments.Citrussupportsthesekindofheaderstoo.PleaseseetheSOAPchapterformoredetails.###IgnoreXMLelements
Someelementsinthemessagepayloadmightnotapplyforvalidationatall.Justthinkofcommunicationtimestampsandynamicvaluesinsideamessage:
Thetimestampvalueinournextexamplewilldynamicallychangefromtestruntotestrunandishardlypredictableforthetester,soletsignoreitinvalidation.
XMLDSL
<message><payload><TestMessage><MessageId>$messageId</MessageId><Timestamp>2001-12-17T09:30:47.0Z</Timestamp><VersionId>@ignore@</VersionId></TestMessage></payload><ignorepath="/TestMessage/Timestamp"/></message>
CitrusReferenceGuide
69Xml
AlthoughwehavegivenastatictimestampvalueinthepayloaddatatheelementisignoredduringvalidationastheignoreXPathexpressionmatchestheelement.Inadditiontothatwealsoignoredtheversionidelementinthisexample.Thistimewithaninline@ignore@expression.ThisisforthoseofyouthatdonotlikeXPath.AsaresulttheignoredmessageelementsareautomaticallyskippedwhenCitruscomparesandvalidatesmessagecontentsanddonotbreakthetestcase.
WhenusingtheJavaDSLthe@ignore@placeholderaswellasXPathexpressionscanbeusedseamlessly.Hereisanexampleofthat:
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceServer").payload(newClassPathResource("com/consol/citrus/message/data/TestRequest.xml")).header("Operation","sayHello").header("MessageId","$messageId").ignore("/TestMessage/Timestamp");
Ofcourseyoucanusetheinline@ignore@placeholderinanexternalfileresource,too.
CustomizeXMLparserandserializer
WhenworkingwithXMLdataformatparsingandserializingisacommontask.XMLstructuresareparsedtoaDOM(DocumentObjectModel)representationinordertoprocesselements,attributesandtextnodes.AlsoDOMnodeobjectsgetserializedtoaStringmessagepayloadrepresentation.TheXMLparserandserializeriscustomizabletoacertainlevel.BydefaultCitrususestheDOMLevel3LoadandSaveimplementationwithfollowingsettings:
Parsersettings
cdata-sections=truesplit-cdata-sections=falsevalidate-if-schema=trueelement-content-whitespace=false
Serializersettings
format-pretty-print=truesplit-cdata-sections=false
CitrusReferenceGuide
70Xml
element-content-whitespace=true
TheparametersarealsodescribedinW3CDOMconfigurationdocumentation.WecancustomizethedefaultsettingsbyaddingaXmlConfigurerSpringbeantotheCitrusapplicationcontext.
<beanid="xmlConfigurer"class="com.consol.citrus.xml.XmlConfigurer"><propertyname="parseSettings"><map><entrykey="validate-if-schema"value="false"value-type="java.lang.Boolean"/></map></property><propertyname="serializeSettings"><map><entrykey="comments"value="false"value-type="java.lang.Boolean"/><entrykey="format-pretty-print"value="false"value-type="java.lang.Boolean"/></map></property></bean>
NoteThisconfigurationisofglobalnature.AllXMLprocessingoperationswillbeaffectedwiththisconfiguration.
GroovyXMLvalidation
WiththeGroovyXmlSlurperyoucaneasilyvalidateXMLmessagepayloadswithouthavingtodealdirectlywithXML.PeoplewhodonotwanttodealwithXPathmayalsolikethisvalidationalternative.Thetesterdirectlynavigatesthroughthemessageelementsandusessimplecodeassertionsinordertocontrolthemessagecontent.HereisanexamplehowtovalidatemessageswithGroovyscript:
XMLDSL
<receiveendpoint="helloServiceClient"timeout="5000"><message><validate><scripttype="groovy">assertroot.children().size()==4assertroot.MessageId.text()=='$messageId'assertroot.CorrelationId.text()=='$correlationId'assertroot.User.text()=='HelloService'assertroot.Text.text()=='Hello'+context.getVariable("user")</script></validate>
CitrusReferenceGuide
71Xml
</message><header><elementname="Operation"value="sayHello"/><elementname="CorrelationId"value="$correlationId"/></header></receive>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceClient").validateScript("assertroot.MessageId.text()=='$messageId';"+"assertroot.CorrelationId.text()=='$correlationId';").header("Operation,"sayHello").header("CorrelationId","$correlationId").timeout(5000L);
TheGroovyXmlSlurpervalidationscriptgoesrightintothemessage-taginsteadofaXMLcontroltemplateorXPathvalidation.TheGroovyscriptsupportsJavaassertstatementsformessageelementvalidation.Citrusautomaticallyinjectstherootelementroottothevalidationscript.ThisistheGroovyXmlSlurperobjectandthestartofelementnavigation.Basedonthisrootelementyoucanaccesschildelementsandattributeswithadotnotatedsyntax.Justusetheelementnamesseparatedbyasimpledot.Veryeasy!Ifyouneedthelistofchildelementsusethechildren()functiononanyelement.Withthetext()functionyougetaccesstotheelement'stext-value.Thesize()isveryusefulforvalidatingthenumberofchildelementswhichcompletesthebasicvalidationstatements.
Asyoucanseefromtheexample,wemayusetestvariableswithinthevalidationscript,too.Citrushasalsoinjectedtheactualtestcontexttothevalidationscript.Thetestcontextobjectholdsalltestvariables.Soyoucanalsoaccessvariableswithcontext.getVariable("user")forinstance.Onthetestcontextyoucanalsosetnewvariablevalueswithcontext.setVariable("user","newUserName").
Thereisevenmoreobjectinjectionforthevalidationscript.WiththeautomaticallyaddedobjectreceivedMessageYouhaveaccesstotheCitrusmessageobjectforthisreceiveaction.Thisenablesyoutodowhateveryouwantwiththemessagepayloadorheader.
XMLDSL
<receiveendpoint="helloServiceClient"timeout="5000">
CitrusReferenceGuide
72Xml
<message><validate><scripttype="groovy">assertreceivedMessage.getPayload(String.class).contains("HelloCitrus!")assertreceivedMessage.getHeader("Operation")=='sayHello'
context.setVariable("request_payload",receivedMessage.getPayload(String.class</script></validate></message></receive>
Thelistingaboveshowssomepowerofthevalidationscript.Wecanaccessthemessagepayload,wecanaccessthemessageheader.Withtestcontextaccesswecanalsosavethewholemessagepayloadasanewtestvariableforlaterusageinthetest.
IngeneralGroovycodeinsidetheXMLtestcasedefinitionoraspartoftheJavaDSLcodeisnotverycomfortabletomaintain.Youdonothavecodesyntaxassistorcodecompletion.Thisiswhywecanalsouseexternalfileresourcesforthevalidationscripts.Thesyntaxlookslikefollows:
XMLDSL
<receiveendpoint="helloServiceClient"timeout="5000"><message><validate><scripttype="groovy"file="classpath:validationScript.groovy"/></validate></message><header><elementname="Operation"value="sayHello"/><elementname="CorrelationId"value="$correlationId"/></header></receive>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceClient").validateScript(newFileSystemResource("validationScript.groovy")).header("Operation,"sayHello").header("CorrelationId","$correlationId").timeout(5000L);
CitrusReferenceGuide
73Xml
WereferencedsomeexternalfileresourcevalidationScript.groovy.Thisfilecontentisloadedatruntimeandisusedasscriptbody.NowthatwehaveanormalgroovyfilewecanusethecodecompletionandsyntaxhighlightingofourfavoriteGroovyeditor.
NoteYoucanusetheGroovyvalidationscriptincombinationwithothervalidationtypeslikeXMLtreecomparisonandXPathvalidation.TipForfurtherinformationontheGroovyXmlSlurperpleaseseetheofficialGroovywebsiteanddocumentation
CitrusReferenceGuide
74Xml
XMLschemavalidation
ThereareseveralpossibilitiestodescribethestructureofXMLdocuments.ThetwomostpopularwaysareDTD(Documenttypedefinition)andXSD(XMLSchemadefinition).OnceaXMLdocumenthasdecidedtobeclassifiedusingaschemadefinitionthestructureofthedocumenthastofitthepredefinedrulesinsidetheschemadefinition.XMLdocumentinstancesarevalidonlyincasetheymeetallthesestructurerulesdefinedintheschemadefinition.CurrentlyCitruscanvalidateXMLdocumentsusingtheschemalanguagesDTDandXSD.
XSDschemarepositories
CitrustriestovalidateallincomingXMLmessagesagainstaschemadefinitioninordertoensurethatallrulesarefulfilled.AsaconsequencethemessagereceivingactionsinCitrusdohavetoknowtheXMLschemadefinition(*.xsd)fileresourcesthatbelongtoourproject.ThereforeCitrusintroducesacentralschemarepositorycomponentwhichholdsallavailableXMLschemafilesforaproject.
<citrus:schema-repositoryid="schemaRepository"><citrus:schemas><citrus:schemaid="travelAgencySchema"location="classpath:citrus/flightbooking/TravelAgencySchema.xsd"/><citrus:schemaid="royalArilineSchema"location="classpath:citrus/flightbooking/RoyalAirlineSchema.xsd"/><citrus:referenceschema="smartArilineSchema"/></citrus:schemas></citrus:schema-repository>
<citrus:schemaid="smartArilineSchema"location="classpath:citrus/flightbooking/SmartAirlineSchema.xsd"/>
AsyoucanseetheschemarepositoryisasimpleXMLcomponentdefinedinsidetheSpringapplicationcontext.Therepositorycanholdnestedschemadefinitionsdefinedbysomeidentifierandafilelocationforthexsdschemafile.Schemadefinitionscanalsobereferencedbyitsidentifierforusageinseveralschemarepositoryinstances.
ByconventionthedefaultschemarepositorycomponentisdefinedintheCitrusSpringapplicationcontextwiththeidschemaRepository.Springapplicationcontextisthenabletoinjecttheschemarepositoryintoallmessagereceivingtestactionsatruntime.ThereceivingtestactionconsolidatestherepositoryforamatchingschemadefinitionfileinordertovalidatetheincomingXMLdocumentstructure.
CitrusReferenceGuide
75Schema
TheconnectionbetweenincomingXMLmessagesandxsdschemafilesintherepositoryisdonebyamappingstrategywhichwewilldiscusslaterinthischapter.BydefaultCitruspickstherightschemabasedonthetargetnamespacethatisdefinedinsidetheschemadefinition.ThetargetnamespaceoftheschemadefinitionhastomatchthenamespaceoftherootelementinthereceivedXMLmessage.WiththismappingstrategyyouwillnothavetowireXMLmessagesandschemafilesmanuallyallisdoneautomaticallybytheCitrusschemarepositoryatruntime.AllyouneedtodoistoregisterallavailableschemadefinitionfilesregardlessofwhichtargetnamespaceornatureinsidetheCitrusschemarepository.
ImportantXMlschemavalidationismandatoryinCitrus.ThismeansthatCitrusalwaystriestofindamatchingschemadefinitioninsidetheschemarepositoryinordertoperformsyntaxvalidationonincomingschemaqualifiedXMLmessages.AclassifiedXMLmessageisdefinedbyitsnamespacedefinitions.Consequentlyyouwillgetvalidationerrorsincasenomatchingschemadefinitionfileisfoundinsidetheschemarepository.SoifyouexplicitlydonotwanttovalidatetheXMLschemaforsomereasonyouhavetodisablethevalidationexplicitlyinyourtestwithschema-validation="false".
<receiveendpoint="httpMessageEndpoint"><messageschema-validation="false"><validate><xpathexpression="//ns1:TestMessage/ns1:MessageHeader/ns1:MessageId"value="$messageId"/><xpathexpression="//ns1:TestMessage/ns1:MessageHeader/ns1:CorrelationId"value="$correlationId"/><namespaceprefix="ns1"value="http://citrus.com/namespace"/></validate></message><header><elementname="Operation"value="sayHello"/><elementname="MessageId"value="$messageId"/></header></receive>
ThismandatoryschemavalidationmightsoundannoyingtoyoubutinouropinionitisveryimportanttovalidatethestructureofthereceivedXMLmessages,sodisablingtheschemavalidationshouldnotbethestandardforalltests.Disablingautomaticschemavalidationshouldonlyapplytoveryspecialsituations.Sopleasetrytoputallavailableschemadefinitionstotheschemarepositoryandyouwillbefine.
WSDLschemas
CitrusReferenceGuide
76Schema
InSOAPWebServicesworldtheWSDL(WebServiceSchemaDefinitionLanguage)definesthestructureannatureoftheXMLmessagesexchangedacrosstheinterface.OftentheWSDLfilesdoholdtheXMLschemadefinitionsasnestedelements.InCitrusyoucandirectlysettheWSDLfileaslocationofaschemadefinitionlikethis:
<citrus:schemaid="arilineWsdl"location="classpath:citrus/flightbooking/AirlineSchema.wsdl"/>
CitrusisabletofindthenestedschemadefinitionsinsidetheWSDLfileinordertobuildavalidschemafilefortheschemarepository.SoincomingXMLmessagesthatrefertotheWSDLfilecanbevalidatedforsyntaxrules.
Schemalocationpatterns
Settingallschemasonebyoneinaschemarepositorycanbeverboseanduncomfortable,especiallywhendealingwithlotsofxsdandwsdlfiles.Theschemarepositoryalsosupportslocationpatternexpressions.Seethisexampletoseehowitworks:
<citrus:schema-repositoryid="schemaRepository"><citrus:locations><citrus:locationpath="classpath:citrus/flightbooking/*.xsd"/></citrus:locations></citrus:schema-repository>
Theschemarepositorysearchesforallfilesmatchingtheresourcepathlocationpatternandaddsthemasschemainstancestotherepository.OfcoursethisalsoworkswithWSDLfiles.
Schemacollections
Sometimesmultipleaschemadefinitionisseparatedintomultiplefiles.ThisisaproblemfortheCitrusschemarepositoryastheschemamappingstrategythenisnotabletopicktherightfileforvalidation,inparticularwhenworkingwithtargetnamespacevaluesaskeyfortheschemamappingstrategy.Asasolutionforthisproblemyouhavetoputallschemaswiththesametargetnamespacevalueintoaschemacollection.
<citrus:schema-collectionid="flightbookingSchemaCollection"><citrus:schemas>
CitrusReferenceGuide
77Schema
<citrus:schemalocation="classpath:citrus/flightbooking/BaseTypes.xsd"/><citrus:schemalocation="classpath:citrus/flightbooking/AirlineSchema.xsd"/></citrus:schemas></citrus:schema-collection>
BothschemadefinitionsBaseTypes.xsdandAirlineSchema.xsdsharethesametargetnamespaceandthereforeneedtobecombinedinschemacollectioncomponent.Theschemacollectioncanbereferencedinanyschemarepositoryasnormalschemadefinition.
<citrus:schema-repositoryid="schemaRepository"><citrus:schemas><citrus:referenceschema="flightbookingSchemaCollection"/></citrus:schemas></citrus:schema-repository>
Schemamappingstrategy
TheschemarepositoryinCitrusholdsonetomanyschemadefinitionfilesanddynamicallypicksuptherightoneaccordingtothevalidatedmessagepayload.Therepositoryneedstohavesomestrategyfordecidingwhichschemadefinitiontochoose.Seethefollowingschemamappingstrategiesanddecidewhichofthemissuitableforyou.
TargetNamespaceMappingStrategy
Thisisthedefaultschemamappingstrategy.Schemadefinitionsusuallydefinesometargetnamespacewhichisvalidforallelementsandtypesinsidetheschemafile.ThetargetnamespaceisalsousedasrootnamespaceinXMLmessagepayloads.AccordingtothisinformationCitruscanpickuptherightschemadefinitionfileintheschemarepository.Youcansettheschemamappingstrategyaspropertyintheconfigurationfiles:
<citrus:schema-repositoryid="schemaRepository"schema-mapping-strategy="schemaMappingStrategy"><citrus:schemas><citrus:schemaid="helloSchema"location="classpath:citrus/samples/sayHello.xsd"/></citrus:schemas></citrus:schema-repository>
<beanid="schemaMappingStrategy"
CitrusReferenceGuide
78Schema
class="com.consol.citrus.xml.schema.TargetNamespaceSchemaMappingStrategy"/>
ThesayHello.xsdschemafiledefinesatargetnamespace(http://consol.de/schemas/sayHello.xsd):
<xs:schemaxmlns: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>
IncomingrequestmessagesshouldalsohavethetargetnamespacesetintherootelementandthisishowCitrusmatchestherightschemafileintherepository.
<HelloRequestxmlns="http://consol.de/schemas/sayHello.xsd"><MessageId>123456789</MessageId><CorrelationId>1000</CorrelationId><User>Christoph</User><Text>HelloCitrus</Text></HelloRequest>
RootQNameMappingStrategy
ThenextpossibilityformappingincomingrequestmessagestoaschemadefinitionisviatheXMLrootelementQName.EachXMLmessagepayloadstartswitharootelementthatusuallydeclaresthetypeofaXMLmessage.Accordingtothisrootelementyoucansetupmappingsintheschemarepository.
<citrus:schema-repositoryid="schemaRepository"schema-mapping-strategy="schemaMappingStrategy"><citrus:schemas><citrus:referenceschema="helloSchema"/><citrus:referenceschema="goodbyeSchema"/></citrus:schemas></citrus:schema-repository>
<beanid="schemaMappingStrategy"class="com.consol.citrus.xml.schema.RootQNameSchemaMappingStrategy"><propertyname="mappings"><map><entrykey="HelloRequest"value="helloSchema"/><entrykey="GoodbyeRequest"value="goodbyeSchema"/>
CitrusReferenceGuide
79Schema
</map></property></bean>
<citrus:schemaid="helloSchema"location="classpath:citrus/samples/sayHello.xsd"/>
<citrus:schemaid="goodbyeSchema"location="classpath:citrus/samples/sayGoodbye.xsd"/>
Thelistingabovedefinestworootqnamemappings-oneforHelloRequestandoneforGoodbyeRequestmessagetypes.Anincomingmessageoftypeisthenmappedtotherespectiveschemaandsoon.WiththisdedicatedmappingsyouareabletocontrolwhichschemaisusedonaXMLrequest,regardlessoftargetnamespacedefinitions.
Schemamappingstrategychain
Let'sdiscussthepossibilitytocombineseveralschemamappingstrategiesinalogicalchain.Youcandefinemorethanonemappingstrategythatareevaluatedinsequence.Thefirststrategytofindaproperschemadefinitionfileintherepositorywins.
<citrus:schema-repositoryid="schemaRepository"schema-mapping-strategy="schemaMappingStrategy"><citrus:schemas><citrus:referenceschema="helloSchema"/><citrus:referenceschema="goodbyeSchema"/></citrus:schemas></citrus:schema-repository>
<beanid="schemaMappingStrategy"class="com.consol.citrus.xml.schema.SchemaMappingStrategyChain"><propertyname="strategies"><list><beanclass="com.consol.citrus.xml.schema.RootQNameSchemaMappingStrategy"><propertyname="mappings"><map><entrykey="HelloRequest"value="helloSchema"/></map></property></bean><beanclass="com.consol.citrus.xml.schema.TargetNamespaceSchemaMappingStrategy"/></list></property></bean>
CitrusReferenceGuide
80Schema
SotheschemamappingchainusesbothRootQNameSchemaMappingStrategyandTargetNamespaceSchemaMappingStrategyincombination.Incasethefirstrootqnamestrategyfailstofindapropermappingthenexttargetnamespacestrategycomesinandtriestofindaproperschema.
Schemadefinitionoverruling
Nowitistimetotalkaboutschemadefinitionsettingsontestactionlevel.WehavelearnedbeforethatCitrustriestoautomaticallyfindamatchingschemadefinitioninsomeschemarepository.Therecomesatimewhereyouasatesterjusthavetopicktherightschemadefinitionbyyourself.YoucanoverruleallschemamappingstrategiesinCitrusbydirectlysettingthedesiredschemainyourreceivingmessageaction.
<receiveendpoint="httpMessageEndpoint"><messageschema="helloSchema"><validate><xpathexpression="//ns1:TestMessage/ns1:MessageHeader/ns1:MessageId"value="$messageId"/><xpathexpression="//ns1:TestMessage/ns1:MessageHeader/ns1:CorrelationId"value="$correlationId"/><namespaceprefix="ns1"value="http://citrus.com/namespace"/></validate></message></receive>
<citrus:schemaid="helloSchema"location="classpath:citrus/samples/sayHello.xsd"/>
Intheexampleabovethetesterexplicitlysetsaschemadefinitioninthereceiveaction(schema="helloSchema").Theattributevaluereferstonamedschemabeansomewhereintheapplciationcontext.Thisoverrulesallschemamappingstrategiesusedinthecentralschemarepositoryasthegivenschemaisdirectlyusedforvalidation.Thisfeatureishelpfulwhendealingwithdifferentschemaversionsatthesametimewheretheschemarepositorycannothelpyouanymore.
Anotherpossibilitywouldbetosetacustomschemarepositoryatthispoint.ThismeansyoucanhavemorethanoneschemarepositoryinyourCitrusprojectandyoupicktherightonebyyourselfinthereceiveaction.
<receiveendpoint="httpMessageEndpoint"><messageschema-repository="mySpecialSchemaRepository"><validate><xpathexpression="//ns1:TestMessage/ns1:MessageHeader/ns1:MessageId"
CitrusReferenceGuide
81Schema
value="$messageId"/><xpathexpression="//ns1:TestMessage/ns1:MessageHeader/ns1:CorrelationId"value="$correlationId"/><namespaceprefix="ns1"value="http://citrus.com/namespace"/></validate></message></receive>
Theschema-repositoryattributereferstoaCitrusschemarepositorycomponentwhichisdefinedsomewhereintheSpringapplicationcontext.
ImportantIncaseyouhaveseveralschemarepositoriesinyourprojectdoalwaysdefineadefaultrepository(name="schemaRepository").ThishelpsCitrustoalwaysfindatleastonerepositorytointeractwith.
DTDvalidation
XMLDTD(Documenttypedefinition)isanotherwaytovalidatethestructureofaXMLdocument.ManypeoplesaythatDTDisdeprecatedandXMLschemaisthemuchmoreefficientwaytodescribetherulesofaXMLstructure.Wedonotdisagreewiththat,butwealsoknowthatlegacysystemsmightstilluseDTD.SoinordertoavoidvalidationerrorswehavetodealwithDTDvalidationaswell.
FirstthingyoucandoaboutDTDvalidationistospecifyaninlineDTDinyourexpectedmessagetemplate.
<receiveendpoint="httpMessageEndpoint"><messageschema-validation="false"><data><![CDATA[<!DOCTYPEroot[<!ELEMENTroot(message)><!ELEMENTmessage(text)><!ELEMENTtext(#PCDATA)>]><root><message><text>HelloTestFramework!</text></message></root>]]><data/></message></receive>
CitrusReferenceGuide
82Schema
ThesystemundertestmayalsosendthemessagewithainlineDTDdefinition.Sovalidationwillsucceed.
InmostcasestheDTDisreferencedasexternal.dtdfileresource.Youcandothisinyourexpectedmessagetemplateaswell.
<receiveendpoint="httpMessageEndpoint"><messageschema-validation="false"><data><![CDATA[<!DOCTYPErootSYSTEM"com/consol/citrus/validation/example.dtd"><root><message><text>HelloTestFramework!</text></message></root>]]><data/></message></receive>
CitrusReferenceGuide
83Schema
JSONmessagevalidation
MessageformatssuchasJSONhavebecomeverypopular,inparticularwhenspeakingofRESTfulWebServicesandJavaScriptusingJSONasthemessageformattogofor.CitrusisabletoexpectandvalidateJSONmessagesaswewillseeinthenextsections.
ImportantBydefaultCitruswilluseXMLmessageformatswhensendingandreceivingmessages.ThisalsoreflectstothemessagevalidationlogicCitrususesforincomingmessages.SobydefaultCitruswilltrytoparsetheincomingmessageasXMLDOMelementtree.IncasewewouldliketoenableJSONmessagevalidationwehavetotellCitrusthatweexpectaJSONmessagerightnow.
Andthisisquiteeasy.CitrushasaJSONmessagevalidatorimplementationactivebydefaultandimmediatelyaswemarkanincomingmessageasJSONdatathismessagevalidatorwilljumpin.
CitrusprovidesseveraldefaultmessagevalidatorimplementationsforJOSNmessageformat:
com.consol.citrus.validation.json.JsonTextMessageValidator:BasicJSONmessagevalidatorimplementationcomparesJSONobjects(expectedandreceived).TheorderofJSONentriescandifferasspecifiedinJSONprotocol.TesterdefinesanexpectedcontrolJSONobjectwithtestvariablesandignoredentries.JSONArrayaswellasnestedJSONObjectsaresupported,too.TheJSONvalidatorofferstwodifferentmodestooperate.Bydefaultstrictmodeissetandthevalidatorwillalsochecktheexactamountofcontrolobjectfieldstomatch.NoadditionalfieldsinreceivedJSONdatastructurewillbeaccepted.InsoftmodevalidatorallowsadditionalfieldsinreceivedJSONdatastructuresothecontrolJSONobjectcanbeapartialsubsetinwhichcaseonlythecontrolfieldsarevalidated.AdditionalfieldsinthereceivedJSONdatastructureareignoredthen.
com.consol.citrus.validation.script.GroovyJsonMessageValidator:ExtendedgroovymessagevalidatorprovidesspecificJSONslurpersupport.WithJSONslurperthetestercanvalidatetheJSONmessagepayloadwithclosuresforinstance.
YoucanoverwritethisdefaultmessagevalidatorsforJSONbyplacingabeanintotheSpringApplicationcontext.Thebeanusesadefaultnameasidentifier.Thenyourcustombeanwilloverwritethedefaultvalidator:
<beanid="defaultJsonMessageValidator"class="com.consol.citrus.validation.json.JsonTextMessageValidator"
CitrusReferenceGuide
84Json
<beanid="defaultGroovyJsonMessageValidator"class="com.consol.citrus.validation.script.GroovyJsonMessageValidator"
ThisishowyoucancustomizethemessagevalidatorsusedforJSONmessagedata.
WehavementionedbeforethatCitrusisworkingwithXMLbydefault.ThisiswhywehavetotellCitrusthatthemessagethatwearereceivingusestheJSONmessageformat.WehavetotellthetestcasereceivingactionthatweexpectadifferentformatotherthanXML.
<receiveendpoint="httpMessageEndpoint"><messagetype="json"><data>"type":"read","mbean":"java.lang:type=Memory","attribute":"HeapMemoryUsage","path":"@equalsIgnoreCase('USED')@","value":"$heapUsage","timestamp":"@ignore@"</data></message></receive>
Themessagereceivingactioninourtestcasespecifiesamessageformattypetype="json".ThistellsCitrustolookforsomemessagevalidatorimplementationcapableofvalidatingJSONmessages.AswehaveaddedthepropermessagevalidatortotheSpringapplicationcontextCitruswillpicktherightvalidatorandJSONmessagevalidationisperformedonthismessage.Asyoucanseeyouwecanusetheusualtestvariablesandtheignoreelementsyntaxhere,too.CitrusisabletohandledifferentJSONelementorderswhencomparingreceivedandexpectedJSONobject.WecanalsouseJSONarraysandnestedobjects.ThedefaultJSONmessagevalidatorimplementationinCitrusisverypowerfulincomparingJSONobjects.
InsteadofdefininganexpectedmessagepayloadtemplatewecanalsouseGroovyvalidationscripts.LetshavealookattheGroovyJSONmessagevalidatorexample.AsusualthedefaultGroovyJSONmessagevalidatorisactivebydefault.ButthespecialGroovymessagevalidatorimplementationwillonlyjumpinwhenweusedavalidationscriptinourreceivemessagedefinition.Let'shaveanexampleforthat.
CitrusReferenceGuide
85Json
<receiveendpoint="httpMessageEndpoint"><messagetype="json"><validate><scripttype="groovy">
</script></validate></message></receive>
AgainwetellCitrusthatweexpectamessageoftype="json".NowweusedavalidationscriptthatiswritteninGroovy.CitruswillautomaticallyactivatethespecialmessagevalidatorthatexecutesourGroovyscript.ThescriptvalidationismorepowerfulaswecanusethefullpoweroftheGroovylanguage.ThevalidationscriptautomaticallyhasaccesstotheincomingJSONmessageobjectjson.WecanusetheGroovyJSONdotnotatedsyntaxinordertonavigatethroughtheJSONstructure.TheGroovyJSONslurperobjectjsonisautomaticallypassedtothevalidationscript.ThiswayyoucanaccesstheJSONobjectelementsinyourcodedoingsomeassertions.
Thereisevenmoreobjectinjectionforthevalidationscript.WiththeautomaticallyaddedobjectreceivedMessageYouhaveaccesstotheCitrusmessageobjectforthisreceiveaction.Thisenablesyoutodowhateveryouwantwiththemessagepayloadorheader.
XMLDSL
<receiveendpoint="httpMessageEndpoint"><messagetype="json"><validate><scripttype="groovy">assertreceivedMessage.getPayload(String.class).contains("HelloCitrus!")assertreceivedMessage.getHeader("Operation")=='sayHello'
context.setVariable("request_payload",receivedMessage.getPayload(String.class</script></validate></message></receive>
CitrusReferenceGuide
86Json
Thelistingaboveshowssomepowerofthevalidationscript.Wecanaccessthemessagepayload,wecanaccessthemessageheader.Withtestcontextaccesswecanalsosavethewholemessagepayloadasanewtestvariableforlaterusageinthetest.
IngeneralGroovycodeinsidetheXMLtestcasedefinitionoraspartoftheJavaDSLcodeisnotverycomfortabletomaintain.Youdonothavecodesyntaxassistorcodecompletion.Thisiswhywecanalsouseexternalfileresourcesforthevalidationscripts.Thesyntaxlookslikefollows:
XMLDSL
<receiveendpoint="helloServiceClient"timeout="5000"><message><validate><scripttype="groovy"file="classpath:validationScript.groovy"/></validate></message></receive>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceClient").validateScript(newFileSystemResource("validationScript.groovy"));
WereferencedsomeexternalfileresourcevalidationScript.groovy.Thisfilecontentisloadedatruntimeandisusedasscriptbody.NowthatwehaveanormalgroovyfilewecanusethecodecompletionandsyntaxhighlightingofourfavoriteGroovyeditor.
ImportantUsingseveralmessagevalidatorimplementationsatthesametimeintheSpringapplicationcontextisalsonoproblem.Citrusautomaticallysearchesforallavailablemessagevalidatorsapplicableforthegivenmessageformatandexecutesthesevalidatorsinsequence.SoseveralmessagevalidatorscancoexistinaCitrusproject.
WhenwehavemultiplemessagevalidatorsthatapplytothemessageformatCitruswillexecutealloftheminsequence.Incaseyouneedtoexplicitlychooseamessagevalidatorimplementationyoucandosointhereceiveaction:
<receiveendpoint="httpMessageEndpoint"><messagetype="json"validator="groovyJsonMessageValidator">
CitrusReferenceGuide
87Json
<validate><scripttype="groovy">
</script></validate></message></receive>
InthisexampleweusethegroovyJsonMessageValidatorexplicitlyinthereceivetestaction.ThemessagevalidatorimplementationwasaddedasSpringbeanwithidgroovyJsonMessageValidatortotheSpringapplicationcontextbefore.NowCitruswillonlyexecutetheexplicitmessagevalidator.Otherimplementationsthatmightalsoapplyareskipped.
TipBydefaultCitruswillconsolidateallavailablemessagevalidatorsforamessageformatinsequence.Youcanexplicitlypickaspecialmessagevalidatorinthereceivemessageactionasshownintheexampleabove.Inthiscaseallothervalidatorswillnottakepartinthisspecialmessagevalidation.Butbecareful:Whenpickingamessagevalidatorexplicitlyyouareofcourselimitedtothismessagevalidatorcapabilities.Validationfeaturesofothervalidatorsarenotvalidinthiscase(e.g.messageheadervalidation,XPathvalidation,etc.)
SomuchforreceivingJSONmessagedatainCitrus.OfcoursesendingJSONmessagesinCitrusisalsoveryeasy.JustuseJSONmessagepayloadsinyoursendingmessageaction.
<sendendpoint="httpMessageEndpoint"><message><data>"type":"read","mbean":"java.lang:type=Memory","attribute":"HeapMemoryUsage","path":"used"</data></message></send>
CitrusReferenceGuide
88Json
CitrusReferenceGuide
89Json
XHTMLmessagevalidation
WhenCitrusreceivesplainHtmlmessageswelikelywanttousethepowerfulXMLvalidationcapabilitiessuchasXMLtreecomparisonorXPathsupport.UnfortunatelyHtmlmessagesdonotfollowtheXMLwellformedrulesverystrictly.ThisimpliesthatXMLmessagevalidationwillfailbecauseofnonwellformedHtmlcode.
XHTMLclosesthisgapbyautomaticallyfixingthemostcommonHtmlXMLincompatibleruleviolationssuchasmissingendtags(e.g.).
Let'strythiswithasimpleexample.Veryfirstthingforustodoistoaddanewlibrarydependencytotheproject.CitrusisusingthejtidylibraryinordertopreparetheHTMLandXHTMLmessagesforvalidation.Asthis3rdpartydependencyisoptionalinCitruswehavetoadditnowtoourprojectdependencylist.JustaddthejtidydependencytoyourMavenprojectPOM.
<dependency><groupId>net.sf.jtidy</groupId><artifactId>jtidy</artifactId><version>r938</version></dependency>
Pleaserefertothejtidyprojectdocumentationforthelatestversions.Noweverythingisready.AsusualtheCitrusmessagevalidatorforXHTMLisactiveinbackgroundbydefault.YoucanoverwritethisdefaultimplementationbyplacingaSpringbeanwithiddefaultXhtmlMessageValidatortotheCitrusapplicationcontext.
<beanid="defaultXhtmlMessageValidator"class="com.consol.citrus.validation.xhtml.XhtmlMessageValidator"
NowwecantellthetestcasereceivingactionthatwewanttousetheXHTMLmessagevalidationinourtestcase.
<receiveendpoint="httpMessageEndpoint"><messagetype="xhtml"><data><![CDATA[<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.1//EN""org/w3c/xhtml/xhtml1-strict.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><head>
CitrusReferenceGuide
90Xhtml
<title>CitrusHelloWorld</title></head><body><h1>HelloWorld!</h1><br/><p>Thisisatest!</p></body>]]></data></message></receive>
Themessagereceivingactioninourtestcasehastospecifyamessageformattypetype="xhtml".AsyoucanseetheHtmlmessagepayloadgetXHTMLspecificDOCTYPEprocessinginstruction.Thexhtml1-strict.dtdismandatoryintheXHTMLmessagevalidation.ForbetterconvenienceallXHTMLdtdfilesarepackagedwithinCitrussoyoucanusethisasarelativepath.
TheincomingHtmlmessageisautomaticallyconvertedintoproperXHTMLcodewithwellformedXML.SonowtheXHTMLmessagevalidatorcanusetheXMLmessagevalidationmechanismofCitrusforcomparingreceivedandexpecteddata.Asusualyoucanusetestvariables,ignoreelementexpressionsandXPathexpressions.
CitrusReferenceGuide
91Xhtml
Plaintextmessagevalidation
PlaintextmessagevalidationistheeasiestvalidationinCitrusthatyoucanthinkof.ThisvalidationjustperformsanexactJavaStringmatchofreceivedandexpectedmessagepayloads.
Asusualadefaultmessagevalidatorforplaintextmessagesisactivebydefault.Citruswillpickthismessagevalidatorforallmessagesoftype="plaintext".ThedefaultmessagevalidatorimplementationcanbeoverwrittenbyplacingaSpringbeanwithiddefaultPlaintextMessageValidatortotheSpringapplicationcontext.
<beanid="defaultPlaintextMessageValidator"class="com.consol.citrus.validation.text.PlainTextMessageValidator"
InthetestcasereceivingactionwetellCitrustouseplaintextmessagevalidation.
<receiveendpoint="httpMessageEndpoint"><messagetype="plaintext"><data>HelloWorld!</data></message></receive>
Withthemessageformattypetype="plaintext"setCitrusperformsStringequalsonthemessagepayloads(receivedandexpected).Onlyexactmatchwillpassthetest.
BythewaysendingplaintextmessagesinCitrusisalsoveryeasy.Justusetheplaintextmessagepayloaddatainyoursendingmessageaction.
<sendendpoint="httpMessageEndpoint"><message><data>HelloWorld!</data></message></send>
Ofcoursetestvariablesaresupportedintheplaintextpayloads.Thevariablesarereplacebythereferencedvaluesbeforesendingorreceivingthemessage.
Plaintextmessagepayloadsmayonlydifferinwhitespacese.g.newlinecharacters.Bydefaultthemessagevalidationfailsevenifonlywhitespacecharactersaredifferent.
CitrusReferenceGuide
92Plaintext
Youcandisablethisdefaultbehaviorandignorewhitespacescharacterslikenewlineswithfollowingsystemproperty:
citrus.plaintext.validation.ignore.whitespace=true
Alsoyoucouldsetthepropertydirectlyonthemesagevalidatorbean:
<beanid="defaultPlaintextMessageValidator"class="com.consol.citrus.validation.text.PlainTextMessageValidator"<propertyname="ignoreWhitespace"value="true"/></bean>
CitrusReferenceGuide
93Plaintext
Binarymessagevalidation
Binarymessagevalidationisnotveryeasytodoespeciallywhenitcomestocomparedatawithagivencontrolmessage.Asatesteryouwanttovalidatethebinarycontent.InCitrusthewaytocomparebinarymessagecontentistousebase64Stringencoding.Thebinarydataisencodedasbase64charactersequenceandthereforeiscomparablewithanexpectedcontent.
Thereceivedmessagecontentdoesnothavetobebase64encoded.Citrusisdoingthisconversionautomaticallybeforevalidationtakesplace.Thebinarydatacanbeanythinge.g.images,pdforgzipcontent.
Thedefaultmessagevalidatorforbinarymessagesisactivebydefault.Citruswillpickthismessagevalidatorforallmessagesoftype="binary_base64".ThedefaultmessagevalidatorimplementationcanbeoverwrittenbyplacingaSpringbeanwithiddefaultBinaryBase64MessageValidatortotheSpringapplicationcontext.
<beanid="defaultBinaryBase64MessageValidator"class="com.consol.citrus.validation.text.BinaryBase64MessageValidator"
InthetestcasereceivingactionwetellCitrustousebinarybase64messagevalidation.
<receiveendpoint="httpMessageEndpoint"><messagetype="binary_base64"><data>citrus:encodeBase64('HelloWorld!')</data></message></receive>
Withthemessageformattypetype="binary_base64"Citrusperformsthebase64charactersequencevalidation.Incomingmessagecontentisautomaticallyencodedasbase64Stringandcomparedtotheexpecteddata.Thiswaywecanmakesurethatthebinarycontentisasexpected.
BythewaysendingbinarymessagesinCitrusisalsoveryeasy.Justusethetype="binary"messagetypeinthesendoperation.Citrusnowconvertsthemessagepayloadtoabinarystreamaspayload.
<sendendpoint="httpMessageEndpoint"><messagetype="binary"><data>HelloWorld!</data></message>
CitrusReferenceGuide
94Binary
</send>
Base64encodingisalsosupportedinoutboundmessages.JustusetheencodeBase64functioninCitrus.Theresultisabase64encodedStringasmessagepayload.
<sendendpoint="httpMessageEndpoint"><message><data>citrus:encodeBase64('HelloWorld!')</data></message></send>
CitrusReferenceGuide
95Binary
Gzipmessagevalidation
Gzipisafamousmessagecompressionlibrary.Whendealingwithlargemessagecontentthecompressionmightbeagoodwaytooptimizethemessagetransportation.Citrusisabletohandlegzippedmessagepayloadsonsendandreceiveoperations.Whensendingcompresseddatawejusthavetousethemessagetypegzip.
<sendendpoint="messageEndpoint"><messagetype="gzip"><data>HelloWorld!</data></message></send>
Justusethetype="gzip"messagetypeinthesendoperation.Citrusnowconvertsthemessagepayloadtoagzipbinarystreamaspayload.
Whenvalidatinggzipbinarymessagecontentthemessagesarecomparedwithagivencontrolmessageinbinarybase64Stringrepresentation.Thegzipbinarydataisautomaticallyunzippedandencodedasbase64charactersequenceinordertocomparewithanexpectedcontent.
Thereceivedmessagecontentisusinggzipformatbuttheactualmessagecontentdoesnothavetobebase64encoded.Citrusisdoingthisconversionautomaticallybeforevalidationtakesplace.Thebinarydatacanbeanythinge.g.images,pdforplaintextcontent.
Thedefaultmessagevalidatorforgzipmessagesisactivebydefault.Citruswillpickthismessagevalidatorforallmessagesoftype="gzip_base64".ThedefaultmessagevalidatorimplementationcanbeoverwrittenbyplacingaSpringbeanwithiddefaultGzipBinaryBase64MessageValidatortotheSpringapplicationcontext.
<beanid="defaultGzipBinaryBase64MessageValidator"class="com.consol.citrus.validation.text.GzipBinaryBase64MessageValidator"
InthetestcasereceivingactionwetellCitrustousegzipmessagevalidation.
<receiveendpoint="messageEndpoint"><messagetype="gzip_base64"><data>citrus:encodeBase64('HelloWorld!')</data></message></receive>
CitrusReferenceGuide
96Gzip
Withthemessageformattypetype="gzip_base64"Citrusperformsthegzipbase64charactersequencevalidation.Incomingmessagecontentisautomaticallyunzippedandencodedasbase64Stringandcomparedtotheexpecteddata.Thiswaywecanmakesurethatthebinarycontentisasexpected.
NoteIfyouareusinghttpclientandservercomponentsthegzipcompressionsupportisbuiltinwiththeunderlyingSpringandhttpcommonslibraries.SoinhttpcommunicationyoujusthavetosettheheaderAccept-Encoding=gziporContent-Encoding=gzip.Themessagedataisthenautomaticallyzipped/unzippedbeforeCitrusgetsthemessagedataforvalidation.Readmoreaboutthishttpspecificgzipcompressioninchapterhttp.
CitrusReferenceGuide
97Gzip
UsingXPathSometimeagointhisdocumentwehavealreadyseenhowXMLmessagepayloadsareconstructedwhensendingandreceivingmessages.NowusingXPathisaverypowerfulwayofaccessingelementsincomplexXMLstructures.TheXPathexpressionlanguageisveryhandywhenitcomestosaveelementvaluesastestvariablesorwhenvalidatingspecialelementsinaXMLmessagestructure.
XPathisaverypowerfultechnologyforwalkingXMLtrees.ThisW3CstandardstandsforadvancedXMLtreehandlingusingaspecialsyntaxasquerylanguage.CitrussupportstheXPathsyntaxinthefollowingfields:
<message><elementpath="[XPath-Expression]"></message><validate><xpathexpression="[XPath-Expression]"/></validate><extract><messagepath="[XPath-Expression]"></extract><ignorepath="[XPath-Expression]"/>
ThenextprogramlistingindicatesthepowerinusingXPathwithCitrus:
<message><validate><xpathexpression="//User/Name"value="John"/><xpathexpression="//User/Address[@type='office']/Street"value="Companystreet21"/><xpathexpression="//User/Name"value="$userName"/><xpathexpression="//User/@isAdmin"value="$isAdmin"/><xpathexpression="//User/@isAdmin"value="true"result-type="boolean"/><xpathexpression="//*[.='search-for']"value="searched-for"/><xpathexpression="count(//orderStatus[.='success'])"value="3"result-type="number"/></validate></message>
NowwedescribetheXPathusageinCitrusstepbystep.
ManipulatewithXPath
SomeelementsinXMLmessagepayloadsmightbeofdynamicnature.Justthinkofgeneratedidentifiersortimestamps.Alsowedonotwanttorepeatthesamestaticidentifierseveraltimesinourtestcases.Thisisthetimewheretestvariablesanddynamicmessageelementoverwritecomeinhandy.Theideaissimple.Wewantto
CitrusReferenceGuide
98Xpath
overwriteaspecificmessageelementinourpayloadwithadynamicvalue.ThiscanbedonewithXPathorinlinevariabledeclarations.Letshavealookatanexamplelistingshowingbothways:
XMLDSL
<message><payload><TestMessage><MessageId>$messageId</MessageId><CreatedBy>_</CreatedBy><VersionId>$version</VersionId></TestMessage></payload><elementpath="/TestMessage/CreatedBy"value="$user"/></message>
Theprogramlistingaboveshowswaysofsettingvariablevaluesinsideamessagetemplate.Firstofallyoucansimplyplacevariableexpressionsinsidethemessage(seehow$messageIdisused).InadditiontothatyoucanalsouseXPathexpressionstoexplicitlyoverwritemessageelementsbeforevalidation.
<elementpath="/TestMessage/CreatedBy"value="$user"/>
TheXPathexpressionevaluatesandsearchesfortherightelementinthemessagepayload.Thepreviouslydefinedvariable$userreplacestheelementvalue.OfcoursethisworkswithXMLattributestoo.
BothwaysviaXPathorinlinevariableexpressionsareequaltoeachother.WithrespecttothecomplexityofXMLnamespacesandXPathyoumayfindtheinlinevariableexpressionmorecomfortabletouse.Anywayfeelfreetochoosethewaythatfitsbestforyou.Thisishowwecanadddynamicvariablevaluestothecontroltemplateinordertoincreasemaintainabilityandrobustnessofmessagevalidation.
TipValidationmatchersputvalidationmechanismstoanewlevelofferingdynamicassertionstatementsforvalidation.Havealookatthepossibilitieswithassertionstatementsinvalidation-matchers###ValidatewithXPath
WehavealreadyseenhowtovalidatewholeXMLstructureswithcontrolmessagetemplates.Allelementsarevalidatedandcomparedoneafteranother.Insomecasesthisapproachmightbetooextensive.Imaginethetesteronlyneedstovalidateasmall
CitrusReferenceGuide
99Xpath
subsetofmessageelements.Thedefinitionofcontroltemplatesincombinationwithseveralignorestatementsisnotappropriateinthiscase.Youwouldratherwanttouseexplicitelementvalidation.
XMLDSL
<message><validate><xpathexpression="/TestRequest/MessageId"value="$messageId"/><xpathexpression="/TestRequest/VersionId"value="2"/></validate></message>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceServer").validate("/TestRequest/MessageId","$messageId").validate("//VersionId","2").header("Operation","sayHello");
InsteadofcomparingthewholemessagesomemessageelementsarevalidatedexplicitlyviaXPath.CitrusevaluatestheXPathexpressiononthereceivedmessageandcomparestheresultvaluetothecontrolvalue.Thebasicmessagestructureaswellasallothermessageelementsarenotincludedintothisexplicitvalidation.
NoteIfthistypeofelementvalidationischosenneithernornortemplatedefinitionsareallowedinCitrusXMLtestcases.
TipCitrusoffersanalternativedot-notatedsyntaxinordertowalkthroughXMLtrees.IncaseyouarenotfamiliarwithXPathorsimplyneedaveryeasywaytofindyourelementinsidetheXMLtreeyoumightusethisway.EveryelementhierarchyintheXMLtreeisrepresentedwithasimpledot-forexample:
TestRequest.VersionId
TheexpressionwillsearchtheXMLtreefortherespectiveelement.Attributesaresupportedtoo.Incasethelastelementinthedot-notatedexpressionisaXMLattributetheframeworkwillautomaticallyfindit.
CitrusReferenceGuide
100Xpath
Ofcoursethisdot-notatedsyntaxisverysimpleandmightnotbeapplicableformorecomplextreenavigation.XPathismuchmorepowerful-nodoubt.Howeverthedot-notatedsyntaxmighthelpthoseofyouthatarenotfamiliarwithXPath.Sothedot-notationissupportedwhereverXPathexpressionsmightapply.
TheXpathexpressionscanevaluatetodifferentresulttypes.BydefaultCitrusisoperatingonNODEandSTRINGresulttypessothatyoucanvalidatesomeelementvalue.ButyoucanalsousedifferentresulttypessuchasNODESETandBOOLEAN.Seethisexamplehowthatworks:
XMLDSL
<message><validate><xpathexpression="/TestRequest/Error"value="false"result-type="boolean"/><xpathexpression="/TestRequest/Status[.='success']"value="3"result-type="number"/><xpathexpression="/TestRequest/OrderType"value="[single,multi,multi]"result-type="node-set"</validate></message>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceServer").validate("boolean:/TestRequest/Error",false).validate("number:/TestRequest/Status[.='success']",3).validate("node-set:/TestRequest/OrderType","[single,multi,multi]").header("Operation","sayHello");
Intheexampleaboveweusedifferentexpressionresulttypes.Firstwewanttomakesurenor/TestRequest/Errorelementispresent.Thiscanbedonewithabooleanresulttypeandfalsevalue.Secondwewanttovalidatethenumberoffoundelementsfortheexpression/TestRequest/Status[.='success'].TheXPathexpressionevaluatestoanodelistthatresultsinitslistsizetobechecked.Andlastnotleastweevaluatetoanode-setresulttypewhereallvaluesinthenodelistwillbetranslatedtoacommadelimitedstringvalue.
Nowletshavealookatsomemorepowerfulvalidationexpressionsusingmatcherimplementations.UptonowwehaveseenthatXPathexpressionresultsarecomparablewithequalTooperations.Wewouldliketoaddsomemorepowerful
CitrusReferenceGuide
101Xpath
validationsuchasgreaterThan,lessThan,hasSizeandmuchmore.ThereforewehaveintroducedHamcrestvalidationmatchersupportinCitrus.Hamcrestisaverypowefulmatcherlibrarythatprovidesafantasticsetofmatcherimplementations.Letsseehowwecanaddtheseinourtestcase:
XMLDSL
<message><validate><xpathexpression="/TestRequest/Error"value="@assertThat(anyOf(empty(),nullValue()))@"/><xpathexpression="/TestRequest/Status[.='success']"value="@assertThat(greaterThan(0))@"<xpathexpression="/TestRequest/OrderType"value="@assertThat(hasSize(3))@"result-type="node-set"</validate></message>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceServer").validate("/TestRequest/Error",anyOf(empty(),nullValue())).validate("number:/TestRequest/Status[.='success']",greaterThan(0)).validate("node-set:/TestRequest/OrderType",hasSize(3)).header("Operation","sayHello");
WhenusingtheXMLDSLwehavetousetheassertThatvalidationmatchersyntaxfordefiningtheHamcrestmatchers.YoucancombinematcherimplementationasseenintheanyOf(empty(),nullValue())expression.WhenusingtheJavaDSLyoucanjustaddthematcherasexpectedresultobject.Citrusevaluatesthematchersandmakessureeverythingisasexpected.Thisisaverypowerfulvalidationmechanismasitalsoworkswithnode-setscontainingmultiplevaluesaslist.
ThisishowyoucanaddverypowerfulmessageelementvalidationinXMLusingXPathexpressions.
ExtractvariableswithXPath
Imagineyoureceiveamessageinyourtestwithsomegeneratedmessageidentifiervalues.Youhavenochancetopredicttheidentifiervaluebecauseitwasgeneratedatruntimebyaforeignapplication.Youcanignorethevalueinordertoprotectyourvalidation.Butinmanycasesyoumightneedtoreturnthisidentifierintherespective
CitrusReferenceGuide
102Xpath
responsemessageorsomewhatlateroninthetest.Sowehavetosavethedynamicmessagecontentforreuseinlaterteststeps.Thesolutionissimpleandverypowerful.Wecanextractdynamicvaluesfromreceivedmessagesandsavethosetotestvariables.Addthiscodetoyourmessagereceivingaction.
XMLDSL
<extract><headername="Operation"variable="operation"/><messagepath="/TestRequest/VersionId"variable="versionId"/></extract>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("helloServiceServer").extractFromHeader("Operation","operation").extractFromPayload("//TestRequest/VersionId","versionId");
echo("Extractedoperationfromheaderis:$operation");echo("Extractedversionfrompayloadis:$versionId");
AsyoucanseeCitrusisabletoextractbothheaderandmessagepayloadcontentintotestvariables.Itdoesnotmatterifyouusenewtestvariablesorexistingvariablesastarget.Theextractionwillautomaticallycreateanewvariableincaseitdoesnotexist.Thetimethevariablewascreatedallfollowingtestactionscanaccessthetestvariablesasusual.Soyoucanreferencethevariablevaluesinresponsemessagesorotherteststepsahead.
TipWecanalsouseexpressionresulttypesinordertomanipulatethetestvariableoutcome.Incaseweuseabooleanresulttypetheexistenceofelementscanbesavedtovariablevalues.Theresulttypenode-settranslatesanodelistresulttoacommaseparatedstringofallvaluesinthisnodelist.Simplyusetheexpressionresulttypeattributesasshowninprevioussections.
XMLnamespacesinXPath
WhenitcomestoXMLnamespacesyouhavetobecarefulwithyourXPathexpressions.LetshavealookatanexamplemessagethatusesXMLnamespaces:
CitrusReferenceGuide
103Xpath
<ns1:TestMessagexmlns:ns1="http://citrus.com/namespace"><ns1:TestHeader><ns1:CorrelationId>_</ns1:CorrelationId><ns1:Timestamp>2001-12-17T09:30:47.0Z</ns1:Timestamp><ns1:VersionId>2</ns1:VersionId></ns1:TestHeader><ns1:TestBody><ns1:Customer><ns1:Id>1</ns1:Id></ns1:Customer></ns1:TestBody></ns1:TestMessage>
NowwewouldliketovalidatesomeelementsinthismessageusingXPath
<message><validate><xpathexpression="//TestMessage/TestHeader/VersionId"value="2"/><xpathexpression="//TestMessage/TestHeader/CorrelationId"value="$correlationId"/></validate></message>
ThevalidationwillfailalthoughtheXPathexpressionlookscorrectregardingtheXMLtree.Becausethemessageusesthenamespacexmlns:ns1="http://citrus.com/namespace"withitsprefixns1ourXPathexpressionisnotabletofindtheelements.ThecorrectXPathexpressionusesthenamespaceprefixasdefinedinthemessage.
<message><validate><xpathexpression="//ns1:TestMessage/ns1:TestHeader/ns1:VersionId"value="2"/><xpathexpression="//ns1:TestMessage/ns1:TestHeader/ns1:CorrelationId"value="$correlationId"</message>
Nowtheexpressionsworkfineandthevalidationissuccessful.Butthisisquiteerrorprone.Thisisbecausethetestisnowdependingonthenamespaceprefixthatisusedbysomeapplication.Assoonasthemessageissentwithadifferentnamespaceprefix(e.g.ns2)thevalidationwillfailagain.
Youcanavoidthiseffectwhenspecifyingyourownnamespacecontextandyourownnamespaceprefixduringvalidation.
CitrusReferenceGuide
104Xpath
<message><validate><xpathexpression="//pfx:TestMessage/pfx:TestHeader/pfx:VersionId"value="2"/><xpathexpression="//pfx:TestMessage/pfx:TestHeader/pfx:CorrelationId"value="$correlationId"<namespaceprefix="pfx"value="http://citrus.com/namespace"/></validate></message>
Nowthetestinindependentfromanynamespaceprefixinthereceivedmessage.Thenamespacecontextwillresolvethenamespacesandfindtheelementsalthoughthemessagemightusedifferentprefixes.Theonlythingthatmattersisthatthenamespacevalue(http://citrus.com/namespace)matches.
TipInsteadofthisnamespacecontextonvalidationlevelyoucanalsohaveaglobalnamespacecontextwhichisvalidinalltestcases.WejustaddabeaninthebasicSpringapplicationcontextconfigurationwhichdefinesglobalnamespacemappings.
<namespace-context><namespaceprefix="def"uri="http://www.consol.de/samples/sayHello"/></namespace-context>
OncedefinedthedefnamespaceprefixisvalidinalltestcasesandallXPathexpressions.Thisenablesyoutofreeyourtestcasesfromnamespaceprefixbindingsthatmightbebrokenwithtime.YoucanusetheseglobalnamespacemappingswhereverXPathexpressionsarevalidinsideatestcase(validation,ignore,extract).
DefaultnamespacesinXPath
IntheprevioussectionwehaveseenthatXMLnamespacescangettrickywithXPathvalidation.Defaultnamespacescandoevenmore!Soletslookattheexamplewithdefaultnamespaces:
<TestMessagexmlns="http://citrus.com/namespace"><TestHeader><CorrelationId>_</CorrelationId><Timestamp>2001-12-17T09:30:47.0Z</Timestamp><VersionId>2</VersionId></TestHeader><TestBody><Customer><Id>1</Id></Customer>
CitrusReferenceGuide
105Xpath
</TestBody></TestMessage>
Themessageusesdefaultnamespaces.ThefollowingapproachinXPathwillfailduetonamespaceproblems.
<message><validate><xpathexpression="//TestMessage/TestHeader/VersionId"value="2"/><xpathexpression="//TestMessage/TestHeader/CorrelationId"value="$correlationId"/></validate></message>
EvendefaultnamespacesneedtobespecifiedintheXPathexpressions.Lookatthefollowingcodelistingthatworksfinewithdefaultnamespaces:
<message><validate><xpathexpression="//:TestMessage/:TestHeader/:VersionId"value="2"/><xpathexpression="//:TestMessage/:TestHeader/:CorrelationId"value="$correlationId"/></validate></message>
TipItisrecommendedtousethenamespacecontextasdescribedinthepreviouschapterwhenvalidating.Onlythisapproachensuresflexibilityandstabletestcasesregardingnamespacechanges.
CitrusReferenceGuide
106Xpath
UsingJSONPathJSONPathistheJSONequivalenttoXPathintheXMLmessageworld.WithJSONPathexpressionsyoucanqueryandmanipulateentriesofaJSONmessagestructure.TheJSONPathexpressionsevaluateagainstaJSONmessagewheretheJSONobjectstructureisrepresentedinadotnotatedsyntax.
YouwillseethatJSONPathisaverypowerfultechnologywhenitcomestofindobjectentriesinacomplexJSONhierarchystructure.AlsoJSONPathcanhelptodomessagemanipulationsbeforeamessageissentoutforinstance.CitrussupportsJSONPathexpressionsinvariousscenarios:
<message><elementpath="[JSONPath-Expression]"></message><validate><json-pathexpression="[JSONPath-Expression]"/></validate><extract><messagepath="[JSONPath-Expression]"></extract><ignorepath="[JSONPath-Expression]"/>
ManipulatewithJSONPath
FirstthingwewanttodowithJSONPathistomanipulateamessagecontentbeforeitisactuallysentout.Thisisveryusefulwhenworkingwithmessagefileresourcesthatarereusedaccrossmultipletestcases.EachtestcasecanmanipulatethemessagecontentindividuallywithJSONPathbeforesending.Letshavealookatthissimplesample:
<messagetype="json"><resourcefile="file:path/to/user.json"/><elementpath="$.user.name"value="Admin"/><elementpath="$.user.admin"value="true"/><elementpath="$..status"value="closed"/></message>
Weuseabasicmessagecontentfilethatiscalleduser.json.ThecontentofthefileisfollowingJSONdatastructure:
user:"id":citrus:randomNumber(10)"name":"Unknown","admin":"?","projects":
CitrusReferenceGuide
107JsonPath
["name":"Project1","status":"open","name":"Project2","status":"open","name":"Project3","status":"closed"]
Citrusloadsthefilecontentanduseditasmessagepayload.BeforethemessageissentouttheJSONPathexpressionshavethechancetomanipulatethemessagecontent.AllJSONPathexpressionsareevaluatedandthegivevaluesoverwriteexistingvaluesaccordingly.Theresultingmessagelookslikefollows:
user:"id":citrus:randomNumber(10)"name":"Admin","admin":"true","projects":["name":"Project1","status":"closed","name":"Project2","status":"closed","name":"Project3","status":"closed"]
TheJSONPathexpressionshavesettheusernametoAdmin.Theadminbooleanpropertywassettotrueandallprojectstatusvaluesweresettoclosed.Nowthemessageisreadytobesentout.IncaseaJSONPathexpressionshouldfailtofindamatchingelementwithinthemessagestructurethetestcasewillfail.
CitrusReferenceGuide
108JsonPath
WiththisJSONPathmechanismouareabletomanipulatemessagecontentbeforeitissentorreceivedwithinCitrus.Thismakeslifeveryeasywhenusingmessageresourcefilesthatarereusedacrossmultipletestcases.
ValidatewithJSONPath
LetscontinuetouseJSONPathexpressionswhenvalidatingareceivemessageinCitrus:
XMLDSL
<messagetype="json"><validate><json-pathexpression="$.user.name"value="Penny"/><json-pathexpression="$['user']['name']"value="$userName"/><json-pathexpression="$.user.aliases"value="["penny","jenny","nanny"]"/><json-pathexpression="$.user[?(@.admin)].password"value="@startsWith('$%00')@"/><json-pathexpression="$.user.address[?(@.type='office')]"value=""city":"Munich","street":"CompanyStreet","type":"office""/></validate></message>
JavaDSL
receive(someEndpoint).messageType(MessageType.JSON).validate("$.user.name","Penny").validate("$['user']['name']","$userName").validate("$.user.aliases","["penny","jenny","nanny"]").validate("$.user[?(@.admin)].password","@startsWith('$%00')@").validate("$.user.address[?(@.type='office')]",""city":"Munich","street":"CompanyStreet","type":"office"");
TheaboveJSONPathexpressionswillbeevaluatedwhenCitrusvalidatesthereceivedmessage.Theexpressionresultiscomparedtotheexpectedvaluewhereexpectationscanbestaticvaluesaswellastestvariablesandvalidationmatcherexpressions.IncaseaJSONPathexpressionshouldnotbeabletofindanyelementsthetestcasewillalsofail.
JSONisaprettysimpleyetpowerfulmessageformat.SimplifiedaJSONmessagejustknowsJSONObject,JSONArrayandJSONValueitems.ThehandlingofJSONObjectandJSONValueitemsinJSONPathexpressionsisstraightforward.Wejustuseadot
CitrusReferenceGuide
109JsonPath
notatedsyntaxforwalkingthroughtheJSONObjecthierarchy.ThehandlingofJSONArrayitemsisalsonotverydifficulteither.CitruswilltrythebesttoconvertJSONArrayitemstoStringrepresentationvaluesforcomparison.
ImportantJSONPathexpressionswillonlyworkonJSONmessageformats.ThisiswhywehavetotellCitrusthecorrectmessageformat.BydefaultCitrusisworkingwithXMLmessagedataandthereforetheXMLvalidationmechanismsdoapplybydefault.WiththemessagetypeattributesettojsonwemakesurethatCitrusenablesJSONspecificfeaturesonthemessagevalidationsuchasJSONPathsupport.
NowletsgetabitmorecomplexwithvalidationmatchersandJSONobjectfunctions.CitrustriestogiveyouthemostcomfortablevalidationcapabilitieswhencomparingJSONobjectvaluesandJSONarrays.OnefirstthingyoucanuseisobjectfunctionslikekeySet()orsize().ThesefunctionalityisnotcoveredbyJSONPathoutofthebowbutaddedbyCitrus.Sethefollowingexampleonhowtouseit:
XMLDSL
<messagetype="json"><validate><json-pathexpression="$.user.keySet()"value="[id,name,admin,projects]"/><json-pathexpression="$.user.aliases.size()"value="3"/></validate></message>
JavaDSL
receive(someEndpoint).messageType(MessageType.JSON).validate("$.user.keySet()","[id,name,admin,projects]").validate("$.user.aliases.size()","3");
TheobjectfunctionsdoreturnspecialJSONobjectrelatedpropertiessuchasthesetofkeysforanobjectorthesizeofanJSONarray.
Nowletsgetevenmorecomfortablevalidationcapabilitieswithmatchers.CitrussupportsHamcrestmatcherswhichgivesusaverypowerfulwayofvalidatingJSONobjectelementsandarrays.Seethefollowingexamplesthatdemonstratehowthisworks:
XMLDSL
<messagetype="json">
CitrusReferenceGuide
110JsonPath
<validate><json-pathexpression="$.user.keySet()"value="@assertThat(contains(id,name,admin,projects))@"<json-pathexpression="$.user.aliases.size()"value="@assertThat(allOf(greaterThan(0),lessThan(5)))@"</validate></message>
JavaDSL
receive(someEndpoint).messageType(MessageType.JSON).validate("$.user.keySet()",contains("id","name","admin","projects")).validate("$.user.aliases.size()",allOf(greaterThan(0),lessThan(5)));
WhenusingtheXMLDSLwehavetousetheassertThatvalidationmatchersyntaxfordefiningtheHamcrestmatchers.YoucancombinematcherimplementationasseenintheallOf(greaterThan(0),lessThan(5))expression.WhenusingtheJavaDSLyoucanjustaddthematcherasexpectedresultobject.Citrusevaluatesthematchersandmakessureeverythingisasexpected.ThisisaverypowerfulvalidationmechanismasitcombinestheHamcrestmatchercapabilitieswithJSONmessagevalidation.
ExtractvariableswithJSONPath
Citrusisabletosavemessagecontenttotestvariablesattestruntime.Whenanincomingmessageispassingthemessagevalidationtheusercanextractsomevaluesofthatreceivedmessagetonewtestvariablesforlateruseinthetest.Thisisespeciallyhandsomewhenhavingtosendbacksomedynamicvalues.SoletssavesomevaluesusingJSONPath:
<messagetype="json"><data>user:"name":"Admin","password":"secret","admin":"true","aliases":["penny","chef","master"]</data><extract><messagepath="$.user.name"variable="userName"/><messagepath="$.user.aliases"variable="userAliases"/><messagepath="$.user[?(@.admin)].password"variable="adminPassword"/>
CitrusReferenceGuide
111JsonPath
</extract></message>
WiththisexamplewehaveextractedthreenewtestvariablesviaJSONPathexpressionevaluation.Thethreetestvariableswillbeavailabletoallupcomingtestactions.Thevariablevaluesare:
userName=AdminuserAliases=["penny","chef","master"]adminPassword=secret
AsyoucanseewecanalsoextractcomplexJSONObjectitemsorJSONArrayitems.ThetestvariablevalueisaStringrepresentationofthecomplexobject.
IgnorewithJSONPath
ThenextusagescenarioforJSONPathexpressionsinCitrusistheignoringofelementsduringmessagevalidation.AsyoualreadyknowCitrusprovidespowerfulvalidationmechanismsforXMLandJSONmessageformat.Theframeworkisabletocomparereceivedandexpectedmessagecontentswithpowerfulvalidatorimplementations.NowitthistimewewanttouseaJSONPathexpressionforignoringaveryspecificentryintheJSONobjectstructure.
<messagetype="json"><data>"users":["name":"Jane","token":"?","lastLogin":0,"name":"Penny","token":"?","lastLogin":0,"name":"Mary","token":"?","lastLogin":0]</data><ignoreexpression="$.users[*].token"/>
CitrusReferenceGuide
112JsonPath
<ignoreexpression="$..lastLogin"/></message>
ThistimeweaddJSONPathexpressionsasignorestatements.Thismeansthatweexplicitlyleaveouttheevaluatedelementsfromvalidation.Obviouslythismechanismisagoodthingtodowhendynamicmessagedatasimplyisnotdeterministicsuchastimestampsanddynamicidentifiers.IntheexampleaboveweexplicitlyskipthetokenentryandalllastLoginvaluesthatareobviouslytimestampvaluesinmilliseconds.
TheJSONPathevaluationisverypowerfulwhenitcomestoselectasetofJSONobjectsandelements.ThisishowwecanignoreseveralelementswithonesingleJSONPathexpressionwhichisverypowerful.
CitrusReferenceGuide
113JsonPath
TestactionsThischaptergivesabriefdescriptiontoalltestactionsthatatestercanincorporateintothetestcase.Besidessendingandreceivingmessagesthetestermayaccesstheseactionsinordertobuildamorecomplextestscenariothatfitsthedesiredusecase.
CitrusReferenceGuide
114Actions
Sendingmessages
Inaintegrationtestscenariowewanttotriggerprocessesandcallinterfaceservicesonthesystemundertest.Inordertodothisweneedtobeabletosendmessagestovariousmessagetransports.ThereforethesendmessagetestactioninCitrusisoneofthemostimportanttestactions.FirstofallletushavealookattheCitrusmessagedefinitioninCitrus:
Amessageconsistsofamessageheader(name-valuepairs)andamessagepayload.Laterinthissectionwewillseedifferentwaysofconstructingamessagewithpayloadandheadervalues.Butfirstofalllet'sconcentrateonasimplesendingmessageactioninsideatestcase.
XMLDSL
<testcasename="SendMessageTest"><description>Basicsendmessageexample</description>
<variables><variablename="text"value="HelloCitrus!"/><variablename="messageId"value="Mx1x123456789"/></variables>
<actions><sendendpoint="helloServiceEndpoint"><messagename="helloMessage">
CitrusReferenceGuide
115Send
<payload><TestMessage><Text>$text</Text></TestMessage></payload></message><header><elementname="Operation"value="sayHello"/><elementname="MessageId"value="$messageId"/></header></send></actions></testcase>
Themessagenameisoptionalanddefinesthemessageidentifierinthelocalmessagestore.Thismessagenameisveryusefulwhenaccessingthemessagecontentlateronduringthetestcase.Thelocalmessagestoreishandledpertestcaseandcontainsallexchangedmessages.Thesampleusesbothheaderandpayloadasmessagepartstosend.Inbothpartsyoucanusevariabledefinitions(see$textand$messageId).Sofirstofallletusrecapwhatvariablesdo.Testvariablesaredefinedattheverybeginningofthetestcaseandarevalidthroughoutallactionsthattakeplaceinthetest.Thismeansthatactionscansimplyreferenceavariablebytheexpression$variable-name.
TipUsevariableswhereveryoucan!Atleasttheimportantentitiesofatestshouldbedefinedasvariablesatthebeginning.Thetestcaseimprovesmaintainabilityandflexibilitywhenusingvariables.
Nowletshaveacloserlookatthesendingaction.The'endpoint'attributemightcatchyourattentionfirst.ThisattributereferencesamessageendpointinCitrusconfigurationbyname.Aspreviouslymentionedthemessageendpointdefinitionlivesinaseparateconfigurationfileandcontainstheactualmessagetransportsettings.Inthisexamplethe"helloServiceEndpoint"isreferencedwhichisamessageendpointforsendingoutmessagesviaJMSorHTTPforinstance.
Thetestcaseisnotawareofanytransportdetails,becauseitdoesnothaveto.Theadvantagesareobvious:Ontheonehandmultipletestcasescanreferencethemessageendpointdefinitionforbetterreuse.Secondlytestcasesareindependentofmessagetransportdetails.Soconnectionfactories,usercredentials,endpointurivaluesandsoonarenotpresentinthetestcase.
CitrusReferenceGuide
116Send
Inotherwordsthe"endpoint"attributeofthe<send>elementspecifieswhichmessageendpointdefinitiontouseandthereforewherethemessageshouldgoto.OnceagainallavailablemessageendpointsareconfiguredinaseparateCitrusconfigurationfile.Wewillcometothislateron.Besuretoalwayspicktherightmessageendpointtypeinordertopublishyourmessagetotherightdestination.
IfyoudonotliketheXMLlanguageyoucanalsousepureJavacodetodefinethesametest.InJavayouwouldalsomakeuseofthemessageendpointdefinitionandreferencethisinstance.ThesametestasshownaboveinJavaDSLlookslikethis:
JavaDSLdesigner
importorg.testng.ITestContext;importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.testng.TestNGCitrusTestDesigner;
@TestpublicclassSendMessageTestDesignerextendsTestNGCitrusTestDesigner
@CitrusTest(name="SendMessageTest")publicvoidsendMessageTest()description("Basicsendmessageexample");
variable("text","HelloCitrus!");variable("messageId","Mx1x123456789");
send("helloServiceEndpoint").name("helloMessage").payload("<TestMessage>"+"<Text>$text</Text>"+"</TestMessage>").header("Operation","sayHello").header("RequestTag","$messageId");
JavaDSLrunner
importorg.testng.ITestContext;importorg.testng.annotations.Test;importcom.consol.citrus.annotations.CitrusTest;importcom.consol.citrus.dsl.testng.TestNGCitrusTestRunner;
@TestpublicclassSendMessageTestRunnerextendsTestNGCitrusTestRunner
CitrusReferenceGuide
117Send
@CitrusTest(name="SendMessageTest")publicvoidsendMessageTest()variable("text","HelloCitrus!");variable("messageId","Mx1x123456789");
send(action->action.endpoint("helloServiceEndpoint").name("helloMessage").payload("<TestMessage>"+"<Text>$text</Text>"+"</TestMessage>").header("Operation","sayHello").header("RequestTag","$messageId"));
InsteadofusingtheXMLtagsforsendweusemethodsfromTestNGCitrusTestDesignerclass.Thesamemessageendpointisreferencedwithinthesendmessageaction.
Nowthatthemessagesenderpatternisclearwecanconcentrateonhowtospecifythemessagecontenttobesent.ThereareseveralpossibilitiesforyoutodefinemessagecontentinCitrus:
message:Thiselementconstructsthemessagetobesent.Thereareseveralchildelementsavailable:payload:NestedXMLpayloadasdirectchildnode.data:InlineCDATAdefinitionofthemessagepayloadresource:ExternalfileresourceholdingthemessagepayloadThesyntaxwouldbe:<resourcefile="classpath:com/consol/citrus/messages/TestRequest.xml"/>Thefilepathprefixindicatestheresourcetype,sothefilelocationisresolvedeitherasfilesystemresource(file:)orclasspathresource(classpath:).element:ExplicitlyoverwritevaluesintheXMLmessagepayloadusingXPath.Youcanreplacemessagecontentwithdynamicvaluesbeforesending.Eachentryprovidesa"path"and"value"attribute.The"path"givesaXPathexpressionevaluatingtoaXMLnodeelementorattributeinthemessage.The"value"canbeavariableexpressionoranyotherstaticvalue.Citruswillreplacethevaluebeforesendingthemessage.header:Definesaheaderforthemessage(e.g.JMSheaderinformationorSOAPheader):element:Eachheaderreceivesa"name"and"value".The"name"willbethenameoftheheaderentryand"value"itsrespectivevalue.Againtheusageofvariableexpressionsasvalueissupportedhere,too.
CitrusReferenceGuide
118Send
XMLDSL
<sendendpoint="helloServiceEndpoint"><message><payload><!--messagepayloadasXML--></payload></message></send>
<sendendpoint="helloServiceEndpoint"><message><data><![CDATA[<!--messagepayloadasXML-->]]></data></message></send>
<sendendpoint="helloServiceEndpoint"><message><resourcefile="classpath:com/consol/citrus/messages/TestRequest.xml"/></message></send>
Themostimportantthingwhendealingwithsendingactionsistopreparethemessagepayloadandheader.YouareabletoconstructthemessagepayloadeitherbynestedXMLchildnodes(payload),asinlineCDATA()orexternalfile().
NoteSometimesthenestedXMLmessagepayloadelementsmaycauseXSDschemavalidationruleviolations.ThisisbecauseofvariablevaluesnotfittingtheXSDschemarulesforexample.InthisscenarioyoucouldalsousesimpleCDATAsectionsaspayloaddata.Inthiscaseyouneedtousethe<data>elementincontrasttothe<payload>elementthatwehaveusedinourexamplessofar.
WiththisalternativeyoucanskiptheXMLschemavalidationfromyourIDEatdesigntime.UnfortunatelyyouwillloosetheXSDautocompletionfeaturesmanyXMLeditorsofferwhenconstructingyourpayload.
TheThesamepossibilitiesapplytotheCitrusJavaDSL.
JavaDSLdesigner
CitrusReferenceGuide
119Send
@CitrusTestpublicvoidmessagingTest()send("helloServiceEndpoint").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>");
@CitrusTestpublicvoidmessagingTest()send("helloServiceEndpoint").payload(newClassPathResource("com/consol/citrus/messages/TestRequest.xml"));
@CitrusTestpublicvoidmessagingTest()send("helloServiceEndpoint").payloadModel(newTestRequest("HelloCitrus!"));
@CitrusTestpublicvoidmessagingTest()send("helloServiceEndpoint").message(newDefaultMessage("HelloWorld!")));
BesidesdefiningmessagepayloadsasnormalStringsandviaexternalfileresource(classpathandfilesystem)youcanalsousemodelobjectsaspayloaddatainJavaDSL.ThismodelobjectpayloadrequiresapropermessagemarshallerthatshouldbeavailableasSpringbeaninsidetheapplicationcontext.BydefaultCitrusissearchingforabeanoftypeorg.springframework.oxm.Marshaller.
IncaseyouhavemultiplemessagemarshallersintheapplicationcontextyouhavetotellCitruswhichonetouseinthisparticularsendmessageaction.
@CitrusTestpublicvoidmessagingTest()send("helloServiceEndpoint").payloadModel(newTestRequest("HelloCitrus!"),"myMessageMarshallerBean");
CitrusReferenceGuide
120Send
NowCitruswillmarshalthemessagepayloadwiththemessagemarshallerbeannamedmyMessageMarshallerBean.Thiswayyoucanhavemultiplemessagemarshallerimplementationsactiveinyourproject(XML,JSON,andsoon).
LastnotleastthemessagecanbedefinedasCitrusmessageobject.HereyoucanchooseoneofthedifferentmessageimplementationsusedinCitrusforSOAP,HttporJMSmessages.Oryoujustusethedefaultmessageimplementationormaybeacustomimplementation.
Beforesendingtakesplaceyoucanexplicitlyoverwritesomemessagevaluesinpayload.Youcanthinkofoverwritingspecificmessageelementswithvariablevalues.AlsoyoucanoverwritevaluesusingXPath(xpath)orJSONPath(json-path)expressions.
Themessageheaderispartofourdutyofdefiningpropermessages,too.SoCitrususesname-valuepairslike"Operation"and"MessageId"inthenextexampletosetmessageheaderentries.Dependingonwhatmessageendpointisusedandwhichmessagetransportunderneaththeheadervalueswillbeshippedindifferentways.InJMStheheadersgototheheadersectionofthemessage,inHttpwesetmimeheadersaccordingly,inSOAPwecanaccesstheSOAPheaderelementsandsoon.Citrusaimstodothehardworkforyou.SoCitrusknowshowtosetheadersondifferentmessagetransports.
XMLDSL
<sendendpoint="helloServiceEndpoint"><message><payload><TestMessage><Text>Hello!</Text></TestMessage></payload></message><header><elementname="Operation"value="sayHello"/></header></receive>
Themessageheaderstosendaredefinedbyasimplenameandvaluepair.Ofcourseyoucanusetestvariablesinheadervaluesaswell.Let'sseehowthislookslikeinJavaDSL:
JavaDSLdesigner
CitrusReferenceGuide
121Send
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>").header("Operation","sayHello");
JavaDSLrunner
@CitrusTestpublicvoidmessagingTest()receive(action->action.endpoint("helloServiceEndpoint").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>").header("Operation","sayHello"));
ThisisbasicallyhowtosendmessagesinCitrus.Thetestcaseisresponsibleforconstructingthemessagecontentwhilethepredefinedmessageendpointholdstransportspecificsettings.Testcasesreferenceendpointcomponentstopublishmessagestotheoutsideworld.Thevariablesupportinmessagepayloadandmessageheaderenablesyoutoadddynamicvaluesbeforesendingoutthemessage.
CitrusReferenceGuide
122Send
Receivingmessages
Justlikesendingmessagesthereceivingpartisaveryimportantactioninanintegrationtest.HonestlythereceiveactionisevenmoreimportantinCitrusaswealsowanttovalidatetheincomingmessagecontents.Wearewritingatestsowealsoneedassertionsandchecksthateverythingworksasexpected.
Asalreadymentionedbeforeamessageconsistsofamessageheader(name-valuepairs)andamessagepayload.Laterinthisdocumentwewillseehowtovalidateincomingmessageswithpayloadandheadervalues.Westartwithaverysimpleexample:
XMLDSL
<receiveendpoint="helloServiceEndpoint"><messagename="helloRequest"><payload><TestMessage><Text>$text</Text></TestMessage></payload></message><header><elementname="Operation"value="sayHello"/><elementname="MessageId"value="$messageId"/></header></receive>
Overallthereceivemessageactionlooksquitesimilartothesendmessageaction.Conceptsareidenticalaswedefinethemessagecontentwithpayloadandheadervalues.Themessagenameisoptionalanddefinesthemessageidentifierinthelocalmessagestore.Thismessagenameisveryusefulwhenaccessingthemessagecontentlateronduringthetestcase.Thelocalmessagestoreishandledpertestcaseandcontainsallexchangedmessages.
Wecanusetestvariablesinbothmessagepayloadanheaders.NowletushavealookattheJavaDSLrepresentationofthissimpleexample:
JavaDSLdesigner
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint")
CitrusReferenceGuide
123Receive
.name("helloRequest").payload("<TestMessage>"+"<Text>$text</Text>"+"</TestMessage>").header("Operation","sayHello").header("MessageId","$messageId");
JavaDSLrunner
@CitrusTestpublicvoidmessagingTest()receive(action->action.endpoint("helloServiceEndpoint").name("helloRequest").payload("<TestMessage>"+"<Text>$text</Text>"+"</TestMessage>").header("Operation","sayHello").header("MessageId","$messageId"));
Thereceiveactionwaitsforamessagetoarrive.Thewholetestexecutionisstoppedwhilewaitingforthemessage.Thisisimportanttoensurethestepbysteptestworkflowprocessing.Ofcourseyoucanspecifymessagetimeoutssothereceiverwillonlywaitagivenamountoftimebeforeraisingatimeouterror.Followingfromthattimeoutexceptionthetestcasefailsasthemessagedidnotarriveintime.Citrusdefinesdefaulttimeoutsettingsforallmessagereceivingtasks.
Inagoodcasescenariothemessagearrivesintimeandthecontentcanbevalidatedasanextstep.Thisvalidationcanbedoneinvariousways.OntheonehandyoucanspecifyawholeXMLmessagethatyouexpectascontroltemplate.Inthiscasethereceivedmessagestructureiscomparedtotheexpectedmessagecontentelementbyelement.Ontheotherhandyoucanuseexplicitelementvalidationwhereonlyasmallsubsetofmessageelementsisincludedintovalidation.
BesidesthemessagepayloadCitruswillalsoperformvalidationonthereceivedmessageheadervalues.Testvariableusageissupportedasusualduringthewholevalidationprocessforpayloadandheaderchecks.
Ingeneralthevalidationcomponent(validator)inCitrusworkshandinhandwithamessagereceivingcomponentasthefollowingfigureshows:
CitrusReferenceGuide
124Receive
Themessagereceivingcomponentpassesthemessagetothevalidatorwheretheindividualvalidationstepsareperformed.Letushaveacloserlookatthevalidationoptionsandfeaturesstepbystep.
Validatemessagepayloads
Themostdetailedvalidationofincomingmessagesistodefinesomeexpectedmessagepayload.TheCitrusmessagevalidatorwillthenperformadetailedmessagepayloadcomparison.Theincomingmessagehastomatchexactlytotheexpectedmessagepayload.ThedifferentmessagevalidatorimplementationsinCitrusprovidedeepcomparisonofmessagestructuressuchasXML,JSONandsoon.
Sobydefininganexpectedmessagepayloadwevalidatetheincomingmessageinsyntaxandsemantics.Incaseadifferenceisidentifiedbythemessagevalidatorthevalidationandthetestcasefailswithrespectiveexceptions.Thisishowyoucandefinemessagepayloadsinreceiveaction:
XMLDSL
<receiveendpoint="helloServiceEndpoint"><message><payload><!--messagepayloadasXML--></payload></message></receive>
<receiveendpoint="helloServiceEndpoint"><message><data><![CDATA[<!--messagepayloadasXML-->]]></data></message></receive>
<receiveendpoint="helloServiceEndpoint"><message>
CitrusReferenceGuide
125Receive
<resourcefile="classpath:com/consol/citrus/messages/TestRequest.xml"/></message></receive>
Thethreeexamplesaboverepresentthreedifferentwaysofdefiningthemessagepayloadinareceivemessageaction.OntheonehandwecanuseinlinemessagepayloadsasnestedXMLorCDATAsectionsinthetest.Ontheotherhandwecanloadthemessagecontentfromexternalfileresource.
NoteSometimesthenestedXMLmessagepayloadelementsmaycauseXSDschemavalidationruleviolations.ThisisbecauseofvariablevaluesnotfittingtheXSDschemarulesforexample.InthisscenarioyoucouldalsousesimpleCDATAsectionsaspayloaddata.Inthiscaseyouneedtousethe<data>elementincontrasttothe<payload>elementthatwehaveusedinourexamplessofar.
WiththisalternativeyoucanskiptheXMLschemavalidationfromyourIDEatdesigntime.UnfortunatelyyouwillloosetheXSDautocompletionfeaturesmanyXMLeditorsofferwhenconstructingyourpayload.
InJavaDSLwealsohavemultipleoptionsforspecifyingthemessagepayloads:
JavaDSLdesigner
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>");
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").payload(newClassPathResource("com/consol/citrus/messages/TestRequest.xml"));
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").payloadModel(newTestRequest("HelloCitrus!"));
CitrusReferenceGuide
126Receive
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").message(newDefaultMessage("HelloWorld!")));
TheexamplesaboverepresentthebasicvariationsofhowtodefinemessagepayloadsinCitrusJavaDSL.ThepayloadcanbeasimpleStringoraSpringfileresource(classpathorfilesystem).Inadditiontothatwecanuseamodelobject.WhenusingmodelobjectsaspayloadsweneedapropermessagemarshallerimplementationintheSpringapplicationcontext.Bydefaultthisisamarshallerbeanoftypeorg.springframework.oxm.MarshallerthathastobepresentintheSpringapplicationcontext.YoucanaddsuchabeanforXMLandJSONmessagemarshallingforinstance.
IncaseyouhavemultiplemessagemarshallersintheapplicationcontextyouhavetotellCitruswhichonetouseinthisparticularsendmessageaction.
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").payloadModel(newTestRequest("HelloCitrus!"),"myMessageMarshallerBean");
NowCitruswillmarshalthemessagepayloadwiththemessagemarshallerbeannamedmyMessageMarshallerBean.Thiswayyoucanhavemultiplemessagemarshallerimplementationsactiveinyourproject(XML,JSON,andsoon).
LastnotleastthemessagecanbedefinedasCitrusmessageobject.HereyoucanchooseoneofthedifferentmessageimplementationsusedinCitrusforSOAP,HttporJMSmessages.Oryoujustusethedefaultmessageimplementationormaybeacustomimplementation.
IngeneraltheexpectedmessagecontentcanbemanipulatedusingXPath(xpath)orJSONPath(json-path).Inadditiontothatyoucanignoresomeelementsthatareskippedincomparison.Wewilldescribethislateroninthissection.Nowletscontinuewithmessageheadervalidation.
Validatemessageheaders
CitrusReferenceGuide
127Receive
Messageheadersareusedwidelyinenterprisemessagingsolution:Themessageheadersarepartofthemessagesemanticsandneedtobevalidated,too.Citruscanvalidatemessageheaderbynameandvalue.
XMLDSL
<receiveendpoint="helloServiceEndpoint"><message><payload><TestMessage><Text>Hello!</Text></TestMessage></payload></message><header><elementname="Operation"value="sayHello"/></header></receive>
Theexpectedmessageheadersaredefinedbyanameandvaluepair.Citruswillcheckthattheexpectedmessageheaderispresentandwillcheckthevalue.IncasethemessageheaderisnotfoundorthevaluedoesnotmatchCitruswillraiseanexceptionandthetestfails.Youcanusevalidationmatchers(validation-matchers)foramorepowerfulvalidationofheadervalues,too.
Let'sseehowthislookslikeinJavaDSL:
JavaDSLdesigner
@CitrusTestpublicvoidmessagingTest()receive("helloServiceEndpoint").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>").header("Operation","sayHello");
JavaDSLrunner
@CitrusTestpublicvoidmessagingTest()receive(action->action.endpoint("helloServiceEndpoint").payload("<TestMessage>"+"<Text>Hello!</Text>"+"</TestMessage>")
CitrusReferenceGuide
128Receive
.header("Operation","sayHello"));
HeaderdefinitioninJavaDSLisstraightforwardaswejustdefinenameandvalueasusual.ThiscompletesthemessagevalidationwhenreceivingamessageinCitrus.ThemessagevalidatorimplementationsmayaddadditionalvalidationcapabilitiessuchasXMLschemavalidationorXPathandJSONPathvalidation.Pleaserefertotherespectivechaptersinthisguidetolearnmoreaboutthat.
Messageselectors
The<selector>elementinsidethereceivingactiondefineskey-valuepairsinordertofilterthemessagesbeingreceived.Thefilterappliestothemessageheaders.Thismeansthatareceiverwillonlyacceptmessagesmatchingaheaderelementvalue.Inmessagingapplicationstheheaderinformationoftenholdsmessageids,correlationids,operationnamesandsoon.Withthisinformationgivenyoucanexplicitlylistenformessagesthatbelongtoyourtestcase.Thisisveryhelpfultoavoidreceivingmessagesthatarestillavailableonthemessagedestination.
Letssaythetestedsoftwareapplicationkeepssendingmessagesthatbelongtoprevioustestcases.Thiscouldhappeninretrysituationswheretheapplicationerrorhandlingautomaticallytriestosolveacommunicationproblemthatoccurredduringprevioustestcases.Asaresultamessagedestination(e.g.aJMSmessagequeue)containsmessagesthatarenotvalidanymoreforthecurrentlyrunningtestcase.Thetestcasemightfailbecausethereceivedmessagedoesnotapplytotheactualusecase.Sowewilldefinitelyrunintovalidationerrorsastheexpectedmessagecontrolvaluesdonotmatch.
Nowwehavetofindawaytoavoidtheseproblems.Thetestcouldfilterthemessagesonadestinationtoonlyreceivemessagesthatapplyfortheusecasethatisbeingtested.TheJavaMessagingSystem(JMS)cameupwithamessageheaderselectorthatwillonlyacceptmessagesthatfittheexpectedheadervalues.
Letushaveacloserlookatamessageselectorinsideareceivingaction:
XMLDSL
<selector><element>name="correlationId"value="Cx1x123456789"</element><element>name="operation"value="getOrders"</element></selector>
CitrusReferenceGuide
129Receive
JavaDSLdesigner
@CitrusTestpublicvoidreceiveMessageTest()receive("testServiceEndpoint").selector("correlationId='Cx1x123456789'ANDoperation='getOrders'");
JavaDSLrunner
@CitrusTestpublicvoidreceiveMessageTest()receive(action->action.endpoint("testServiceEndpoint").selector("correlationId='Cx1x123456789'ANDoperation='getOrders'"));
Thisexampleshowshowmessageselectorswork.Theselectorwillonlyacceptmessagesthatmeetthecorrelationidandtheoperationintheheadervalues.Allothermessagesonthemessagedestinationareignored.TheselectorelementsareautomaticallyassociatedtoeachotherusingthelogicalANDoperator.Thismeansthatthemessageselectorstringwouldlooklikethis:correlationId='Cx1x123456789'ANDoperation='getOrders'.
Insteadofusingseveralelementsintheselectoryoucanalsodefineaselectorstringdirectlywhichgivesyoumorepowerinconstructingtheselectionlogicyourself.ThiswayyoucanuseANDlogicaloperatorsyourself.
<selector><value>correlationId='Cx1x123456789'ANDoperation='getOrders'</value></selector>
ImportantIncaseyouwanttoruntestsinparallelmessageselectorsbecomeessentialinyourtestcases.Thedifferenttestsrunningatthesametimewillstealmessagesfromeachotherwhenyoulackofmessageselectionmechanisms.
ImportantPreviouslyonlyJMSmessagedestinationsofferedsupportformessageselectors!WithCitrusversion1.2weintroducedmessageselectorsupportforSpringIntegrationmessagechannels,too(seemessage-channel-selector-support).
GroovyMarkupBuilder
CitrusReferenceGuide
130Receive
WiththeGroovyMarkupBuilderyoucanbuildXMLmessagepayloadsinasimpleway,withouthavingtowritethetypicalXMLoverhead.ForexampleweuseaGroovyscripttoconstructtheXMLmessagetobesentout.InsteadofaplainCDATAXMLsectionorthenestedpayloadXMLdatawewriteaGroovyscriptsnippet.TheGroovyMarkupBuildergeneratestheXMLmessagepayloadwithexactlythesameresult:
XMLDSL
<sendendpoint="helloServiceEndpoint"><message><buildertype="groovy">markupBuilder.TestMessageMessageId('$messageId')Timestamp('?')VersionId('2')Text('HelloCitrus!')</builder><elementpath="/TestMessage/Timestamp"value="$createDate"/></message><header><elementname="Operation"value="sayHello"/><elementname="MessageId"value="$messageId"/></header></send>
WeusethebuilderelementwithtypegroovyandtheMarkupBuildercodeisdirectlywrittentothiselement.Asyoucanseefromtheexampleabove,youcanmixXPathandGroovymarkupbuildercode.TheMarkupBuildersyntaxisveryeasyandfollowsthesimplerule:markupBuilder.ROOT-ELEMENTCHILD-ELEMENTS.HoweverthetesterhastofollowsomesimplerulesandnamingconventionswhenusingtheCitrusMarkupBuilderextension:
TheMarkupBuilderisaccessedwithinthescriptoveranobjectnamedmarkupBuilder.Thenameofthecustomrootelementfollowswithallitschildelements.Childelementsmaybedefinedwithincurlybracketsaftertheroot-element(thesameappliesforfurthernestedchildelements)Attributesandelementvaluesaredefinedwithinroundbrackets,aftertheelementnameAttributeandelementvalueshavetostandwithinapostrophes(e.g.attribute-name:'attribute-value')
CitrusReferenceGuide
131Receive
TheGroovyMarkupBuilderscriptmayalsobeusedwithinreceiveactionsasshowninthefollowinglisting:
XMLDSL
<sendendpoint="helloServiceEndpoint"><message><buildertype="groovy"file="classpath:com/consol/citrus/groovy/helloRequest.groovy"/></message></send>
<receiveendpoint="helloServiceEndpoint"timeout="5000"><message><buildertype="groovy">markupBuilder.TestResponse(xmlns:'http://www.consol.de/schemas/samples/sayHello.xsd')MessageId('$messageId')CorrelationId('$correlationId')User('HelloService')Text('Hello$user')</builder></message></receive>
Asyoucanseeitisalsopossibletodefinethescriptasexternalfileresource.Inadditiontothatnamespacesupportisgivenasnormalattributedefinitionwithintheroundbracketsaftertheelementname.
TheMarkupBuilderimplementationinGroovyoffersgreatpossibilitiesindefiningmessagepayloads.WedonotneedtowriteXMLtagoverheadandwecanconstructcomplexmessagepayloadswithGroovylogiclikeiterationsandconditionalelements.FordetailedMarkupBuilderdescriptionspleaseseetheofficialGroovydocumentation.
CitrusReferenceGuide
132Receive
Databaseactions
Inmanycasesitisnecessarytoaccessthedatabaseduringatest.Thisenablesatestertoalsovalidatethepersistentdatainadatabase.Itmightalsobehelpfultopreparethedatabasewithsometestdatabeforerunningatest.Youcandothisusingthetwodatabaseactionsthataredescribedinthefollowingsections.
IngeneralCitrushandlesSELECTstatementsdifferentlytootherstatementslikeINSERT,UPDATEandDELETE.WhenexecutingaSQLquerywithSELECTyouareabletoaddvalidationstepsontheresultsetsreturnedfromthedatabase.ThisisnotallowedwhenexecutingupdatestatementslikeINSERT,UPDATE,DELETE.
ImportantDonotmixstatementsoftypeSELECTwithothersinasinglesqltestaction.ThiswillleadtoerrorsbecausevalidationstepsarenotvalidforstatementsotherthanSELECT.Pleaseuseseparatetestactionsforupdatestatements.
SQLupdate,insert,delete
TheactionsimplyexecutesagroupofSQLstatementsinordertochangedatainadatabase.Typicallytheactionisusedtopreparethedatabaseatthebeginningofatestortocleanupthedatabaseattheendofatest.YoucanspecifySQLstatementslikeINSERT,UPDATE,DELETE,CREATETABLE,ALTERTABLEandmanymore.
OntheonehandyoucanspecifythestatementsasinlineSQLorstoredinanexternalSQLresourcefileasshowninthenexttwoexamples.
XMLDSL
<actions><sqldatasource="someDataSource"><statement>DELETEFROMCUSTOMERS</statement><statement>DELETEFROMORDERS</statement></sql>
<sqldatasource="myDataSource"><resourcefile="file:tests/unit/resources/script.sql"/></sql></actions>
JavaDSLdesigner
@Autowired
CitrusReferenceGuide
133Database
@Qualifier("myDataSource")privateDataSourcedataSource;
@CitrusTestpublicvoidsqlTest()sql(dataSource).statement("DELETEFROMCUSTOMERS").statement("DELETEFROMORDERS");
sql(dataSource).sqlResource("file:tests/unit/resources/script.sql");
JavaDSLrunner
@Autowired@Qualifier("myDataSource")privateDataSourcedataSource;
@CitrusTestpublicvoidsqlTest()sql(action->action.dataSource(dataSource).statement("DELETEFROMCUSTOMERS").statement("DELETEFROMORDERS"));
sql(action->action.dataSource(dataSource).sqlResource("file:tests/unit/resources/script.sql"));
ThefirstactionusesinlineSQLstatementsdefineddirectlyinsidethetestcase.ThenextactionusesanexternalSQLresourcefileinstead.ThefileresourcecanholdseveralSQLstatementsseparatedbynewlines.Allstatementsinsidethefileareexecutedsequentiallybytheframework.
ImportantYouhavetopayattentiontosomeruleswhendealingwithexternalSQLresources.
EachstatementshouldbegininanewlineItisnotallowedtodefinestatementswithwordwrappingCommentsbeginwithtwodashes"--"
NoteTheexternalfileisreferencedeitherasfilesystemresourceorclasspathresource,byusingthe"file:"or"classpath:"prefix.
CitrusReferenceGuide
134Database
Bothexamplesusethe"datasource"attribute.Thisvaluedefinesthedatabasedatasourcetobeused.Theconnectiontoadatasourceismandatory,becausethetestcasedoesnotknowaboutusercredentialsordatabasenames.The'datasource'attributereferencespredefineddatasourcesthatarelocatedinaseparateSpringconfigurationfile.
SQLquery
ThequeryactionisspeciallydesignedtoexecuteSQLqueries(SELECT*FROM).Sothetestisabletoreaddatafromadatabase.Thequeryresultsarevalidatedagainstexpecteddataasshowninthenextexample.
XMLDSL
<sqldatasource="testDataSource"><statement>selectNAMEfromCUSTOMERSwhereID='$customerId'</statement><statement>selectcount(*)fromERRORS</statement><statement>selectIDfromORDERSwhereDESCLIKE'Def%'</statement><statement>selectDESCRIPTIONfromORDERSwhereID='$id'</statement>
<validatecolumn="ID"value="1"/><validatecolumn="NAME"value="Christoph"/><validatecolumn="COUNT(*)"value="$rowsCount"/><validatecolumn="DESCRIPTION"value="null"/></sql>
JavaDSLdesigner
@Autowired@Qualifier("testDataSource")privateDataSourcedataSource;
@CitrusTestpublicvoiddatabaseQueryTest()query(dataSource).statement("selectNAMEfromCUSTOMERSwhereCUSTOMER_ID='$customerId'").statement("selectCOUNT(1)asoverall_cntfromERRORS").statement("selectORDER_IDfromORDERSwhereDESCRIPTIONLIKE'Migrate%'").statement("selectDESCRIPTIONfromORDERSwhereORDER_ID=2").validate("ORDER_ID","1").validate("NAME","Christoph").validate("OVERALL_CNT","$rowsCount").validate("DESCRIPTION","NULL");
CitrusReferenceGuide
135Database
JavaDSLrunner
@Autowired@Qualifier("testDataSource")privateDataSourcedataSource;
@CitrusTestpublicvoiddatabaseQueryTest()query(action->action.dataSource(dataSource).statement("selectNAMEfromCUSTOMERSwhereCUSTOMER_ID='$customerId'").statement("selectCOUNT(1)asoverall_cntfromERRORS").statement("selectORDER_IDfromORDERSwhereDESCRIPTIONLIKE'Migrate%'").statement("selectDESCRIPTIONfromORDERSwhereORDER_ID=2").validate("ORDER_ID","1").validate("NAME","Christoph").validate("OVERALL_CNT","$rowsCount").validate("DESCRIPTION","NULL"));
Theactionoffersawiderangeofvalidatingfunctionalityfordatabaseresultsets.FirstofallyouhavetoselectthedataviaSQLstatements.HereagainyouhavethechoicetouseinlineSQLstatementsorexternalfileresourcepattern.
Theresultsetsarevalidatedthroughelements.Itispossibletodoadetailedcheckoneveryselectedcolumnoftheresultset.Simplyrefertotheselectedcolumnnameinordertovalidateitsvalue.Theusageoftestvariablesissupportedaswellasdatabaseexpressionslikecount(),avg(),min(),max().
Yousimplydefinetheentrywiththecolumnnameasthe"column"attributeandanyexpectedvalueexpressionasexpected"value".Theframeworkthenwillcheckthecolumntofittheexpectedvalueandraisevalidationerrorsincaseofmismatch.
LookingatthefirstSELECTstatementintheexampleyouwillseethattestvariablesaresupportedintheSQLstatements.Theframeworkwillreplacethevariablewithitsrespectivevaluebeforesendingittothedatabase.
Inthevalidationsectionvariablescanbeusedtoo.Lookatthethirdvalidationentry,wherethevariable"$rowsCount"isused.Thelastvalidationinthisexampleshows,thatNULLvaluesarealsosupportedasexpectedvalues.
Ifasinglevalidationhappenstofail,thewholeactionwillfailwithrespectivevalidationerrors.
CitrusReferenceGuide
136Database
ImportantThevalidationwith""meetssinglerowresultsetsasyouspecifyasinglecolumncontrolvalue.Incaseyouhavemultiplerowsinaresultsetyouratherneedtovalidatethecolumnswithmultiplecontrolvalueslikethis:
<validatecolumn="someColumnName"><values><value>Valuein1strow</value><value>Valuein2ndrow</value><value>Valuein3rdrow</value><value>Valueinxrow</value></values></validate>
WithinJavayoucanpassavariableargumentlisttothevalidatemethodlikethis:
query(dataSource).statement("selectNAMEfromWEEKDAYSwhereNAMELIKE'S%'").validate("NAME","Saturday","Sunday")
Nextexampleshowshowtoworkwithmultiplerowresultsetsandmultiplevaluestoexpectwithinonecolumn:
<sqldatasource="testDataSource"><statement>selectWEEKDAYasDAY,DESCRIPTIONfromWEEK</statement><validatecolumn="DAY"><values><value>Monday</value><value>Tuesday</value><value>Wednesday</value><value>Thursday</value><value>Friday</value><value>@ignore@</value><value>@ignore@</value></values></validate><validatecolumn="DESCRIPTION"><values><value>IhateMondays!</value><value>Tuesdayissportsday</value><value>Themidoftheweek</value><value>Thursdayweplaychess</value><value>Friday,theweekendisnear!</value><value>@ignore@</value><value>@ignore@</value></values>
CitrusReferenceGuide
137Database
</validate></sql>
Forthevalidationofmultiplerowsthe<validate>elementisabletohostalistofcontrolvaluesforacolumn.Asyoucanseefromtheexampleabove,youhavetoaddacontrolvalueforeachrowintheresultset.Thisalsomeansthatwehavetotakecareofthetotalnumberofrows.Fortunatelywecanusetheignoreplaceholder,inordertoskipthevalidationofaspecificrowintheresultset.Functionsandvariablesaresupportedasusual.
ImportantItisimportant,thatthecontrolvaluesaredefinedinthecorrectorder,becausetheyarecomparedoneononewiththeactualresultsetcomingfromdatabasequery.Youmayneedtoadd"orderby"SQLexpressionstogettherightorderofrowsreturned.Ifanyofthevaluesfailsinvalidationorthetotalnumberofrowsisnotequal,thewholeactionwillfailwithrespectivevalidationerrors.
GroovySQLresultsetvalidation
GroovyprovidesgreatsupportforaccessingJavalistobjectsandmaps.AsaJavaSQLresultsetisnothingbutalistofmaprepresentations,whereeachentryinthelistdefinesarowintheresultsetandeachmapentryrepresentsthecolumnsandvalues.SowithGroovy'slistandmapaccesswehavegreatpossibilitiestovalidateaSQLresultset-outofthebox.
XMLDSL
<sqldatasource="testDataSource"><statement>selectIDfromCUSTOMERSwhereNAME='$customerName'</statement><statement>selectORDERTYPE,STATUSfromORDERSwhereID='$orderId'</statement>
<validate-scripttype="groovy">assertrows.size()==2assertrows[0].ID=='1'assertrows[1].STATUS=='inprogress'assertrows[1]==[ORDERTYPE:'SampleOrder',STATUS:'inprogress']</validate-script></sql>
JavaDSLdesigner
query(dataSource).statement("selectORDERTYPE,STATUSfromORDERSwhereID='$orderId'").validateScript("assertrows.size==2;"+
CitrusReferenceGuide
138Database
"assertrows[0].ID=='1';"+"assertrows[0].STATUS=='inprogress';","groovy");
JavaDSLrunner
query(action->action.dataSource(dataSource).statement("selectORDERTYPE,STATUSfromORDERSwhereID='$orderId'").validateScript("assertrows.size==2;"+"assertrows[0].ID=='1';"+"assertrows[0].STATUS=='inprogress';","groovy"));
AsyoucanseeGroovyprovidesfantasticaccessmethodstotheSQLresultset.Wecanbrowsetheresultsetwithnamedcolumnvaluesandcheckthesizeoftheresultset.Wearealsoabletosearchforanentry,iterateovertheresultsetandhaveotherhelpfuloperations.ForadetaileddescriptionofthelistandmaphandlinginGroovymyadviceforyouistohavealookattheofficialGroovydocumentation.
NoteIngeneralotherscriptlanguagesdoalsosupportthiskindoflistandmapaccess.FornowwejusthaveimplementedtheGroovyscriptsupport,buttheframeworkisreadytoworkwithallothergreatscriptlanguagesoutthere,too(e.g.Scala,Clojure,Fantom,etc.).Soifyouprefertoworkwithanotherlanguagejoinandhelpusimplementthosefeatures.
Saveresultsetvalues
Nowthevalidationofdatabaseentriesisaverypowerfulfeaturebutsometimeswesimplydonotknowthepersistedcontentvalues.Thetestmaywanttoreaddatabaseentriesintotestvariableswithoutvalidation.Citrusisabletodothatwiththefollowingexpressions:
XMLDSL
<sqldatasource="testDataSource"><statement>selectIDfromCUSTOMERSwhereNAME='$customerName'</statement><statement>selectSTATUSfromORDERSwhereID='$orderId'</statement>
<extractcolumn="ID"variable="$customerId"/><extractcolumn="STATUS"variable="$orderStatus"/></sql>
JavaDSLdesigner
CitrusReferenceGuide
139Database
query(dataSource).statement("selectSTATUSfromORDERSwhereID='$orderId'").extract("STATUS","orderStatus");
JavaDSLrunner
query(action->action.dataSource(dataSource).statement("selectSTATUSfromORDERSwhereID='$orderId'").extract("STATUS","orderStatus"));
Wecansavethedatabasecolumnvaluesdirectlytotestvariables.Ofcourseyoucancombinethevalueextractionwiththenormalcolumnvalidationdescribedearlierinthischapter.Pleasekeepinmindthatwecannotusetheseoperationsonresultsetswithmultiplerows.Citruswillalwaysusethefirstrowinaresultset.
CitrusReferenceGuide
140Database
Sleep
Thisactionshowshowtomakethetestframeworksleepforagivenamountoftime.Theattribute'time'definestheamountoftimetowaitinseconds.Asshowninthenextexampledecimalvaluesaresupportedtoo.Whennowaitingtimeisspecifiedthedefaulttimeof50000millisecondsapplies.
XMLDSL
<testcasename="sleepTest"><actions><sleepseconds="3.5"/>
<sleepmilliseconds="500"/>
<sleep/></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidsleepTest()sleep(500);//sleep500milliseconds
sleep();//sleepdefaulttime
Whenshouldsomebodyusethisaction?Tousthisactionwasalwaysveryusefulincasethetestneededtowaituntilanapplicationhaddonesomework.Forexampleinsomecasestheapplicationtooksometimetowritesomedataintothedatabase.Wewaitedthenasmallamountoftimeinordertoavoidunnecessarytestfailures,becausethetestframeworksimplyvalidatedthedatabasetooearly.Orasanotherexamplethetestmaywaitagiventimeuntilretrymechanismsaretriggeredinthetestedapplicationandthenproceedwiththetestactions.
CitrusReferenceGuide
141Sleep
Java
ThetestframeworkiswritteninJavaandrunsinsideaJavavirtualmachine.ThefunctionalityofcallingotherJavaobjectsandmethodsinthissameJavaVMthroughJavaReflectionisself-evident.WiththisactionyoucancallanyJavaAPIavailableatruntimethroughthespecifiedJavaclasspath.
Theactionsyntaxlookslikefollows:
<javaclass="com.consol.citrus.test.util.InvocationDummy"><constructor><argumenttype="">TestInvocation</argument></constructor><methodname="invoke"><argumenttype="String[]">1,2</argument></method></java>
<javaclass="com.consol.citrus.test.util.InvocationDummy"><constructor><argumenttype="">TestInvocation</argument></constructor><methodname="invoke"><argumenttype="int">4</argument><argumenttype="String">TestInvocation</argument><argumenttype="boolean">true</argument></method></java>
<javaclass="com.consol.citrus.test.util.InvocationDummy"><methodname="main"><argumenttype="String[]">4,Test,true</argument></method></java>
TheJavaclassisspecifiedbyfullyqualifiedclassname.Constructorargumentsareaddedusingtheelementwithalistofchildelements.Thetypeoftheargumentisdefinedwithintherespectiveattribute"type".BydefaultthetypewouldbeString.
TheinvokedmethodontheJavaobjectissimplyreferencedbyitsname.Methodargumentsdonotbringanythingnewafterknowingtheconstructorargumentdefinition,dothey?.
CitrusReferenceGuide
142Java
Methodargumentssupportdatatypeconversiontoo,evenstringarrays(usefulwhencallingCLIs).Inthethirdactionintheexamplecodeyoucanseethatcolonseparatedstringsareautomaticallyconvertedtostringarrays.
Simpledatatypesaredefinedbytheirname(int,boolean,floatetc.).Besurethattheinvokedmethodandclassconstructorfityourargumentsandviceversa,otherwiseyouwillcauseerrorsatruntime.
BesidesinstantiatingafullynewobjectinstanceforaclasshowaboutreusingabeaninstanceavailableinSpringbeancontainer.SimplyusetherefattributeandrefertoanexistingbeaninSpringapplicationcontext.
<javaref="invocationDummy"><methodname="invoke"><argumenttype="int">4</argument><argumenttype="String">TestInvocation</argument><argumenttype="boolean">true</argument></method></java>
<beanid="invocationDummy"class="com.consol.citrus.test.util.InvocationDummy"/>
ThemethodisinvokedontheSpringbeaninstance.Thisisveryusefulasyoucaninjectotherobjects(e.g.viaAutowiring)totheSpringbeaninstancebeforemethodinvocationintesttakesplace.ThisenablesyoutoexecuteanyJavalogicinsideatestcase.
CitrusReferenceGuide
143Java
Receivetimeout
Insomecasesitmightbenecessarytovalidatethatamessageisnotpresentonadestination.Thismeansthatthisactionexpectsatimeoutwhenreceivingamessagefromanendpointdestination.Forinstancethetesterintendstoensurethatnomessageissenttoacertaindestinationinatimeperiod.Inthatcasethetimeoutwouldnotbeatestabortingerrorbuttheexpectedbehavior.Andincontrasttothenormalbehaviorwhenamessageisreceivedinthetimeperiodthetestwillfailwitherror.
Inordertovalidatesuchatimeoutsituationtheactionshallhelp.Theusageisverysimpleasthefollowingexampleshows:
XMLDSL
<testcasename="receiveJMSTimeoutTest"><actions><expect-timeoutendpoint="myEndpoint"wait="500"/></actions></testcase>
JavaDSLdesigner
@Autowired@Qualifier("myEndpoint")privateEndpointmyEndpoint;
@CitrusTestpublicvoidreceiveTimeoutTest()receiveTimeout(myEndpoint).timeout(500);
JavaDSLrunner
@Autowired@Qualifier("myEndpoint")privateEndpointmyEndpoint;
@CitrusTestpublicvoidreceiveTimeoutTest()receiveTimeout(action->action.endpoint(myEndpoint).timeout(500));
CitrusReferenceGuide
144Timeout
Theactionofferstwoattributes:
endpoint:Referencetoamessageendpointthatwilltrytoreceivemessages.
wait/timeout:Timeperiodtowaitformessagestoarrive
Sometimesyoumaywanttoaddsomeselectoronthetimeoutreceivingaction.Thiswayyoucanveryselectivecheckonamessagetonotbepresentonamessagedestination.Thisispossiblewithdefiningamessageselectoronthetestactionasfollows.
XMLDSL
<expect-timeoutendpoint="myEndpoint"wait="500"><select>MessageId='123456789'<select/><expect-timeout/>
JavaDSLdesigner
@CitrusTestpublicvoidreceiveTimeoutTest()receiveTimeout(myEndpoint).selector("MessageId='123456789'").timeout(500);
JavaDSLrunner
@CitrusTestpublicvoidreceiveTimeoutTest()receiveTimeout(action->action.endpoint(myEndpoint).selector("MessageId='123456789'").timeout(500));
CitrusReferenceGuide
145Timeout
Echo
Theactionprintsmessagestotheconsole/logger.Thisfunctionalityisusefulwhendebuggingtestruns.Theproperty"message"definesthetextthatisprinted.Testermightuseittoprintoutdebugmessagesandvariablesasshownthenextcodeexample:
XMLDSL
<testcasename="echoTest"><variables><variablename="date"value="citrus:currentDate()"/></variables><actions><echo><message>HelloTestFramework</message></echo>
<echo><message>Currentdateis:$date</message></echo></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidechoTest()variable("date","citrus:currentDate()");
echo("HelloTestFramework");echo("Currentdateis:$date");
Resultontheconsole:
HelloTestFrameworkCurrenttimeis:05.08.2008
CitrusReferenceGuide
146Echo
Stoptime
Timemeasurementduringatestcanbeveryhelpful.Theactioncreatesandmonitorsmultipletimelines.Theactionofferstheattributeidtoidentifyatimeline.Thetestercanofcourseusemorethanonetimelinewithdifferentidssimultaneously.
Readthenextexampleandyouwillunderstandthemixofdifferenttimelines:
XMLDSL
<testcasename="StopTimeTest"><actions><trace-time/>
<trace-timeid="time_line_id"/>
<sleepseconds="3.5"/>
<trace-timeid="time_line_id"/>
<sleepmilliseconds="5000"/>
<trace-time/>
<trace-timeid="time_line_id"/></actions></testcase>
JavaDSL
@CitrusTestpublicvoidstopTimeTest()stopTime();stopTime("time_line_id");sleep(3.5);//dosomethingstopTime("time_line_id");sleep(5000);//dosomethingstopTime();stopTime("time_line_id");
Thetestoutputlookslikefollows:
StartingTimeWatcher:StartingTimeWatcher:time_line_id
CitrusReferenceGuide
147Stoptime
TimeWatchertime_line_idafter3500millisecondsTimeWatcherafter8500secondsTimeWatchertime_line_idafter8500milliseconds
ImportantTimelineidsshouldnotexistastestvariablesbeforetheactioniscalledforthefirsttime.Thiswouldbreakthetimelineinitialization.
NoteIncasenotimelineidisspecifiedtheframeworkwillmeasurethetimeforadefaulttimeline.Toprintoutthecurrentelapsedtimeforatimelineyousimplyhavetoplacethe`actionintotheactionchainagainandagain,usingtherespectivetimelineidentifier.Theelapsedtimewillbeprintedouttotheconsoleeverytime.
Eachtimelineisstoredastestvariableinthetestcase.Bydefaultyouwillhavethefollowingtestvariablessetforeachtimeline:
CITRUS_TIMELINEfirsttimestampoftimelineCITRUS_TIMELINE_VALUElatesttimemeasurementvalue(timepassedsincefirsttimestampinmilliseconds)
Accordingtoyourtimelineidyouwillgetdifferenttestvariablenames.Alsoyoucancustomizethetimevaluesuffix(default:_VALUE):
XMLDSL
<trace-timeid="custom_watcher"suffix="_1st"/><sleep/><trace-timeid="custom_watcher"suffix="_2nd"/>
JavaDSL
@CitrusTeststopTime("custom_watcher","_1st");sleep();stopTime("custom_watcher","_2nd");
Youwillgetfollowingtestvariablesset:
custom_watcherfirsttimestampoftimelinecustom_watcher_1sttimepassedsincestartcustom_watcher_2ndtimepassedsincestart
Ofcourseusingthesamesuffixmultipletimeswilloverwritethetimestampsintestvariables.
CitrusReferenceGuide
148Stoptime
CitrusReferenceGuide
149Stoptime
Createvariables
Asyouknowvariablesusuallyaredefinedatthebeginningofthetestcase(testcase-variables).Itmightalsobehelpfultoresetexistingvariablesaswellastodefinenewvariablesduringthetest.Theactionisabletodeclarenewvariablesoroverwriteexistingones.
XMLDSL
<testcasename="createVariablesTest"><variables><variablename="myVariable"value="12345"/><variablename="id"value="54321"/></variables><actions><echo><message>Currentvariablevalue:$myVariable</message></echo>
<create-variables><variablename="myVariable"value="$id"/><variablename="newVariable"value="'thisisatest'"/></create-variables>
<echo><message>Currentvariablevalue:$myVariable</message></echo>
<echo><message>Newvariable'newVariable'hasthevalue:$newVariable</message></echo></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidcreateVariableTest()variable("myVariable","12345");variable("id","54321");
echo("Currentvariablevalue:$myVariable");
createVariable("myVariable","$id");
CitrusReferenceGuide
150Createvariables
createVariable("newVariable","thisisatest");
echo("Currentvariablevalue:$myVariable");
echo("Newvariable'newVariable'hasthevalue:$newVariable");
NotePleasenotethedifferencebetweenthevariable()methodandthecreateVariable()method.Thefirstinitializesthetestcasewiththetestvariables.Soallvariablesdefinedwiththismethodarevalidfromtheverybeginningofthetest.IncontrarytothatthecreateVariable()isexecutedwithinthetestactionchain.Thenewlycreatedvariablesarethenvalidfortherestofthetest.Trailingactionscanreferencethevariablesasusualwiththevariableexpression.
CitrusReferenceGuide
151Createvariables
Tracevariables
Youalreadyknowtheactionthatprintsmessagestotheconsoleorlogger.Theactionisspeciallydesignedtotraceallcurrentlyvalidtestvariablestotheconsole.Thiswasmainlyusedbyusfordebugreasons.Theusageisquitesimple:
XMLDSL
<testcasename="traceVariablesTest"><variables><variablename="myVariable"value="12345"/><variablename="nextVariable"value="54321"/></variables><actions><trace-variables><variablename="myVariable"/><variablename="nextVariable"/></trace-variables>
<trace-variables/></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidtraceTest()variable("myVariable","12345");variable("nextVariable","54321");
traceVariables("myVariable","nextVariable");traceVariables();
Simplyaddtheactiontoyouractionchainandallvariableswillbeprintedouttotheconsole.Youareabletodefineaspecialsetofvariablesbyusingthechildelements.Seetheoutputthatwasgeneratedbythetestexampleabove:
CurrentvalueofvariablemyVariable=12345CurrentvalueofvariablenextVariable=54321
CitrusReferenceGuide
152Trace
CitrusReferenceGuide
153Trace
Transform
The<transform>actiontransformsXMLfragmentswithXSLTinordertoconstructvariousXMLrepresentations.Thetransformationresultisstoredintoatestvariableforfurtherusage.Thepropertyxml-datadefinestheXMLsource,thatisgoingtobetransformed,whilexslt-datadefinestheXSLTtransformationrules.Theattributevariablespecifiesthetargettestvariablewhichreceivesthetransformationresult.ThetestermightusetheactiontotransformXMLmessagesasshowninthenextcodeexample:
XMLDSL
<testcasename="transformTest"><actions><transformvariable="result"><xml-data><![CDATA[<TestRequest><Message>HelloWorld!</Message></TestRequest>]]></xml-data><xslt-data><![CDATA[<xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:templatematch="/"><html><body><h2>TestRequest</h2><p>Message:<xsl:value-ofselect="TestRequest/Message"/></p></body></html></xsl:template></xsl:stylesheet>]]></xslt-data></transform><echo><message>$result</message></echo></actions></testcase>
Thetransformationaboveresultsto:
CitrusReferenceGuide
154Transform
<html><body><h2>TestRequest</h2><p>Message:HelloWorld!</p></body></html>
IntheexampleweusedCDATAsectionstodefinethetransformationsourceaswellastheXSLtransformationrules.Asusualyoucanalsouseexternalfileresourceshere.Thetransformactionwithexternalfileresourceslookslikefollows:
<transformvariable="result"><xml-resourcefile="classpath:transform-source.xml"/><xslt-resourcefile="classpath:transform.xslt"/></transform>
TheJavaDSLalternativefortransformingdataviaXSTLinCitruslookslikefollows:
JavaDSLdesigner
@CitrusTestpublicvoidtransformTest()transform().source("<TestRequest>"+"<Message>HelloWorld!</Message>"+"</TestRequest>").xslt("<xsl:stylesheetversion=\"1.0\"xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n""<xsl:templatematch=\"/\">\n"+"<html>\n"+"<body>\n"+"<h2>TestRequest</h2>\n"+"<p>Message:<xsl:value-ofselect=\"TestRequest/Message\"/></p>\n""</body>\n"+"</html>\n"+"</xsl:template>\n"+"</xsl:stylesheet>").result("result");
echo("$result");
transform().source(newClassPathResource("com/consol/citrus/actions/transform-source.xml")).xslt(newClassPathResource("com/consol/citrus/actions/transform.xslt")).result("result");
CitrusReferenceGuide
155Transform
echo("$result");
JavaDSLrunner
@CitrusTestpublicvoidtransformTest()transform(action->action.source("<TestRequest>"+"<Message>HelloWorld!</Message>"+"</TestRequest>").xslt("<xsl:stylesheetversion=\"1.0\"xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n""<xsl:templatematch=\"/\">\n"+"<html>\n"+"<body>\n"+"<h2>TestRequest</h2>\n"+"<p>Message:<xsl:value-ofselect=\"TestRequest/Message\"/></p>\n"+"</body>\n"+"</html>\n"+"</xsl:template>\n"+"</xsl:stylesheet>").result("result"));
echo("$result");
transform(action->action.source(newClassPathResource("com/consol/citrus/actions/transform-source.xml")).xslt(newClassPathResource("com/consol/citrus/actions/transform.xslt")).result("result"));
echo("$result");
Definingmulti-lineStringswithnestedquotesisnofuninJava.Soyoumaywanttouseexternalfileresourcesforyourscriptsasshowninthesecondpartoftheexample.InfactyoucouldalsousescriptlanguageslikeGroovyorScalathathavemuchbettersupportformulti-lineStrings.
CitrusReferenceGuide
156Transform
Groovyscriptexecution
GroovyisanagiledynamiclanguagefortheJavaPlatform.GroovyshipswithalotofverypowerfulfeaturesandfitsperfectlywithJavaasitisbasedonJavaandrunsinsidetheJVM.
TheCitrusGroovysupportmightbetheentranceforyoutowritecustomizedtestactions.YoucaneasilyexecuteGroovycodeinsideatestcase,justlikeanormaltestaction.ThewholetestcontextwithallvariablesisavailabletotheGroovyaction.Thismeanssomeonecanchangevariablevaluesorcreatenewvariablesveryeasily.
Let'shavealookatsomeexamplesinordertounderstandthepossibleGroovycodeinteractionsinCitrus:
XMLDSL
<testcasename="groovyTest"><variables><variablename="time"value="citrus:currentDate()"/></variables><actions><groovy>println'HelloCitrus'</groovy><groovy>println'Thevariableis:$time'</groovy><groovyresource="classpath:com/consol/citrus/script/example.groovy"/></actions></testcase>
JavaDSLdesigner
@CitrusTestpublicvoidgroovyTest()groovy("println'HelloCitrus'");groovy("println'Thevariableis:$time'");
groovy(newClassPathResource("com/consol/citrus/script/example.groovy"));
JavaDSLrunner
@CitrusTest
CitrusReferenceGuide
157Groovy
publicvoidgroovyTest()groovy(action->action.script("println'HelloCitrus'"));groovy(action->action.script("println'Thevariableis:$time'"));
groovy(action->action.script(newClassPathResource("com/consol/citrus/script/example.groovy"
AsyoucanseeitispossibletowriteGroovycodedirectlyintothetestcase.CitruswillinterpretandexecutetheGroovycodeatruntime.Asusualnestedvariableexpressionsarereplacedwithrespectivevalues.IngeneralthisisdoneinadvancebeforetheGroovycodeisinterpreted.FormorecomplexGroovycodesectionswhichgrowinlinesofcodeyoucanalsoreferenceexternalfileresources.
AfterthisbasicGroovycodeusageinsideatestcasewemightbeinterestedaccessingthewholeTestContext.TheTestContextJavaobjectholdsalltestvariablesandfunctiondefinitionsforthetestcaseandcanbereferencedinGroovycodeviasimplenamingconvention.Justaccesstheobjectreference'context'andyouareabletomanipulatetheTestContext(e.g.settinganewvariablewhichisdirectlyreadyforuseinfollowingtestactions).
XMLDSL
<testcasename="groovyTest"><actions><groovy>context.setVariable("greetingText","HelloCitrus")printlncontext.getVariable("greetingText")</groovy><echo><message>Newvariable:$greetingText</message></echo></actions></testcase>
NoteTheimplicitTestContextaccessthatwasshownintheprevioussampleworkswithadefaultGroovyscripttemplateprovidedbyCitrus.TheGroovycodeyouwriteinthetestcaseisautomaticallysurroundedwithaGroovyscriptwhichtakescareofhandlingtheTestContext.Thedefaulttemplatelookslikefollows:
importcom.consol.citrus.*importcom.consol.citrus.variable.*importcom.consol.citrus.context.TestContextimportcom.consol.citrus.script.GroovyAction.ScriptExecutor
CitrusReferenceGuide
158Groovy
publicclassGScriptimplementsScriptExecutorpublicvoidexecute(TestContextcontext)@SCRIPTBODY@
Yourcodeisplacedinsubstitutiontothe@SCRIPTBODY@placeholder.NowyoumightunderstandhowCitrushandlesthecontextautomatically.YoucanalsowriteyourownscripttemplatesmakingmoreadvancedusageofotherJavaAPIsandGroovycode.Justaddascripttemplatepathtothetestactionlikethis:
<groovyscript-template="classpath:my-custom-template.groovy">[...]</groovy>
Ontheotherhandyoucandisabletheautomaticscripttemplatewrappinginyouractionatall:
<groovyuse-script-template="false">println'JustusesomeGroovycode'</groovy>
ThenextexampledealswithadvancedGroovycodeandwritingwholeclasses.WewriteanewGroovyclasswhichimplementstheScriptExecutorinterfaceofferedbyCitrus.ThisinterfacedefinesaspecialexecutemethodandprovidesaccesstothewholeTestContextforadvancedtestvariablesaccess.
<testcasename="groovyTest"><variables><variablename="time"value="citrus:currentDate()"/></variables><actions><groovy><![CDATA[importcom.consol.citrus.*importcom.consol.citrus.variable.*importcom.consol.citrus.context.TestContextimportcom.consol.citrus.script.GroovyAction.ScriptExecutor
publicclassGScriptimplementsScriptExecutorpublicvoidexecute(TestContextcontext)printlncontext.getVariable("time")
CitrusReferenceGuide
159Groovy
]]></groovy></actions></testcase>
ImplementingtheScriptExecutorinterfaceinacustomGroovyclassisapplicableforveryspecialtestcontextmanipulationsasyouareabletoimportanduseotherJavaAPIclassesinthiscode.
CitrusReferenceGuide
160Groovy
Failingthetest
Thefailactionwillgenerateanexceptioninordertoterminatethetestcasewitherror.Thetestcasewillthereforenotbesuccessfulinthereports.
Theusercanspecifyacustomerrormessagefortheexceptioninordertodescribetheerrorcause.Hereisaverysimpleexampletoclarifythesyntax:
XMLDSL
<testcasename="failTest"><actions><failmessage="Testwillfailwithcustommessage"/></actions></testcase>
Testresults:
Executionoftest:failTestfailed!Nestedexceptionis:com.consol.citrus.exceptions.CitrusRuntimeException:Testwillfailwithcustommessage
[...]
CITRUSTESTRESULTS
failTest:failed-Exceptionis:Testwillfailwithcustommessage
Found1testcasestoexecuteSkipped0testcases(0.0%)Executed1testcases,containing3actionsTestsfailed:1(100.0%)Testssuccessfully:0(0.0%)
WhileusingtheJavaDSLtestermightwanttoraisesomeJavaexceptionsinthemiddleofconfiguringthetestcase.Butthisisnotpossibleaswehavetoseparatethedesigntimeandtheexecutiontimeofthetestcase.The@CitrusTestannotatedconfigurationmethodiscalledforbuildingupthewholetestcase.Afterthismethodwasprocessedthetestgetsexecutedinruntimeoththetest.Ifyouspecifyathrowsexceptionstatementintheconfigurationmethodthiswillnotbedoneatruntimebutatdesigntime.ThisiswhyyouhavetousethespecialfailtestactionwhichraisesaJavaexceptionduringtheruntimeofthetest.Thenextexamplewillnotworkasexpected:
CitrusReferenceGuide
161Fail
JavaDSLdesignerandrunner
@CitrusTestpublicvoidwrongUsageSample()//sometestactions
thrownewValidationException("Thistestshouldfailnow");//doesnotworkasexpected
Thevalidationexceptionaboveisdirectlyraisedbeforethetestisabletostartasthe@CitrusTestannotatedmethoddoesnotrepresentthetestruntime.Insteadofthiswehavetousethefailactionasfollows:
JavaDSLdesignerandrunner
@CitrusTestpublicvoidfailTest()//sometestactions
fail("Thistestshouldfailnow");//failsattestruntimeasexpected
Nowthetestfailsatruntimeasthefailactionisraisedduringthetestexecutionasexpected.
CitrusReferenceGuide
162Fail
Input
Duringthetestcaseexecutionitispossibletoreadsomeuserinputfromthecommandline.Thetestexecutionwillstopandwaitforkeyboardinputsoverthestandardinputstream.Theuserhastotypetheinputandenditwiththereturnkey.
Theuserinputisstoredtotherespectivevariablevalue.
XMLDSL
<testcasename="inputTest"><variables><variablename="userinput"value=""></variable><variablename="userinput1"value=""></variable><variablename="userinput2"value="y"></variable><variablename="userinput3"value="yes"></variable><variablename="userinput4"value=""></variable></variables><actions><input/><echo><message>userinputwas:$userinput</message></echo>
<inputmessage="Nowpressenter:"variable="userinput1"/><echo><message>userinputwas:$userinput1</message></echo>
<inputmessage="Doyouwanttocontinue?"valid-answers="y/n"variable="userinput2"/><echo><message>userinputwas:$userinput2</message></echo>
<inputmessage="Doyouwanttocontinue?"valid-answers="yes/no"variable="userinput3"/><echo><message>userinputwas:$userinput3</message></echo>
<inputvariable="userinput4"/><echo><message>userinputwas:$userinput4</message></echo></actions></testcase>
Asyoucanseetheinputactioniscustomizablewithapromptmessagethatisdisplayedtotheuserandsomevalidanswerpossibilities.Theuserinputisstoredtoatestvariableforfurtheruseinthetestcase.Indetailtheinputactionoffersfollowingattributes:
message->messagedisplayedtotheuser
valid-answers->optionalslashseparatedstringcontainingthepossiblevalidanswers
CitrusReferenceGuide
163Input
variable->resultvariablenameholdingtheuserinput(default=$userinput)
ThesameactioninJavaDSLnowlooksquitefamiliartousalthoughattributenamingisslightlydifferent:
JavaDSLdesigner
@CitrusTestpublicvoidinputActionTest()variable("userinput","");variable("userinput1","");variable("userinput2","y");variable("userinput3","yes");variable("userinput4","");
input();echo("userinputwas:$userinput");input().message("Nowpressenter:").result("userinput1");echo("userinputwas:$userinput1");input().message("Doyouwanttocontinue?").answers("y","n").result("userinput2");echo("userinputwas:$userinput2");input().message("Doyouwanttocontinue?").answers("yes","no").result("userinput3");echo("userinputwas:$userinput3");input().result("userinput4");echo("userinputwas:$userinput4");
JavaDSLrunner
@CitrusTestpublicvoidinputActionTest()variable("userinput","");variable("userinput1","");variable("userinput2","y");variable("userinput3","yes");variable("userinput4","");
input(action->);echo("userinputwas:$userinput");input(action->action.message("Nowpressenter:").result("userinput1"));echo("userinputwas:$userinput1");input(action->action.message("Doyouwanttocontinue?").answers("y","n").result("userinput2"echo("userinputwas:$userinput2");input(action->action.message("Doyouwanttocontinue?").answers("yes","no").result("userinput3"echo("userinputwas:$userinput3");input(action->action.result("userinput4"));echo("userinputwas:$userinput4");
CitrusReferenceGuide
164Input
Whentheuserinputisrestrictedtoasetofvalidanswerstheinputvalidationofcoursecanfailduetomismatch.Thisisthecasewhentheuserprovidessomeinputnotmatchingthevalidanswersgiven.Inthiscasetheuserisagainaskedtoprovidevalidinput.Thetestactionwillcontinuetoaskforvalidinputuntilavalidanswerisgiven.
NoteUserinputsmaynotfittoautomatictestingintermsofcontinuousintegrationtestingwherenouserispresenttotypeinthecorrectansweroverthekeyboard.Inthiscaseyoucanalwaysskiptheuserinputinadvancebyspecifyingavariablethatmatchestheuserinputvariablename.Astheuserinputvariableisthenalreadypresenttheuserinputismissedoutandthetestproceedsautomatically.
CitrusReferenceGuide
165Input
Load
Youareabletoloadpropertiesfromexternalpropertyfilesandstorethemastestvariables.Theactionwillrequireafileresourceeitherfromclasspathorfilesysteminordertoreadthepropertyvalues.
Letuslookatanexampletogetanideaaboutthisaction:
Contentofload.properties:
username=MickeyMousegreeting.text=HelloTestFramework
XMLDSL
<testcasename="loadPropertiesTest"><actions><load><propertiesfile="file:tests/resources/load.properties"/></load>
<trace-variables/></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidloadPropertiesTest()load("file:tests/resources/load.properties");
traceVariables();
Output:
Currentvalueofvariableusername=MickeyMouseCurrentvalueofvariablegreeting.text=HelloTestFramework
Theactionwillloadallavailablepropertiesinthefileload.propertiesandstorethemtothetestcaseaslocalvariables.
CitrusReferenceGuide
166Load
ImportantPleasebeawareofthefactthatexistingvariablesareoverwritten!
CitrusReferenceGuide
167Load
Wait
Withthisactionyoucanmakeyourtestwaituntilacertainconditionissatisfied.Theattributesecondsdefinestheamountoftimetowaitinseconds.Youcanalsousethemillisecondsattributeforamorefinegrainedtimevalue.Theattributeintervaldefinestheamountoftimetowaitbetweeneachcheck.Theintervalisalwaysspecifiedasmillisecondtimeinterval.
Ifthecheckdoesnotexceedwithinthedefinedoverallwaitingtimethenthetestexecutionfailswithanappropriateerrormessage.Therearedifferenttypesofconditionstocheck.
http:ThisconditionisbasedonaHttprequestcallonaserverendpoint.CitruswillwaituntiltheHttpresponseisasdefined(e.g.Http200OK).Thisisusefulwhenyouwanttowaitforaservertostart.file:Thisconditionchecksfortheexistenceofafileonthelocalfilesystem.Citruswillwaituntilthefileispresent.message:Thisconditionchecksfortheexistenceofamessageinthelocalmessagestoreofthecurrenttestcase.Citruswillwaituntilthemessagewiththegivennameispresent.
Nextletushavealookatasimpleexample:
XMLDSL
<testcasename="waitTest"><actions><waitseconds="10"interval="2000"><httpurl="http://sample.org/resource"statusCode="200"timeout="2000"/><wait/></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidwaitTest()waitFor().http("http://sample.org/resource").seconds(10L).interval(2000L);
CitrusReferenceGuide
168Wait
TheexamplewaitsforsomeHttpserverresourcetobeavailablewithHttp200OKresponse.CitruswilluseHEADrequestmethodbydefault.YoucansettherequestmethodwiththemethodattributeontheHttpcondition.
Nextletushavealookatthefileconditionusage:
XMLDSL
<testcasename="waitTest"><actions><waitseconds="10"interval="2000"><filepath="path/to/resource/file.txt"/><wait/></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidwaitTest()waitFor().file("path/to/resource/file.txt");
Citruschecksforthefiletoexistunderthegivenpath.Onlyifthefileexiststhetestwillcontinuewithfurthertestactions.
Nextletushavealookatthemessageconditionusage:
XMLDSL
<testcasename="waitTest"><actions><waitseconds="10"interval="2000"><messagename="helloRequest"/><wait/></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidwaitTest()waitFor().message("helloRequest");
CitrusReferenceGuide
169Wait
CitruschecksforthemessagewiththenamehelloRequestinthelocalmessagestore.Onlyifthemessagewiththegivennameisfoundthetestwillcontinuewithfurthertestactions.Thelocalmessagestoreisautomaticallyfilledwithallexchangedmessages(sendorreceive)inatestcase.Themessagenamesaredefinedintherespectivesendorreceiveoperationsinthetest.
Whenshouldsomebodyusethisaction?Thisactionisveryusefulwhenyouwantyourtesttowaitforacertaineventtooccurbeforecontinuingwiththetestexecution.ForexampleifyouwishthatyourtestwaitsuntilaDockercontainerisstartedorforanapplicationtocreatealogfilebeforecontinuing,thenusethisaction.Youcanalsocreateyourownconditionstatementsandbindittothetestaction.
CitrusReferenceGuide
170Wait
PurgingJMSdestinations
PurgingJMSdestinationsduringthetestrunisquiteessential.DifferenttestcasescaninfluenceeachotherwhensendingmessagestothesameJMSdestinations.Atestcaseshouldonlyreceivethosemessagesthatactuallybelongtoit.ThereforeitisagoodideatopurgeallJMSqueuedestinationsbetweenthetestcases.ObsoletemessagesthatarestuckinaJMSqueueforsomereasonarethenremovedsothatthefollowingtestcaseisnotoffended.
NoteCitrusprovidesspecialsupportforJMSrelatedfeatures.WehavetoactivatethoseJMSfeaturesinourtestcasebyaddingaspecial"jms"namespaceandschemadefinitionlocationtothetestcaseXML.
<spring:beansxmlns="http://www.citrusframework.org/schema/testcase"xmlns:spring="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jms="http://www.citrusframework.org/schema/jms/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/testcasehttp://www.citrusframework.org/schema/testcase/citrus-testcase.xsdhttp://www.citrusframework.org/schema/jms/testcasehttp://www.citrusframework.org/schema/jms/testcase/citrus-jms-testcase.xsd">
[...]
</beans>
NowwearereadytousetheJMSfeaturesinourtestcaseinordertopurgesomeJMSqueues.Thiscanbedonewithfollowingactiondefinition:
XMLDSL
<testcasename="purgeTest"><actions><jms:purge-jms-queues><jms:queuename="Some.JMS.QUEUE.Name"/><jms:queuename="Another.JMS.QUEUE.Name"/><jms:queuename="My.JMS.QUEUE.Name"/></jms:purge-jms-queues>
<jms:purge-jms-queuesconnection-factory="connectionFactory"><jms:queuename="Some.JMS.QUEUE.Name"/><jms:queuename="Another.JMS.QUEUE.Name"/>
CitrusReferenceGuide
171PurgeJMSqueues
<jms:queuename="My.JMS.QUEUE.Name"/></jms:purge-jms-queues></actions></testcase>
Noticethatwehavereferencedthejmsnamespacewhenusingthepurge-jms-queuestestaction.
JavaDSLdesigner
@Autowired@Qualifier("connectionFactory")privateConnectionFactoryconnectionFactory;
@CitrusTestpublicvoidpurgeTest()purgeQueues().queue("Some.JMS.QUEUE.Name").queue("Another.JMS.QUEUE.Name");
purgeQueues(connectionFactory).timeout(150L)//customtimeoutinms.queue("Some.JMS.QUEUE.Name").queue("Another.JMS.QUEUE.Name");
JavaDSLrunner
@Autowired@Qualifier("connectionFactory")privateConnectionFactoryconnectionFactory;
@CitrusTestpublicvoidpurgeTest()purgeQueues(action->action.queue("Some.JMS.QUEUE.Name").queue("Another.JMS.QUEUE.Name"));
purgeQueues(action->action.connectionFactory(connectionFactory).timeout(150L)//customtimeoutinms.queue("Some.JMS.QUEUE.Name").queue("Another.JMS.QUEUE.Name"));
CitrusReferenceGuide
172PurgeJMSqueues
PurgingtheJMSqueuesineverytestcaseisquiteexhaustingbecauseeverytestcaseneedstodefineapurgingactionattheverybeginningofthetest.Fortunatelythetestsuitedefinitionofferstaskstorunbefore,betweenandafterthetestcaseswhichshouldeaseupthistasksalot.Thetestsuiteoffersaverysimplewaytopurgethedestinationsbetweenthetests.Seetestsuite-before-testformoreinformationaboutthis.
AsyoucanseeinthenextexampleitisquiteeasytospecifyagroupofdestinationsintheSpringconfigurationthatgetpurgedbeforeatestisexecuted.
<citrus:before-testid="purgeBeforeTest"><citrus:actions><jms:purge-jms-queues><jms:queuename="Some.JMS.QUEUE.Name"/><jms:queuename="Another.JMS.QUEUE.Name"/></jms:purge-jms-queues></citrus:actions></citrus:before-test>
NotePleasekeepinmindthattheJMSrelatedconfigurationcomponentsinCitrusbelongtoaseparateXMLnamespacejms:.WehavetoaddthisnamespacedeclarationtoeachtestcaseXMLandSpringbeanXMLconfigurationfileasdescribedattheverybeginningofthissection.
Thesyntaxforpurgingthedestinationsisthesameasweuseditinsidethetestcase.SonowweareabletopurgeJMSdestinationswithgivendestinationnames.ButsometimeswedonotwanttorelyonqueueortopicnamesasweretrievedestinationsoverJNDIforinstance.WecandealwithdestinationscomingfromJNDIlookuplikefollows:
<jee:jndi-lookupid="jmsQueueHelloRequestIn"jndi-name="jms/jmsQueueHelloRequestIn"/><jee:jndi-lookupid="jmsQueueHelloResponseOut"jndi-name="jms/jmsQueueHelloResponseOut"/>
<citrus:before-testid="purgeBeforeTest"><citrus:actions><jms:purge-jms-queues><jms:queueref="jmsQueueHelloRequestIn"/><jms:queueref="jmsQueueHelloResponseOut"/></jms:purge-jms-queues></citrus:actions></citrus:before-test>
CitrusReferenceGuide
173PurgeJMSqueues
Wejustusetheattribute'ref'insteadof'name'andCitrusislookingforabeanreferenceforthatidentifierthatresolvestoaJMSdestination.YoucanusetheJNDIbeanreferencesinsideatestcase,too.
XMLDSL
<testcasename="purgeTest"><actions><jms:purge-jms-queues><jms:queueref="jmsQueueHelloRequestIn"/><jms:queueref="jmsQueueHelloResponseOut"/></jms:purge-jms-queues></actions></testcase>
OfcourseyoucanusequeueobjectreferencesalsoinJavaDSLtestcases.HereweeasilycanuseSpring'sdependencyinjectionwithautowiringtogettheobjectreferencesfromtheIoCcontainer.
JavaDSLdesigner
@Autowired@Qualifier("jmsQueueHelloRequestIn")privateQueuejmsQueueHelloRequestIn;
@Autowired@Qualifier("jmsQueueHelloResponseOut")privateQueuejmsQueueHelloResponseOut;
@CitrusTestpublicvoidpurgeTest()purgeQueues().queue(jmsQueueHelloRequestIn).queue(jmsQueueHelloResponseOut);
JavaDSLrunner
@Autowired@Qualifier("jmsQueueHelloRequestIn")privateQueuejmsQueueHelloRequestIn;
@Autowired@Qualifier("jmsQueueHelloResponseOut")privateQueuejmsQueueHelloResponseOut;
CitrusReferenceGuide
174PurgeJMSqueues
@CitrusTestpublicvoidpurgeTest()purgeQueues(action->action.queue(jmsQueueHelloRequestIn).queue(jmsQueueHelloResponseOut));
NoteYoucanmixqueuenameandqueueobjectreferencesasyoulikewithinonesinglepurgequeuetestaction.
CitrusReferenceGuide
175PurgeJMSqueues
Purgingmessagechannels
MessagechannelsdefinecentralmessagingdestinationsinCitrus.Thesearenamelyinmemorymessagequeuesholdingmessagesfortestcases.Thesemessagesmaybecomeobsoleteduringatestrun,especiallywhentestcasesfailandstopintheirmessageconsumption.Purgingthesemessagechanneldestinationsisessentialinthesescenariosinordertonotinfluenceupcomingtestcases.Eachtestcaseshouldonlyreceivethosemessagesthatactuallyrefertothetestmodel.Thereforeitisagoodideatopurgeallmessagechanneldestinationsbetweenthetestcases.Obsoletemessagesthatgetstuckinamessagechanneldestinationforsomereasonarethenremovedsothatupcomingtestcasearenotbroken.
Followingactiondefinitionpurgesallmessagesfromalistofmessagechannels:
XMLDSL
<testcasename="purgeChannelTest"><actions><purge-channel><channelname="someChannelName"/><channelname="anotherChannelName"/></purge-channel>
<purge-channel><channelref="someChannel"/><channelref="anotherChannel"/></purge-channel></actions></testcase>
AsyoucanseethetestactionsupportschannelnamesaswellaschannelreferencestoSpringbeaninstances.WhenusingchannelreferencesyourefertotheSpringbeanidornameinyourapplicationcontext.
TheJavaDSLworksquitesimilarasyoucanreadfromnextexamples:
JavaDSLdesigner
@Autowired@Qualifier("channelResolver")privateDestinationResolver<MessageChannel>channelResolver;
@CitrusTestpublicvoidpurgeTest()
CitrusReferenceGuide
176Purgechannels
purgeChannels().channelResolver(channelResolver).channelNames("ch1","ch2","ch3").channel("ch4");
JavaDSLrunner
@Autowired@Qualifier("channelResolver")privateDestinationResolver<MessageChannel>channelResolver;
@CitrusTestpublicvoidpurgeTest()purgeChannels(action->action.channelResolver(channelResolver).channelNames("ch1","ch2","ch3").channel("ch4"));
Thechannelresolverreferenceisoptional.BydefaultCitruswillautomaticallyuseaSpringapplicationcontextchannelresolversoyoujusthavetousetherespectiveSpringbeannamesthatareconfiguredintheSpringapplicationcontext.Howeversettingacustomchannelresolvermaybeadequateforyouinsomespecialcases.
WhilespeakingofSpringapplicationcontextbeanreferencesthenextexampleusessuchbeanreferencesforchannelstopurge.
JavaDSLdesigner
@Autowired@Qualifier("channel1")privateMessageChannelchannel1;
@Autowired@Qualifier("channel2")privateMessageChannelchannel2;
@Autowired@Qualifier("channel3")privateMessageChannelchannel3;
@CitrusTestpublicvoidpurgeTest()purgeChannels().channels(channel1,channel2).channel(channel3);
CitrusReferenceGuide
177Purgechannels
JavaDSLrunner
@Autowired@Qualifier("channel1")privateMessageChannelchannel1;
@Autowired@Qualifier("channel2")privateMessageChannelchannel2;
@Autowired@Qualifier("channel3")privateMessageChannelchannel3;
@CitrusTestpublicvoidpurgeTest()purgeChannels(action->action.channels(channel1,channel2).channel(channel3));
Messageselectorsenableyoutoselectivelyremovemessagesfromthedestination.Allmessagesthatpassthemessageselectionlogicgetdeletedtheothermessageswillremainunchangedinsidethechanneldestination.ThemessageselectorisaSpringbeanthatimplementsaspecialmessageselectorinterface.Apossibleimplementationcouldbeaselectordeletingallmessagesthatareolderthanfiveseconds:
importorg.springframework.messaging.Message;importorg.springframework.integration.core.MessageSelector;
publicclassTimeBasedMessageSelectorimplementsMessageSelector
publicbooleanaccept(Message<?>message)if(System.currentTimeMillis()-message.getHeaders().getTimestamp()>5000)returnfalse;elsereturntrue;
NoteThemessageselectorreturnsfalseforthosemessagesthatshouldbedeletedfromthechannel!
CitrusReferenceGuide
178Purgechannels
YousimplydefinethemessageselectorasanewSpringbeanintheCitrusapplicationcontextandreferenceitinyourtestactionproperty.
<beanid="specialMessageSelector"class="com.consol.citrus.special.TimeBasedMessageSelector"/>
Nowletushavealookathowyoureferencetheselectorinyourtestcase:
XMLDSL
<purge-channelsmessage-selector="specialMessageSelector"><channelname="someChannelName"/><channelname="anotherChannelName"/></purge-channels>
JavaDSLdesigner
@Autowired@Qualifier("specialMessageSelector")privateMessageSelectorspecialMessageSelector;
@CitrusTestpublicvoidpurgeTest()purgeChannels().channelNames("ch1","ch2","ch3").selector(specialMessageSelector);
JavaDSLrunner
@Autowired@Qualifier("specialMessageSelector")privateMessageSelectorspecialMessageSelector;
@CitrusTestpublicvoidpurgeTest()purgeChannels(action->action.channelNames("ch1","ch2","ch3").selector(specialMessageSelector));
CitrusReferenceGuide
179Purgechannels
IntheexamplesaboveweuseamessageselectorimplementationthatgetsinjectedviaSpringIoCcontainer.
Purgingchannelsineachtestcaseeverytimeisquiteexhaustingbecauseeverytestcaseneedstodefineapurgingactionattheverybeginningofthetest.Amorestraightforwardapproachwouldbetointroducesomepurgingactionwhichisautomaticallyexecutedbeforeeachtest.FortunatelytheCitrustestsuiteoffersaverysimplewaytodothis.Itisdescribedintestsuite-before-test.
Whenusingthespecialactionsequencebeforetestcasesweareabletopurgechanneldestinationseverytimeatestcaseexecutes.SeetheupcomingexampletofindouthowtheactionisdefinedintheSpringconfigurationapplicationcontext.
<citrus:before-testid="purgeBeforeTest"><citrus:actions><purge-channel><channelname="fooChannel"/><channelname="barChannel"/></purge-channel></citrus:actions></citrus:before-test>
Justusethisbefore-testbeanintheSpringbeanapplicationcontextandthepurgechannelactionisactive.Obsoletemessagesthatarewaitingonthemessagechannelsforconsumptionarepurgedbeforethenexttestinlineisexecuted.
TipPurgingmessagechannelsbecomesalsoveryinterestingwhenworkingwithserverinstancesinCitrus.Eachservercomponentautomaticallyhasaninboundmessagechannelwhereincomingmessagesarestoredtointernally.Soifyouneedtocleanupaserverthathasalreadystoredsomeincomingmessagesyoucandothiseasilybypurgingtheinternalmessagechannel.ThemessagechannelfollowsanamingconventionserverName.inboundwhereserverNameistheSpringbeannameoftheCitrusserverendpointcomponent.Ifyoupurgethisinternalchannelinabeforetestnatureyouaresurethatobsoletemessagesonaserverinstancegetpurgedbeforeeachtestisexecuted.
CitrusReferenceGuide
180Purgechannels
Purgingendpoints
Citrusworkswithmessageendpointswhensendingandreceivingmessages.Ingeneralendpointscanalsoqueuemessages.ThisisespeciallythecasewhenusingJMSmessageendpointsoranyserverendpointcomponentinCitrus.Theseareinmemorymessagequeuesholdingmessagesfortestcases.Thesemessagesmaybecomeobsoleteduringatestrun,especiallywhenatestcasethatwouldconsumethemessagesfails.Deletingallmessagesfromamessageendpointisthereforeausefultaskandisessentialinsuchscenariossothatupcomingtestcasesarenotinfluenced.Eachtestcaseshouldonlyreceivethosemessagesthatactuallyrefertothetestmodel.Thereforeitisagoodideatopurgeallmessageendpointdestinationsbetweenthetestcases.Obsoletemessagesthatgetstuckinamessageendpointdestinationforsomereasonarethenremovedsothatupcomingtestcasearenotbroken.
Followingactiondefinitionpurgesallmessagesfromalistofmessageendpoints:
XMLDSL
<testcasename="purgeEndpointTest"><actions><purge-endpoint><endpointname="someEndpointName"/><endpointname="anotherEndpointName"/></purge-endpoint>
<purge-endpoint><endpointref="someEndpoint"/><endpointref="anotherEndpoint"/></purge-endpoint></actions></testcase>
AsyoucanseethetestactionsupportsendpointnamesaswellasendpointreferencestoSpringbeaninstances.WhenusingendpointreferencesyourefertotheSpringbeannameinyourapplicationcontext.
TheJavaDSLworksquitesimilar-havealook:
JavaDSLdesigner
@Autowired@CitrusTestpublicvoidpurgeTest()
CitrusReferenceGuide
181Purgeendpoints
purgeEndpoints().endpointNames("endpoint1","endpoint2","endpoint3").endpoint("endpoint4");
JavaDSLrunner
@Autowired@CitrusTestpublicvoidpurgeTest()purgeEndpoints(action->action.endpointNames("endpoint1","endpoint2","endpoint3").endpoint("endpoint4"));
WhenusingtheJavaDSLwecaninjectendpointobjectswithSpringbeancontainerIoC.Thenextexampleusessuchbeanreferencesforendpointsinapurgeaction.
JavaDSLdesigner
@Autowired@Qualifier("endpoint1")privateEndpointendpoint1;
@Autowired@Qualifier("endpoint2")privateEndpointendpoint2;
@Autowired@Qualifier("endpoint3")privateEndpointendpoint3;
@CitrusTestpublicvoidpurgeTest()purgeEndpoints().endpoints(endpoint1,endpoint2).endpoint(endpoint3);
JavaDSLrunner
@Autowired@Qualifier("endpoint1")privateEndpointendpoint1;
@Autowired@Qualifier("endpoint2")
CitrusReferenceGuide
182Purgeendpoints
privateEndpointendpoint2;
@Autowired@Qualifier("endpoint3")privateEndpointendpoint3;
@CitrusTestpublicvoidpurgeTest()purgeEndpoints(action->action.endpoints(endpoint1,endpoint2).endpoint(endpoint3));
Messageselectorsenableyoutoselectivelyremovemessagesfromanendpoint.Allmessagesthatmeetthemessageselectorconditiongetdeletedandtheothermessagesremaininsidetheendpointdestination.ThemessageselectoriseitheranormalStringname-valuerepresentationoramapofkeyvaluepairs:
XMLDSL
<purge-endpoints><selector><value>operation='sayHello'</value></selector><endpointname="someEndpointName"/><endpointname="anotherEndpointName"/></purge-endpoints>
JavaDSLdesigner
@CitrusTestpublicvoidpurgeTest()purgeEndpoints().endpointNames("endpoint1","endpoint2","endpoint3").selector("operation='sayHello'");
JavaDSLrunner
@CitrusTestpublicvoidpurgeTest()purgeEndpoints(action->action.endpointNames("endpoint1","endpoint2","endpoint3").selector("operation='sayHello'"));
CitrusReferenceGuide
183Purgeendpoints
IntheexamplesaboveweuseaStringtorepresentthemessageselectorexpression.Ingeneralthemessageselectoroperatesonthemessageheader.SofollowingonfromthatweremoveallmessagesselectivelythathaveamessageheaderoperationwithitsvaluesayHello.
Purgingendpointsineachtestcaseeverytimeisquiteexhaustingbecauseeverytestcaseneedstodefineapurgingactionattheverybeginningofthetest.Amorestraightforwardapproachwouldbetointroducesomepurgingactionwhichisautomaticallyexecutedbeforeeachtest.FortunatelytheCitrustestsuiteoffersaverysimplewaytodothis.Itisdescribedintestsuite-before-test.
Whenusingthespecialactionsequencebeforetestcasesweareabletopurgeendpointdestinationseverytimeatestcaseexecutes.SeetheupcomingexampletofindouthowtheactionisdefinedintheSpringconfigurationapplicationcontext.
<citrus:before-testid="purgeBeforeTest"><citrus:actions><purge-endpoint><endpointname="fooEndpoint"/><endpointname="barEndpoint"/></purge-endpoint></citrus:actions></citrus:before-test>
Justusethisbefore-testbeanintheSpringbeanapplicationcontextandthepurgeendpointactionisactive.Obsoletemessagesthatarewaitingonthemessageendpointsforconsumptionarepurgedbeforethenexttestinlineisexecuted.
TipPurgingmessageendpointsbecomesalsoveryinterestingwhenworkingwithserverinstancesinCitrus.Eachservercomponentautomaticallyhasaninboundmessageendpointwhereincomingmessagesarestoredtointernally.Citruswillautomaticallyusethisincomingmessageendpointastargetforthepurgeactionsoyoucanjustusetheserverinstanceasyouknowitfromyourconfigurationinanypurgeaction.
CitrusReferenceGuide
184Purgeendpoints
Assertfailure
CitrustestactionsfailwithJavaexceptionsanderrormessages.Thisgivesyoutheopportunitytoexpectanactiontofailduringtestexecution.YoucansimpleassertaJavaexceptiontobethrownduringexecution.Seetheexampleforanassertactiondefinitioninatestcase:
XMLDSL
<testcasename="assertFailureTest"><actions><assertexception="com.consol.citrus.exceptions.CitrusRuntimeException"message="Unknownvariable$date"><when><echo><message>Currentdateis:$date</message></echo></when></assert></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidassertTest()assertException().exception(com.consol.citrus.exceptions.CitrusRuntimeException.class).message("Unknownvariable$date").when(echo("Currentdateis:$date"));
NoteNotethattheassertactionrequiresanexception.Incasenoexceptionisthrownbytheembeddedtestactiontheassertionandthetestcasewillfail!
Theassertactionalwayswrapsasingletestaction,whichisthenmonitoredforfailure.Incasethenestedtestactionfailswitherroryoucanvalidatetheerrorinitstypeanderrormessage(optional).Thefailurehastofittheexpectedoneexactlyotherwisetheassertionfailsitself.
ImportantImportanttonoticeisthefactthatassertedexceptionsdonotcausefailureofthetestcase.Asyouexceptthefailuretohappenthetestcontinueswithitsworkoncetheassertionisdonesuccessfully.
CitrusReferenceGuide
185Assert
CitrusReferenceGuide
186Assert
Catchexceptions
InthepreviouschapterwehaveseenhowtoexpectfailuresinCitruswithassertaction.Nowtheassertactionisdesignedforsingleactionstobemonitoredandforfailurestobeexpectedinanycase.The'catch'actionincontrarycanholdseveralnestedtestactionsandexceptionfailureisoptional.
Thenestedactionsareerrorproofforthechosenexceptiontype.Thismeanspossibleexceptionsarecaughtandignored-thetestcasewillnotfailforthisexceptiontype.Butonlyforthisparticularexceptiontype!Otherexceptiontypesthatoccurduringexecutiondocausethetesttofailasusual.
XMLDSL
<testcasename="catchExceptionTest"><actions><catchexception="com.consol.citrus.exceptions.CitrusRuntimeException"><echo><message>Currentdateis:$date</message></echo></catch></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidcatchTest()catchException().exception(CitrusRuntimeException.class).when(echo("Currentdateis:$date"));
ImportantNotethatthereisnovalidationavailableinacatchblock.Socatchingexceptionsisjusttomakeatestmorestabletowardserrorsthatcanoccur.Thecaughtexceptiondoesnotcauseanyfailureinthetest.Thetestcasemaycontinuewithexecutionasiftherewasnotfailure.Alsonoticethatthecatchactionisalsohappywhennoexceptionatallisraised.Incontrarytothattheassertactionrequirestheexceptionandanassertactionisfailinginpositiveprocessing.
Catchingexceptionslikethismayonlyfittoveryerrorproneactionblockswherefailuresdonotharmthetestcasesuccess.Otherwiseafailureinatestactionshouldalwaysreflecttothewholetestcasetofailwitherrors.
CitrusReferenceGuide
187Catch
NoteJavadevelopersmightaskwhynotusetry-catchJavablockinstead?Theanswerissimpleyetveryimportanttounderstand.ThetestmethodiscalledbytheJavaDSLtestcasebuilderforbuildingtheCitrustest.Thiscanbereferredtoasthedesigntimeofthetest.Afterthebuildingtestmethodwasprocessedthetestgetsexecuted,whichcanbecalledtheruntimeofthetest.Thismeansthatatry-catchblockwithinthedesigntimemethodwillneverperformduringthetestrun.TheonlyreliablewaytoaddthecatchcapabilitytothetestaspartofthetestcaseruntimeistousetheCitrustestactionwhichgetsexecutedduringtestruntime.
CitrusReferenceGuide
188Catch
RunningApacheAntbuildtargets
Theactionloadsabuild.xmlAntfileandexecutesoneormoretargetsintheAntproject.ThetargetisexecutedwithoptionalbuildpropertiespassedtotheAntrun.TheAntbuildoutputisloggedwithCitrusloggerandthetestcasesuccessisboundtotheAntbuildsuccess.ThismeansincasetheAntbuildfailsforsomereasonthetestcasewillalsofailwithbuildexceptionaccordingly.
SeethisbasicAntrunexampletoseehowitworkswithinyourtestcase:
XMLDSL
<testcasename="AntRunTest"><variables><variablename="today"value="citrus:currentDate()"/></variables><actions><antbuild-file="classpath:com/consol/citrus/actions/build.xml"><executetarget="sayHello"/><properties><propertyname="date"value="$today"/><propertyname="welcomeText"value="Hello!"/></properties></ant></actions></testcase>
JavaDSLdesigner
@CitrusTestpublicvoidantRunTest()variable("today","citrus:currentDate()");
antrun("classpath:com/consol/citrus/actions/build.xml").target("sayHello").property("date","$today").property("welcomeText","$Hello!");
JavaDSLrunner
@CitrusTestpublicvoidantRunTest()variable("today","citrus:currentDate()");
CitrusReferenceGuide
189Antrun
antrun(action->action.buildFilePath("classpath:com/consol/citrus/actions/build.xml").target("sayHello").property("date","$today").property("welcomeText","$Hello!"));
Therespectivebuild.xmlAntfilemustprovidethetargettocall.Forexample:
<projectname="citrus-build"default="sayHello"><propertyname="welcomeText"value="WelcometoCitrus!"></property>
<targetname="sayHello"><echomessage="$welcomeText-Todayis$date"></echo></target>
<targetname="sayGoodbye"><echomessage="Goodbyeeverybody!"></echo></target></project>
AsyoucanseeyoucanpasscustombuildpropertiestotheAntbuildexecution.ExistingAntbuildpropertiesarereplacedandyoucanusethepropertiesinyourbuildfileasusual.
Youcanalsocallmultipletargetswithinonesinglebuildrunbyusingacommaseparatedlistoftargetnames:
XMLDSL
<testcasename="AntRunTest"><variables><variablename="today"value="citrus:currentDate()"/></variables><actions><antbuild-file="classpath:com/consol/citrus/actions/build.xml"><executetargets="sayHello,sayGoodbye"/><properties><propertyname="date"value="$today"/></properties></ant></actions></testcase>
JavaDSLdesigner
CitrusReferenceGuide
190Antrun
@CitrusTestpublicvoidantRunTest()variable("today","citrus:currentDate()");
antrun("classpath:com/consol/citrus/actions/build.xml").targets("sayHello","sayGoodbye").property("date","$today");
JavaDSLrunner
@CitrusTestpublicvoidantRunTest()variable("today","citrus:currentDate()");
antrun(action->action.buildFilePath("classpath:com/consol/citrus/actions/build.xml").targets("sayHello","sayGoodbye").property("date","$today"));
Thebuildpropertiescanliveinexternalfileresourceasanalternativetotheinlinepropertydefinitions.Youjusthavetousetherespectivefileresourcepathandallnestedpropertiesgetloadedasbuildproperties.
Inadditiontothatyoucanalsodefineacustombuildlistener.ThebuildlistenermustimplementtheAntAPIinterfaceorg.apache.tools.ant.BuildListener.DuringtheAntbuildrunthebuildlisteneriscalledwithseveralcallbackmethods(e.g.buildStarted(),buildFinished(),targetStarted(),targetFinished(),...).ThisishowyoucanaddadditionallogictotheAntbuildrunfromCitrus.Acustombuildlistenercouldmanagethefailstateofyourtestcase,inparticularbyraisingsomeexceptionforcingthetestcasetofailaccordingly.
XMLDSL
<testcasename="AntRunTest"><actions><antbuild-file="classpath:com/consol/citrus/actions/build.xml"build-listener="customBuildListener"><executetarget="sayHello"/><propertiesfile="classpath:com/consol/citrus/actions/build.properties"/></ant></actions></testcase>
CitrusReferenceGuide
191Antrun
JavaDSLdesigner
@AutowiredprivateBuildListenercustomBuildListener;
@CitrusTestpublicvoidantRunTest()antrun("classpath:com/consol/citrus/actions/build.xml").target("sayHello").propertyFile("classpath:com/consol/citrus/actions/build.properties").listener(customBuildListener);
JavaDSLrunner
@AutowiredprivateBuildListenercustomBuildListener;
@CitrusTestpublicvoidantRunTest()antrun(action->action.buildFilePath("classpath:com/consol/citrus/actions/build.xml").target("sayHello").propertyFile("classpath:com/consol/citrus/actions/build.properties").listener(customBuildListener));
ThecustomBuildListenerusedintheexampleaboveshouldreferenceaSpringbeanintheCitrusapplicationcontext.Thebeanimplementstheinterfaceorg.apache.tools.ant.BuildListenerandcontrolstheAntbuildrun.
CitrusReferenceGuide
192Antrun
Start/Stopserverinstances
Citrusisworkingwithservercomponentsthatarestartedandstoppedwithinatestrun.ThiscanbeaHttpserverorsomeSMTPmailserverforinstance.UsuallytheCitrusservercomponentsareautomaticallystartedwhenCitrusisstartingandrespectivelystoppedwhenCitrusisshuttingdown.Sometimesitmightbehelpfultoexplicitlystartandstopaserverinstancewithinyourtestcase.Hereyoucanusespecialstartandstoptestactionsinsideyourtest.Thisisagoodwaytotestdowntimescenariosofinterfacepartnerswithrespectiveerrorhandlingwhenconnectionstoserversarelost
Letmeexplainwithasimplesampletestcase:
XMLDSL
<testcasename="sleepTest"><actions><startserver="myMailServer"/>
<sleep/>
<stopserver="myMailServer"/></actions></testcase>
ThestartandstopservertestactionreceiveaservernamewhichreferencesaSpringbeancomponentoftypecom.consol.citrus.server.ServerinyourbasicSpringapplicationcontext.Theserverinstanceisstartedorstoppedwithinthetestcase.Asyoucanseeinthenextlistingwecanalsostartandstopmultipleserverinstanceswithinasingletestaction.
<testcasename="sleepTest"><actions><start><servers><servername="myMailServer"/><servername="myFtpServer"/></servers></start>
<sleep/>
<stop><servers><servername="myMailServer"/>
CitrusReferenceGuide
193Manageserver
<servername="myFtpServer"/></servers></stop></actions></testcase>
WhenusingtheJavaDSLthebestwaytoreferenceaserverinstanceistoautowiretheSpringbeanviadependencyinjection.TheSpringframeworktakescaseoninjectingtheproperSpringbeancomponentdefinedintheSPringapplicationcontext.ThiswayyoucaneasilystartandstopserverinstanceswithinJavaDSLtestcases.
JavaDSLdesignerandrunner
@Autowired@Qualifier("myFtpServer")privateFtpServermyFtpServer;
@CitrusTestpublicvoidstartStopServerTest()start(myFtpServer);
sleep();
stop(myFtpServer);
NoteStartingandstoppingserverinstancesisasynchronoustestaction.Thismeansthatyourtestcaseiswaitingfortheservertostartbeforeothertestactionstakeplace.Startuptimesandshutdownofserverinstancesmaydelayyourtestaccordingly.
AsyoucanseestartingandstoppingCitrusserverinstancesisveryeasy.Youcanalsowriteyourownserverimplementationsbyimplementingtheinterfacecom.consol.citrus.server.Server.Allcustomserverimplementationscanthenbestartedandstoppedduringatestcase.
CitrusReferenceGuide
194Manageserver
StopTimer
Theactioncanbeusedforstoppingeitheraspecifictimer(containers-timer)oralltimersrunningwithinatest.Thisactionisusefulwhentimersarestartedinthebackground(usingparallelorfork=true)andyouwishtostopthesetimersattheendofthetest.Someexamplesofusingthisactionareprovidedbelow:
XMLDSL
<testcasename="timerTest"><actions><timerid="forkedTimer"fork="true"><sleepmilliseconds="50"/></timer>
<timerfork="true"><sleepmilliseconds="50"/></timer>
<timerrepeatCount="5"><sleepmilliseconds="50"/></timer>
<stop-timertimerId="forkedTimer"/></actions><finally><stop-timer/></finally></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidtimerTest()
timer().timerId("forkedTimer").fork(true).actions(sleep(50L));
timer().fork(true).actions(sleep(50L));
CitrusReferenceGuide
195Stoptimer
timer().repeatCount(5).actions(sleep(50L));
stopTimer("forkedTimer")
doFinally().actions(stopTimer());
Intheaboveexample3timersarestarted,thefirst2inthebackgroundandthethirdinthetestexecutionthread.Timer#3hasarepeatCountsetto5soitwillterminateautomaticallyafter5runs.Timer#1and#2howeverhavenorepeatCountsetsotheywillexecuteuntiltheyaretoldtostop.
Timer#1isstoppedexplicitlyusingthefirststopTimeraction.HerethestopTimeractionincludesthenameofthetimertostop.Thisisconvenientwhenyouwishtoterminateaspecifictimer.HoweversincenotimerIdwassetfortimer#2,youcanterminatethis(andallothertimers)usingthe'stopTimer'actionwithnoexplicittimerIdset.
CitrusReferenceGuide
196Stoptimer
Includingcustomtestactions
Nowwehavealookattheopportunitytoaddcustomtestactionstothetestcaseflow.Letusstartthissectionwithanexample:
XMLDSL
<testcasename="ActionReferenceTest"><actions><actionreference="cleanUpDatabase"/><actionreference="mySpecialAction"/></actions></testcase>
ThegenericelementreferencesSpringbeansthatimplementtheJavainterfacecom.consol.citrus.TestAction.ThisisaveryfastwaytoaddyourownactionimplementationstoaCitrustestcase.ThiswayyoucaneasilyimplementyourownactionsinJavaandincludethemintothetestcase.
Intheexampleabovethecalledactionsarespecialdatabasecleanupimplementations.TheactionsaredefinedasSpringbeansintheCitrusconfigurationandgetreferencedbytheirbeannameorid.
<beanid="cleanUpDatabase"class="my.domain.citrus.actions.SpecialDatabaseCleanupAction"><propertyname="dataSource"ref="testDataSource"/></bean>
TheSpringapplicationcontextholdsyourcustombeanimplementations.YoucansetpropertiesandusethefullSpringpowerwhileimplementingyourcustomtestactioninJava.LetushavealookonhowsuchaJavaclassmaylooklike.
importcom.consol.citrus.actions.AbstractTestAction;importcom.consol.citrus.context.TestContext;
publicclassSpecialDatabaseCleanupActionextendsAbstractTestAction
@AutowiredprivateDataSourcedataSource;
@OverridepublicvoiddoExecute(TestContextcontext)JdbcTemplatejdbcTemplate=newJdbcTemplate(dataSource);
CitrusReferenceGuide
197Genericaction
jdbcTemplate.execute("...");
AllyouneedtodoinyourJavaclassistoimplementtheCitruscom.consol.citrus.TestActioninterface.Theabstractclasscom.consol.citrus.actions.AbstractTestActionmayhelpyoutostartwithyourcustomtestactionimplementationasitprovidesbasicmethodimplementationssoyoujusthavetoimplementthedoExecute()method.
WhenusingtheJavatestcaseDSLyouarealsoquitecomfortablewithincludingyourcustomtestactions.
JavaDSLdesignerandrunner
@AutowiredprivateSpecialDatabaseCleanupActioncleanUpDatabaseAction;
@CitrusTestpublicvoidgenericActionTest()echo("Nowlet'sincludeourspecialtestaction");
action(cleanUpDatabaseAction);
echo("That'sit!");
Usinganonymousclassimplementationsisalsopossible.
JavaDSLdesignerandrunner
@CitrusTestpublicvoidgenericActionTest()echo("Nowlet'scallourspecialtestactionanonymously");
action(newAbstractTestAction()publicvoiddoExecute(TestContextcontext)//dosomething);
echo("That'sit!");
CitrusReferenceGuide
198Genericaction
CitrusReferenceGuide
199Genericaction
TemplatesTemplatesgroupactionsequencestoalogicalunit.Youcanthinkoftemplatesasreusablecomponentsthatareusedinseveraltests.Themaintenanceismuchmoreeffectivebecausethetemplatesarereferencedseveraltimes.
Thetemplatealwayshasauniquename.Insideatestcasewecallthetemplatebythisuniquename.Havealookatafirstexample:
<templatename="doCreateVariables"><create-variables><variablename="var"value="123456789"/></create-variables>
<call-templatename="doTraceVariables"/></template>
<templatename="doTraceVariables"><echo><message>Currenttimeis:$time</message></echo>
<trace-variables/></template>
Thecodeexampleabovedescribestwotemplatedefinitions.Templatesholdasequenceoftestactionsorcallothertemplatesthemselvesasseenintheexampleabove.
NoteTheactioncallsothertemplatesbytheirname.ThecalledtemplatenotnecessarilyhastobelocatedinthesametestcaseXMLfile.ThetemplatemightbedefinedinaseparateXMLfileotherthanthetestcaseitself:
XMLDSL
<testcasename="templateTest"><variables><variablename="myTime"value="citrus:currentDate()"/></variables><actions><call-templatename="doCreateVariables"/>
<call-templatename="doTraceVariables"><parametername="time"value="$myTime">
CitrusReferenceGuide
200Templates
</call-template></actions></testcase>
JavaDSLdesigner
@CitrusTestpublicvoidtemplateTest()variable("myTime","citrus:currentDate()");
applyTemplate("doCreateVariables");
applyTemplate("doTraceVariables").parameter("time","$myTime");
JavaDSLrunner
@CitrusTestpublicvoidtemplateTest()variable("myTime","citrus:currentDate()");
applyTemplate(template->template.name("doCreateVariables"));
applyTemplate(template->template.name("doTraceVariables").parameter("time","$myTime"));
Thereisanopenquestionwhendealingwithtemplatesthataredefinedsomewhereelseoutsidethetestcase.Howtohandlevariables?Atemplatesmayusedifferentvariablenamesthenthetestandviceversa.Nodoubtthetemplatewillfailassoonasspecialvariableswithrespectivevaluesarenotpresent.Unknownvariablescausethetemplateandthewholetesttofailwitherrors.
Soafirstapproachwouldbetoharmonizevariableusageacrosstemplatesandtestcases,sothattemplatesandtestcasesdousethesamevariablenaming.Butthisapproachmightleadtohighcalibrationeffort.Thereforetemplatessupportparameterstosolvethisproblem.Whenatemplateiscalledthecallingactorisabletosetsomeparameters.Letusdiscussanexampleforthisissue.
Thetemplate"doDateCoversion"inthenextsampleusesthevariable$date.Thecallingtestcasecansetthisvariableasaparameterwithoutactuallydeclaringthevariableinthetestitself:
CitrusReferenceGuide
201Templates
<call-templatename="doDateCoversion"><parametername="date"value="$sampleDate"></call-template>
ThevariablesampleDateisalreadypresentinthetestcaseandgetstranslatedintothedateparameter.Followingfromthatthetemplateworksfinealthoughtestandtemplatedoworkondifferentvariablenamings.
Withtemplateparametersyouareabletosolvethecalibrationeffortwhenworkingwithtemplatesandvariables.Itisalwaysagoodideatochecktheusedvariables/parametersinsideatemplatewhencallingit.Theremightbeavariablethatisnotdeclaredyetinsideyourtest.Soyouneedtodefinethisvalueasaparameter.
TemplateparametersmaycontainmorecomplexvalueslikeXMLfragments.Thecall-templateactionoffersfollowingCDATAvariationfordefiningcomplexparametervalues:
<call-templatename="printXMLPayload"><parametername="payload"><value><![CDATA[<HelloRequestxmlns="http://www.consol.de/schemas/samples/sayHello.xsd"><Text>HelloSouth$var</Text></HelloRequest>]]></value></parameter></call-template>
ImportantWhenatemplateworksonvariablevaluesandparameterschangestothesevariableswillautomaticallyaffectthevariablesinthewholetest.Soifyouchangeavariable'svalueinsideatemplateandthevariableisdefinedinsidethetestcasethechangeswillaffectthevariableinaglobalcontext.Wehavetobecarefulwiththiswhenexecutingatemplateseveraltimesinatest,especiallyincombinationwithparallelcontainers(seecontainers-parallel).
<parallel><call-templatename="print"><parametername="param1"value="1"/><parametername="param2"value="HelloEurope"/></call-template><call-templatename="print"><parametername="param1"value="2"/><parametername="param2"value="HelloAsia"/></call-template>
CitrusReferenceGuide
202Templates
<call-templatename="print"><parametername="param1"value="3"/><parametername="param2"value="HelloAfrica"/></call-template></parallel>
Inthelistingaboveatemplateprintiscalledseveraltimesinaparallelcontainer.Theparametervalueswillbehandledinaglobalcontext,soitisquitelikelytohappenthatthetemplateinstancesinfluenceeachotherduringexecution.Wemightgetsuchprintmessages:
2.HelloEurope2.HelloAfrica3.HelloAfrica
Indexparametersdonotfitandthemessage'HelloAsia'iscompletelygone.Thisisbecausetemplatesoverwriteparameterstoeachotherastheyareexecutedinparallelatthesametime.Toavoidthisbehaviorweneedtotellthetemplatethatitshouldhandleparametersaswellasvariablesinalocalcontext.Thiswillenforcethateachtemplateinstanceisworkingonadedicatedlocalcontext.Seetheglobal-contextattributethatissettofalseinthisexample:
<templatename="print"global-context="false"><echo><message>$param1.$param2</message></echo></template>
Afterthattemplateinstanceswon'tinfluenceeachotheranymore.Butnoticethatvariablechangesinsidethetemplatethendonotaffectthetestcaseneither.
CitrusReferenceGuide
203Templates
TestbehaviorsTestbehaviorscombineactionsequencestoalogicalunit.ThebehaviordefinesasetoftestactionsthatcanbeappliedtoaJavaDSLtestcase.Followingfromthatyoucansaythatbehaviorsarereusabletestactiontemplates.Themaintenanceismuchmoreeffectivewhenyoureusebasictestactionsinmanytestcases.
ThebehaviorisaseparateJavaDSLclasswithasingleapplymethodthatconfiguresthetestactions.Havealookatthisfirstexample:
JavaDSL
publicclassFooBehaviorextendsAbstractTestBehaviorpublicvoidapply()variable("foo","test");
echo("fooBehavior");
publicclassBarBehaviorextendsAbstractTestBehaviorpublicvoidapply()variable("bar","test");
echo("barBehavior");
AsyoucanseethebehaviorclassisabletousetheCitrusJavaDSLasusual.Eachbehaviorisabletodefinetestvariablesandactions.Inatestcaseyoucanapplythebehaviorsasfollows:
JavaDSL
@CitrusTestpublicvoidbehaviorTest()variable("myTime","citrus:currentDate()");
FooBehaviorfooBehavior=newFooBehavior();applyBehavior(fooBehavior);
applyBehavior(newBarBehavior());
applyBehavior(fooBehavior);
CitrusReferenceGuide
204Testbehaviors
Whendealingwithbehaviorstestactionsaredefinedsomewhereoutsidethetestcase.Howdowehandletestvariables?Abehaviormayusedifferentvariablenamesthenthetestandviceversa.Nodoubtthebehaviorwillfailassoonasspecialvariableswithrespectivevaluesarenotpresent.Unknownvariablescausethebehaviorandthewholetesttofailwitherrors.
Soagoodapproachwouldbetoharmonizevariableusageacrossbehaviorsandtestcases,sothattemplatesandtestcasesdousethesamevariablenaming.Thebehaviorautomaticallyknowsallvariablesinthetestcase.Andalltestvariablescreatedinsidethebehaviorarevisibletothetestcaseafterapplying.
ImportantWhenabehaviorchangesvariablesthiswillautomaticallyaffectthevariablesinthewholetest.Soifyouchangeavariable'svalueinsideabehaviorandthevariableisdefinedinsidethetestcasethechangeswillaffectthevariableinaglobaltestcontext.Thismeanswehavetobecarefulwhenexecutingabehaviorseveraltimesinatest,especiallyincombinationwithparallelcontainers(seecontainers-parallel).
Behaviortypes
ThetestcaseinJavaisabletofolloweitherdesignerorrunnerstrategies.Thismeanswealsohavetwodifferentbehaviortypesfordesignerandrunnerrespectively.Thebehaviorsarelocatedinseparatepackages
com.consol.citrus.dsl.design.AbstractTestBehaviorcom.consol.citrus.dsl.runner.AbstractTestBehavior
Decidewhichbasebehavioryouwanttoextendfromaccordingtoyourtestcasenature.
CitrusReferenceGuide
205Testbehaviors
ContainersSimilartotemplatesacontainerelementholdsonetomanytestactions.Incontrasttothetemplatethecontainerappearsdirectlyinsidethetestcaseactionchain,meaningthatthecontainerisnotreferencedbymorethanonetestcase.
Containersexecutetheembeddedtestactionsinspecificlogic.Thiscanbeanexecutioniniterationforinstance.Combinedifferentcontainerswitheachotherandyouwillbeabletogenerateverypowerfulhierarchicalstructuresinordertocreateacomplexexecutionlogic.Inthefollowingsectionssomepredefinedcontainersaredescribed.
CitrusReferenceGuide
206Containers
Sequential
Thesequentialcontainerexecutestheembeddedtestactionsinstrictsequence.Readersnowmightsearchforthedifferencetothenormalactionchainthatisspecifiedinsidethetestcase.Theactualpowerofsequentialcontainersdoesshowonlyincombinationwithothercontainerslikeiterationsandparallels.Wewillseethislaterwhenhandlingthesecontainers.
Fornowthesequentialcontainerseemsnotverysensational-onemightsayboring-becauseitsimplygroupsapairoftestactionstosequentialexecution.
XMLDSL
<testcasename="sequentialTest"><actions><sequential><trace-time/><sleep/><echo><message>HalloTestFramework</message></echo><trace-time/></sequential></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidsequentialTest()sequential().actions(stopTime(),sleep(1.0),echo("HelloCitrus"),stopTime());
CitrusReferenceGuide
207Sequential
Conditional
Nowwedealwithconditionalexecutionsoftestactions.Nestedactionsinsideaconditionalcontainerareexecutedonlyincaseabooleandexpressionevaluatestotrue.Otherwisethecontainerexecutionisnotperformedatall.
Seesomeexampletofindouthowitworkswiththeconditionalexpressionstring.
XMLDSL
<testcasename="conditionalTest"><variables><variablename="index"value="5"/><variablename="shouldSleep"value="true"/></variables>
<actions><conditionalexpression="$index=5"><sleepseconds="10"/></conditional>
<conditionalexpression="$shouldSleep"><sleepseconds="10"/></conditional>
<conditionalexpression="@assertThat('$shouldSleep','anyOf(is(true),isEmptyString())')@"<sleepseconds="10"/></conditional></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidconditionalTest()variable("index",5);variable("shouldSleep",true);
conditional().when("$index=5")).actions(sleep(10000L));
conditional().when("$shouldSleep")).actions(sleep(10000L)
CitrusReferenceGuide
208Conditional
);
conditional().when("$shouldSleep",anyOf(is("true"),isEmptyString())).actions(sleep(10000L));
Thenestedsleepactionisexecutedincasethevariable$indexisequaltothevalue'5'.Thisconditionalexecutionoftestactionsisusefulwhendealingwithdifferenttestenvironmentssuchasdifferentoperatingsystemsforinstance.Theconditionalcontaineralsosupportsexpressionsthatevaluatetothecharactersequence"true"or"false"asshowninthe$shouldSleepexample.
ThelastconditionalcontainerintheexampleabovemakesuseofHamcrestmatchers.Thematcherevaluatestotrueoffalseandbasedonthatthecontaineractionsareexecutedorskipped.TheHamcrestmatchersareverypowerfulwhenitcomestoevaluationofmultipleconditionsatatime.
CitrusReferenceGuide
209Conditional
Parallel
Parallelcontainersexecutetheembeddedtestactionsconcurrenttoeachother.EveryactioninthiscontainerwillbeexecutedinaseparateJavaThread.Followingexampleshouldclarifytheusage:
XMLDSL
<testcasename="parallelTest"><actions><parallel><sleep/>
<sequential><sleep/><echo><message>1</message></echo></sequential>
<echo><message>2</message></echo>
<echo><message>3</message></echo>
<iteratecondition="ilt=5"index="i"><echo><message>10</message></echo></iterate></parallel></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidparalletTest()parallel().actions(sleep(),sequential().actions(sleep(),echo("1")
CitrusReferenceGuide
210Parallel
),echo("2"),echo("3"),iterate().condition("ilt=5").index("i")).actions(echo("10")));
Sothenormaltestactionprocessingwouldbetoexecuteoneactionafteranother.Asthefirstactionisasleepoffiveseconds,thewholetestprocessingwouldstopandwaitfor5seconds.Thingsaredifferentinsidetheparallelcontainer.Herethedescendingtestactionswillnotwaitbutexecuteatthesametime.
NoteNotethatcontainerscaneasilywrapothercontainers.Theexampleshowsasimplecombinationofsequentialandparallelcontainersthatwillarchiveacomplexexecutionlogic.Actionsinsidethesequentialcontainerwillexecuteoneafteranother.Butactionsinparallelwillbeexecutedatthesametime.
CitrusReferenceGuide
211Parallel
Iterate
Iterationsareverypowerfulelementswhendescribingcomplexlogic.Thecontainerexecutestheembeddedactionsseveraltimes.Thecontainerwillcontinuewithloopingaslongasthedefinedbreakingconditionstringevaluatestotrue.Incasetheconditionevaluatestofalsetheiterationwillbreakanfinishexecution.
XMLDSL
<testcasename="iterateTest"><actions><iterateindex="i"condition="ilt5"><echo><message>indexis:$i</message></echo></iterate></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoiditerateTest()iterate().condition("ilt5").index("i")).actions(echo("indexis:$i"));
Theattribute"index"automaticallydefinesanewvariablethatholdstheactualloopindexstartingat"1".Thisindexvariableisavailableasanormalvariableinsidetheiteratecontainer.Thereforeitispossibletoprintouttheactualloopindexintheechoactionasshownintheaboveexample.
Theconditionstringismandatoryanddescribestheactualendoftheloop.Initeratecontainerstheloopwillbreakincasetheconditionevaluatestofalse.
TheconditionstringcanbeanyBooleanexpressionandsupportsseveraloperators:
lt(lowerthan)
lt=(lowerthanequals)
gt(greaterthan)
CitrusReferenceGuide
212Iterate
gt=(greaterthanequals)
=(equals)
and(logicalcombiningoftwoBooleanvalues)
or(logicalcombiningoftwoBooleanvalues)
()(brackets)
ImportantItisveryimportanttonoticethattheconditionisevaluatedbeforetheveryfirstiterationtakesplace.Theloopthereforecanbeexecuted0-ntimesaccordingtotheconditionvalue.
Nowthebooleanexpressionevaluationasdescribedaboveislimitedtoverybasicoperationsuchaslowerthan,greaterthanandsoon.WealsocanuseHamcrestmatchersinconditionsthatarewaymorepowerfulthanthat.
XMLDSL
<testcasename="iterateTest"><actions><iterateindex="i"condition="@assertThat(lessThan(5))@"><echo><message>indexis:$i</message></echo></iterate></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoiditerateTest()iterate().condition(lessThan(5)).index("i")).actions(echo("indexis:$i"));
IntheexampleaboveweuseHamcrestmatchersascondition.YoucancombineHamcrestmatchersandcreateverypowerfulconditionevaluationshere.
CitrusReferenceGuide
213Iterate
CitrusReferenceGuide
214Iterate
Repeatuntiltrue
Quitesimilartothepreviouslydescribediteratecontainerthisrepeatingcontainerwillexecuteitsactionsinaloopaccordingtoanendingcondition.TheconditiondescribesaBooleanexpressionusingtheoperatorsasdescribedinthepreviouschapter.
NoteTheloopcontinuesitsworkuntiltheprovidedconditionevaluatestotrue.Itisveryimportanttonoticethattherepeatloopwillexecutetheactionsbeforeevaluatingthecondition.Thismeanstheactionsgetexecuted1-ntimes.
XMLDSL
<testcasename="iterateTest"><actions><repeat-until-trueindex="i"condition="(i=3)or(i=5)"><echo><message>indexis:$i</message></echo></repeat-until-true></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidrepeatTest()repeat().until("(igt5)or(i=3)").index("i")).actions(echo("indexis:$i"));
Asyoucanseetherepeatcontainerisonlyexecutedwhentheiteratingconditionexpressionevaluatestofalse.Bythetimetheconditionistrueexecutionisdiscontinued.Youcanusebasiclogicaloperatorssuchasand,orandsoon.
AmorepowerfulwayisgivenbyHamcrestmatchersthataredirectlysupportedinconditionexpressions.
XMLDSL
<testcasename="iterateTest"><actions>
CitrusReferenceGuide
215Repeat
<repeat-until-trueindex="i"condition="@assertThat(anyOf(is(3),is(5))@"><echo><message>indexis:$i</message></echo></repeat-until-true></actions></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidrepeatTest()repeat().until(anyOf(is(3),is(5)).index("i")).actions(echo("indexis:$i"));
TheHamcrestmatcherusagesimplifiesthereadingalot.Anditempowersyoutocombinemorecomplexconditionexpressions.SoIpersonallypreferthissyntax.
CitrusReferenceGuide
216Repeat
Repeatonerroruntiltrue
Thenextloopingcontaineriscalledrepeat-on-error-until-true.Thiscontainerrepeatsagroupofactionsincaseoneembeddedactionfailedwitherror.Incaseofanerrorinsidethecontainertheloopwilltrytoexecuteallembeddedactionsagaininordertoseekforoverallsuccess.Theexecutioncontinuesuntilallembeddedactionswereprocessedsuccessfullyortheendingconditionevaluatestotrueandtheerror-loopwillleadtofinalfailure.
XMLDSL
<testcasename="iterateTest"><actions><repeat-onerror-until-trueindex="i"condition="i=5"><echo><message>indexis:$i</message></echo><fail/></repeat-onerror-until-true></actions></testcase>
JavaDSLdesigner
@CitrusTestpublicvoidrepeatOnErrorTest()repeatOnError(echo("indexis:$i"),fail("Forcelooptofail!")).until("i=5").index("i");
JavaDSLrunner
@CitrusTestpublicvoidrepeatOnErrorTest()repeatOnError().until("i=5").index("i")).actions(echo("indexis:$i"),fail("Forcelooptofail!"));
CitrusReferenceGuide
217RepeatOnError
Inthecodeexampletheerror-loopcontinuesfourtimesastheactiondefinitelyfailsthetest.DuringthefifthiterationThecondition"i=5"evaluatestotrueandtheloopbreaksitsprocessingleadingtoafinalfailureasthetestactionswerenotsuccessful.
NoteTheoverallsuccessofthetestcasedependsontheerrorsituationinsidetherepeat-onerror-until-truecontainer.Incasetheloopbreaksbecauseoffailingactionsandtheloopwilldiscontinueitsworkthewholetestcaseisfailingtoo.Theerrorloopprocessingissuccessfulincaseallembeddedactionswerenotraisinganyerrorsduringaniteration.
Therepeat-on-errorcontaineralsooffersanautomaticsleepmechanism.Thisauto-sleeppropertywillforcethecontainertowaitagivenamountoftimebeforeexecutingthenextiteration.Weusedthismechanismalotwhenvalidatingdatabaseentries.Let'ssaywewanttochecktheexistenceofanorderentryinthedatabase.Unfortunatelythesystemundertestisnotverywellperformingandmayneedsometimetostoretheneworder.Thisamountoftimeisnotpredictable,especiallywhendealingwithdifferenthardwareonourtestenvironments(localtestingvs.servertesting).Followingfromthatourtestcasemayfailunpredictableonlybecauseofruntimeconditions.
Wecanavoidunstabletestcasesthatarebasedontheseruntimeconditionswiththeauto-sleepfunctionality.
XMLDSL
<repeat-onerror-until-trueauto-sleep="1000"condition="i=5"index="i"><echo><sqldatasource="testDataSource"><statement>SELECTCOUNT(1)ASCNT_ORDERSFROMORDERSWHERECUSTOMER_ID='$customerId'</statement><validatecolumn="CNT_ORDERS"value="1"/></sql></echo></repeat-onerror-until-true>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidrepeatOnErrorTest()repeatOnError().until("i=5").index("i").autoSleep(1000)).actions(query(action->action.dataSource(testDataSource)
CitrusReferenceGuide
218RepeatOnError
.statement("SELECTCOUNT(1)ASCNT_ORDERSFROMORDERSWHERECUSTOMER_ID='$customerId'".validate("CNT_ORDERS","1")));
Wesurroundedthedatabasecheckwitharepeat-onerrorcontainerhavingtheauto-sleeppropertysetto1000milliseconds.Therepeatcontainerwilltrytocheckthedatabaseuptofivetimeswithanautomaticsleepof1secondbeforeeveryiteration.Thisgivesthesystemundertestuptofivesecondstimetostorethenewentrytothedatabase.Thetestcaseisverystableandjustfitstothehardwareenvironment.Onslowtestenvironmentsthetestmayneedseveraliterationstosuccessfullyreadthedatabaseentry.Onveryfastenvironmentsthetestmaysucceedrightonthefirsttry.
ImportantWechangedautosleeptimefromsecondstomillisecondswithCitrus2.0release.SoifyouarecomingfrompreviousCitrusversionsbesuretonowusepropermillisecondvalues.
Sofastenvironmentsarenotsloweddownbystaticsleepoperationsandslowerenvironmentsarestillabletoexecutethistestcasewithhighstability.
CitrusReferenceGuide
219RepeatOnError
Timer
Timersareveryusefulcontainerswhenyouwishtoexecuteacollectionoftestactionsseveraltimesatregularintervals.Thetimercomponentgeneratesaneventwhichinturntriggerstheexecutionofthenestedtestactionsassociatedwithtimer.ThiscanbeusefulinanumberoftestscenariosforexamplewhenCitrusneedstosimulateaheartbeatorifyouaredebuggingatestandyouwisttoquerythecontentsofthedatabase,tomentionjustafew.Thefollowingcodesampleshoulddemonstratethepowerandflexibilityoftimers:
XMLDSL
<testcasename="timerTest"><actions><timerid="forkedTimer"interval="100"fork="true"><echo><message>I'mgoingtoruninthebackgroundandletsomeothertestactionsrun(nestedactionrun$forkedTimer-indextimes)</echo><sleepmilliseconds="50"/></timer>
<timerrepeatCount="3"interval="100"delay="50"><sleepmilliseconds="50"/><echo><message>I'mgoingtorepeatthismessage3timesbeforethenexttestactionsareexecuted</echo></timer>
<echo><message>Testalmostcomplete.Makesurealltimersrunninginthebackgroundarestopped</echo></actions><finally><stop-timertimerId="forkedTimer"/></finally></testcase>
JavaDSLdesignerandrunner
@CitrusTestpublicvoidtimerTest()
timer().timerId("forkedTimer")
CitrusReferenceGuide
220Timer
.interval(100L).fork(true).actions(echo("I'mgoingtoruninthebackgroundandletsomeothertestactionsrun(nestedactionrun$forkedTimer-indextimes)"sleep(50L));
timer().repeatCount(3).interval(100L).delay(50L).actions(sleep(50L),echo("I'mgoingtorepeatthismessage3timesbeforethenexttestactionsareexecuted");
echo("Testalmostcomplete.Makesurealltimersrunninginthebackgroundarestopped");
doFinally().actions(stopTimer("forkedTimer"));
Intheaboveexamplethefirsttimer(timerId=forkedTimer)isstartedinthebackground.Bydefaulttimersareruninthecurrentthreadofexecutionbuttostartitinthebackgroundjustuse"fork=true".Every100millisecondsthistimeremitsaneventwhichwillresultinthenestedactionsbeingexecuted.Thenested'echo'actionoutputsthenumberoftimesthistimerhasalreadybeenexecuted.Itdoesthiswiththehelpofan'index'variable,inthisexample$forkedTimer-index,whichisnamedaccordingtothetimeridwiththesuffix'-index'.Nolimitissetonthenumberoftimesthistimershouldrunsoitwillkeeponrunninguntileitheranestedtestactionfailsoritisinstructedtostop(moreonthisbelow).
Thesecondtimerisconfiguredtorun3timeswithadelayof100millisecondsbetweeneachiteration.Usingtheattribute'delay'wecangetthetimerpausefor50millisecondsbeforerunningthenestedactionsforthefirsttime.Thetimerisconfiguredtoruninthecurrentthreadofexecutionsothelasttestaction,the'echo',hastowaitforthistimertocompletebeforeitisexecuted.
Sohowdowetelltheforkedtimertostoprunning?Ifweforgettodothisthetimerwilljustexecuteindefinitely.Tohelpusoutherewecanusethe'stop-timer'action.Byaddingthistothefinallyblockweensurethatthetimerwillbestopped,evenifsome
CitrusReferenceGuide
221Timer
nestedtestactionfails.Wecouldhaveeasilyaddeditasanestedtestaction,totheforkedTimerforexample,butifsomeothertestactionfailedbeforethestop-timerwascalled,thetimerwouldneverstop.
NoteYoucanalsoconfiguretimerstoruninthebackgroundusingthe'parallel'container,ratherthansettingtheattribute'fork'totrue.Usingparallelallowsmorefine-grainedcontrolofthetestandhastheaddedadvantagethatallerrorsgeneratedfromanestertimeractionarevisibletothetestexecuter.Ifanerroroccurswithinthetimerthentheteststatusissettofailed.Usingfork=trueanerrorcausesthetimertostopexecuting,buttheteststatusisnotinfluencedbythiserror.
CitrusReferenceGuide
222Timer
Customcontainers
IncaseyouhaveacustomactioncontainerimplementationyoumightalsowanttouseitinJavaDSL.TheactioncontainersarehandledwithspecialcareintheJavaDSLbecausetheyhavenestedactions.SowhenyoucallatestactioncontainerintheJavaDSLyoualwayshavesomethinglikethis:
JavaDSLdesignerandrunner
@CitrusTestpublicvoidcontainerTest()echo("Thisechoisoutsideoftheactioncontainer");
sequential().actions(echo("Inside"),echo("Insideoncemore"),echo("Andagain:Inside!"));
echo("Thisechoisoutsideoftheactioncontainer");
NowthethreenestedactionsareaddedtotheactionsequentialcontainerratherthantothetestcaseitselfalthoughweareusingthesameactionJavaDSLmethodsasoutsidethecontainer.ThismechanismisonlyworkingbecauseCitrusishandlingtestactioncontainerswithspecialcare.
Acustomtestactioncontainerimplementationcouldlooklikethis:
publicclassReverseActionContainerextendsAbstractActionContainer@OverridepublicvoiddoExecute(TestContextcontext)for(inti=getActions().size();i>0;i--)getActions().get(i-1).execute(context);
Thecontainerlogicisverysimple:Thecontainerexecutesthenestedactionsinreverseorder.AsalreadymentionedCitrusneedstotakespecialcareonallactioncontainerswhenexecutingaJavaDSLtest.Thisiswhyyoushouldnotexecuteacustomtestcontainerimplementationonyourown.
CitrusReferenceGuide
223Custom
@CitrusTestpublicvoidcontainerTest()ReverseActionContainerreverseContainer=newReverseActionContainer();reverseContainer.addTestAction(newEchoAction().setMessage("Foo"));reverseContainer.addTestAction(newEchoAction().setMessage("Bar"));run(reverseContainer);
TheabovecustomcontainerexecutionisgoingtofailwithinternalerrorastheCitrusJavaDSLwasnotabletorecognisetheactioncontainerasitshouldbe.AlsotheEchoActioninstancecreationisnotverycomfortable.InsteadyoucanuseaspecialcontainerJavaDSLsyntaxalsowithyourcustomcontainerimplementation:
@CitrusTestpublicvoidcontainerTest()container(newReverseActionContainer()).actions(echo("Foo"),echo("Bar"));
Thecustomcontainerimplementationnowworksfinewiththeautomaticallynestedechoactions.AndweareabletousetheusualJavaDSLsyntacticsugarfortestactionslikeecho.
Inanextstepweaddacustomsuperclassforallourtestclasseswhichprovidesahelpermethodforthecustomcontainerimplementationinordertohaveaevenmorecomfortablesyntax.
JavaDSLdesignerandrunner
publicclassCustomCitrusBaseTestextendsTestNGCitrusTestDesigner
publicAbstractTestContainerBuilder<ReverseActionContainer>reverse()returncontainer(newReverseActionContainer());
Nowallsubclassescanusethenewreversemethodforcallingthecustomcontainerimplementation.
@CitrusTestpublicvoidcontainerTest()
CitrusReferenceGuide
224Custom
reverse().actions(echo("Foo"),echo("Bar"));
Nice!ThisishowweshouldintegratecustomizedtestactioncontainerstotheCitrusJavaDSL.
CitrusReferenceGuide
225Custom
FinallysectionThischapterdealswithaspecialsectioninsidethetestcasethatisexecutedevenincaseerrorsdidoccurduringthetest.LetssayyouhavestartedaJettywebserverinstanceatthebeginningofthetestcaseandyouneedtoshutdowntheserverwhenthetesthasfinisheditswork.Orasasecondexampleimaginethatyouhavepreparedsomedatainsidethedatabaseatthebeginningofyourtestandyouwanttomakesurethatthedataiscleanedupattheendofthetestcase.
Inbothsituationswemightrunintosomeproblemswhenthetestfailed.Wefacetheproblemthatthewholetestcasewillterminateimmediatelyincaseoferrors.Cleanuptasksattheendofthetestactionchainmaynotbeexecutedcorrectly.
Dirtystatesinsidethedatabaseorstillrunningserverinstancesthenmightcauseproblemsforfollowingtestcases.Toavoidthisproblemsyoushouldusethefinallyblockofthetestcase.Thesectioncontainsactionsthatareexecutedevenincasethetestfails.Usingthisstrategythedatabasecleaningtasksmentionedbeforewillfindexecutionineverycase(successorfailure).
Thefollowingexampleshowshowtousethefinallysectionattheendofatest:
XMLDSL
<testcasename="finallyTest"><variables><variablename="orderId"value="citrus:randomNumber(5)"/><variablename="date"value="citrus:currentDate('dd.MM.yyyy')"/></variables><actions><sqldatasource="testDataSource"><statement>INSERTINTOORDERSVALUES($orderId,1,1,'$date')</statement></sql>
<echo><message>ORDERcreationtime:$date</message></echo></actions><finally><sqldatasource="testDataSource"><statement>
CitrusReferenceGuide
226Finally
DELETEFROMORDERSWHEREORDER_ID='$orderId'</statement></sql></finally></testcase>
IntheexamplethefirstactioncreatesanentryinthedatabaseusinganINSERTstatement.Tobesurethattheentryinthedatabaseisdeletedafterthetest,thefinallysectioncontainstherespectiveDELETEstatementthatisalwaysexecutedregardlessthetestcasestate(successfulorfailed).
OfcourseyoucanalsousethefinallyblockintheJavatestcaseDSL.Findfollowingexampletoseehowitworks:
JavaDSLdesigner
@CitrusTestpublicvoidfinallySectionTest()variable("orderId","citrus:randomNumber(5)");variable("date","citrus:currentDate('dd.MM.yyyy')");
sql(dataSource).statement("INSERTINTOORDERSVALUES($orderId,1,1,'$date')");
echo("ORDERcreationtime:citrus:currentDate('dd.MM.yyyy')");
doFinally(sql(dataSource).statement("DELETEFROMORDERSWHEREORDER_ID='$orderId'"));
JavaDSLrunner
@CitrusTestpublicvoidfinallySectionTest()variable("orderId","citrus:randomNumber(5)");variable("date","citrus:currentDate('dd.MM.yyyy')");
sql(action->action.dataSource(dataSource).statement("INSERTINTOORDERSVALUES($orderId,1,1,'$date')"));
echo("ORDERcreationtime:citrus:currentDate('dd.MM.yyyy')");
doFinally().actions(sql(action->action.dataSource(dataSource).statement("DELETEFROMORDERSWHEREORDER_ID='$orderId'");
CitrusReferenceGuide
227Finally
NoteJavadevelopersmightaskwhynotusetry-finallyJavablockinstead?Theanswerissimpleyetveryimportanttounderstand.The@CitrusTestannotatedmethodiscalledatdesigntimeofthetestcase.Themethodbuildsthetestcaseafterwardsthetestisexecutedatruntime.Thismeansthatatry-finallyblockwithinthe@CitrusTestannotatedmethodwillneverperformduringthetestrunbutatdesigntimebeforethetestgetsexecuted.ThisiswhywehavetoaddthefinallysectionaspartofthetestcasewithdoFinally().
CitrusReferenceGuide
228Finally
JMSsupportCitrusprovidessupportforsendingandreceivingJMSmessages.Wehavetoseparatebetweensynchronousandasynchronouscommunication.SointhischapterweexplainhowtosetupJMSmessageendpointsforsynchronousandasynchronousoutboundandinboundcommunication
NoteTheJMScomponentsinCitrusarekeptinaseparateMavenmodule.IfnotalreadydonesoyouhavetoincludethemoduleasMavendependencytoyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-jms</artifactId><version>2.7.1</version></dependency>
Citrusprovidesa"citrus-jms"configurationnamespaceandschemadefinitionforJMSrelatedcomponentsandfeatures.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitrusJMSconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus-jms="http://www.citrusframework.org/schema/jms/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/jms/confighttp://www.citrusframework.org/schema/jms/config/citrus-jms-config.xsd">
[...]
</beans>
AfterthatyouareabletousecustomizedCitrusXMLelementsinordertodefinetheSpringbeans.
JMSendpoints
CitrusReferenceGuide
229Jms
BydefaultCitrusJMSendpointsareasynchronous.Soletusfirstofalldealwithasynchronousmessagingwhichmeansthatwewillnotwaitforanyresponsemessageaftersendingorreceivingamessage.
ThetestcaseitselfshouldnotknowaboutJMStransportdetailslikequeuenamesorconnectioncredentials.ThisinformationisstoredintheendpointcomponentconfigurationthatlivesinthebasicSpringconfigurationfileinCitrus.SoletushavealookatasimpleJMSmessageendpointconfigurationinCitrus.
<citrus-jms:endpointid="helloServiceQueueEndpoint"destination-name="Citrus.HelloService.Request.Queue"timeout="10000"/>
TheendpointcomponentreceivesanuniqueidandaJMSdestinationname.Thiscanbeaqueueortopicdestination.WewilldealwithJMStopicslateron.FornowthetimeoutsettingcompletesourfirstJMSendpointcomponentdefinition.
TheendpointneedsaJMSconnectionfactoryforconnectingtoaJMSmessagebroker.TheconnectionfactoryisalsoaddedasSpringbeantotheCitrusSpringapplicationcontext.
<beanid="connectionFactory"class="org.apache.activemq.ActiveMQConnectionFactory"><propertyname="brokerURL"value="tcp://localhost:61616"/></bean>
TheJMSconnectionfactoryreceivestheJMSmessagebrokerURLandisabletoholdmanyotherconnectionspecificoptions.InthisexampleweusetheApacheActiveMQconnectionfactoryimplementationaswewanttousetheActiveMQmessagebroker.CitrusworksbydefaultwithabeanidconnectionFactory.AllCitrusJMScomponentwillautomaticallyrecognizethisconnectionfactory.
TipSpringmakesitveryeasytoconnecttootherJMSbrokerimplementationstoo(e.g.ApacheActiveMQ,TIBCOEnterpriseMessagingService,IBMWebsphereMQ).JustaddtherequiredconnectionfactoryimplementationasconnectionFactorybean.
NoteAlloftheCitrusJMSendpointcomponentswillautomaticallylookforabeannamedconnectionFactorybydefault.Youcanusetheconnection-factoryendpointattributeinordertouseanotherconnectionfactoryinstancewithdifferentbeannames.
<citrus-jms:endpointid="helloServiceQueueEndpoint"destination-name="Citrus.HelloService.Request.Queue"
CitrusReferenceGuide
230Jms
connection-factory="myConnectionFacotry"/>
AsanalternativetothatyoumaywanttouseaspecialSpringjmstemplateimplementationascustombeaninyourendpoint.
<citrus-jms:endpointid="helloServiceQueueEndpoint"destination-name="Citrus.HelloService.Request.Queue"jms-template="myJmsTemplate"/>
Theendpointisnowreadytobeusedinsideatestcase.Insideatestcaseyoucansendorreceivemessagesusingthisendpoint.ThetestactionscanreferencetheJMSendpointusingitsidentifier.WhensendingamessagethemessageendpointcreatesaJMSmessageproducerandwillsimplypublishthemessagetothedefinedJMSdestination.Asthecommunicationisasynchronousbydefaultproducerdoesnotwaitforasynchronousresponse.
WhenreceivingamessageswiththisendpointtheendpointcreatesaJMSconsumerontheJMSdestination.Theendpointthenactsasamessagedrivenlistener.Thismeansthatthemessageconsumerconnectstothegivendestinationandwaitsformessagestoarrive.
NoteBesidesthedestination-nameattributeyoucanalsoprovideareferencetoadestinationimplementation.
<citrus-jms:endpointid="helloServiceQueueEndpoint"destination="helloServiceQueue"/>
<amq:queueid="helloServiceQueue"physicalName="Citrus.HelloService.Request.Queue"/>
ThedestinationattributereferencestoaJMSdestinationobjectintheSpringapplicationcontext.IntheexampleaboveweusedtheActiveMQqueuedestinationcomponent.ThedestinationreferencecanalsorefertoaJNDIlookupforinstance.
JMSsynchronousendpoints
WhenusingsynchronousmessageendpointsCitruswillmanageareplydestinationforreceivingasynchronousresponsemessageonthereplydestination.Thefollowingfigureillustratesthatwenowhavetwodestinationsinourcommunicationscenario.
CitrusReferenceGuide
231Jms
Thesynchronousmessageendpointcomponentissimilartotheasynchronousbrotherthatwehavediscussedbefore.Theonlydifferenceisthattheendpointwillautomaticallymanageareplydestinationbehindthescenes.BydefaultCitrususestemporaryreplydestinationsthatgetautomaticallydeletedafterthecommunicationhandshakeisdone.AgainweneedtouseaJMSconnectionfactoryintheSpringXMLconfigurationasthecomponentneedtoconnecttoaJMSmessagebroker.
<citrus-jms:sync-endpointid="helloServiceSyncEndpoint"destination-name="Citrus.HelloService.InOut.Queue"timeout="10000"/>
Thesynchronouscomponentdefinesatargetdestinationwhichagainiseitheraqueueortopicdestination.Ifnothingelseisdefinedtheendpointwillcreatetemporaryreplydestinationsonitsown.Whentheendpointhassentamessageitwaitssynchronouslyfortheresponsemessagetoarriveonthereplydestination.Youcanreceivethisreplymessageinyourtestcasebyreferencingthissameendoointinareceivetestaction.Incasenoreplymessagearrivesintimeamessagetimeouterrorisraisedrespectively.
Seethefollowingexampletestcasewhichreferencesthesynchronousmessageendpointinitssendandreceivetestactioninordertosendoutamessageandwaitforthesynchronousresponse.
<testcasename="synchronousMessagingTest"><actions><sendendpoint="helloServiceSyncEndpoint"><message><data>[...]</data></message></send>
<receiveendpoint="helloServiceSyncEndpoint"><message><data>[...]</data></message>
CitrusReferenceGuide
232Jms
</receive></actions></testcase>
Weinitiatedthesynchronouscommunicationbysendingamessageonthesynchronousendpoint.Thesecondstepthenreceivesthesynchronousmessageonthetemporaryreplydestinationthatwasautomaticallycreatedforus.
Ifyouratherwanttodefineastaticreplydestinationyoucandoso,too.Thestaticreplydestinationisnotdeletedaftercommunicationhandshake.Youmayneedtoworkwithmessageselectorstheninordertopicktherightresponsemessagethatbelongstoaspecificcommunicationhandshake.Youcandefineastaticreplydestinationonthesynchronousendpointcomponentasfollows.
<citrus-jms:sync-endpointid="helloServiceSyncEndpoint"destination-name="Citrus.HelloService.InOut.Queue"reply-destination-name="Citrus.HelloService.Reply.Queue"timeout="10000"/>
Insteadofusingthereply-destination-namefeelfreetousethedestinationreferencewithreply-destinationattribute.AgainyoucanuseaJNDIlookupthentoreferenceadestinationobject.
ImportantBeawareofpermissionsthataremandatoryforcreatingtemporarydestinations.CitrustriestocreatetemporaryqueuesontheJMSmessagebroker.FollowingfromthattheCitrusJMSuserhastohavethepermissiontodoso.Besurethattheuserhasthesufficientrightswhenusingtemporaryreplydestinations.
Uptonowwehavesentamessageandwaitedforasynchronousresponseinthenextstep.Nowitisalsopossibletoswitchthedirectionsofsendandreceiveactions.ThenwehavethesituationwhereCitrusreceivesaJMSmessagefirstandthenCitrusisinchargeofprovidingapropersynchronousresponsemessagetotheinitialsender.
InthisscenariotheforeignmessageproducerhasstoredadynamicJMSreplyqueuedestinationtotheJMSheader.SoCitrushastosendthereplymessagetothisspecificreplydestination,whichisdynamicofcourse.Fortunatelytheheavyliftisdonewiththe
CitrusReferenceGuide
233Jms
JMSmessageendpointandwedonothavetochangeanythinginourconfiguration.Againwejustdefineasynchronousmessageendpointintheapplicationcontext.
<citrus-jms:sync-endpointid="helloServiceSyncEndpoint"destination-name="Citrus.HelloService.InOut.Queue"timeout="10000"/>
Nowtheonlythingthatchangeshereisthatwefirstreceiveamessageinourtestcaseonthisendpoint.Thesecondstepisasendmessageactionthatreferencesthissameendpointandwearedone.Citrusautomaticallymanagesthereplydestinationsforus.
<testcasename="synchronousMessagingTest"><actions><receiveendpoint="helloServiceSyncEndpoint"><message><data>[...]</data></message></receive>
<sendendpoint="helloServiceSyncEndpoint"><message><data>[...]</data></message></send></actions></testcase>
JMStopics
UptonowwehaveusedJMSqueuedestinationsonourendpoints.CitrusisalsoabletoconnecttoJMStopicdestinations.IncontrarytoJMSqueueswhichrepresentsthepoint-to-pointcommunicationJMStopicsusepublish-subscribemechanisminordertospreadmessagesoverJMS.AJMStopicproducerpublishesmessagestothetopic,whilethetopicacceptsmultiplemessagesubscriptionsanddeliversthemessagetoallsubscribers.
TheCitrusJMSendpointsoffertheattribute'pub-sub-domain'.OncethisattributeissettotrueCitruswilluseJMStopicsinsteadofqueuedestinations.Seethefollowingexamplewherethepublish-subscribeattributeissettotrueinJMSmessageendpoint
CitrusReferenceGuide
234Jms
components.
<citrus-jms:endpointid="helloServiceQueueEndpoint"destination="helloServiceQueue"pub-sub-domain="true"/>
WhenusingJMStopicsyouwillbeabletosubscribeseveraltestactionstothetopicdestinationandreceiveamessagemultipletimesasallsubscriberswillreceivethemessage.
ImportantItisveryimportanttokeepinmindthatCitrusdoesnotdealwithdurablesubscribers.Thismeansthatmessagesthatweresentinadvancetothemessagesubscriptionarenotdeliveredtothemessageendpoint.SoracingconditionsmaycauseproblemswhenusingJMStopicendpointsinCitrus.BesuretoletCitrussubscribetothetopicbeforemessagesaresenttoit.Otherwiseyoumayloosesomemessagesthatweresentinadvancetothesubscription.
JMSmessageheaders
TheJMSspecificationdefinesasetofspecialmessageheaderentriesthatcangointoyourJMSmessage.TheseJMSheadersarestoreddifferentlyinaJMSmessageheaderthanothercustomheaderentriesdo.Thereforethesespecialheadervaluesshouldbesetinaspecialsyntaxthatwediscussinthenextparagraphs.
<header><elementname="citrus_jms_correlationId"value="$correlationId"/><elementname="citrus_jms_messageId"value="$messageId"/><elementname="citrus_jms_redelivered"value="$redelivered"/><elementname="citrus_jms_timestamp"value="$timestamp"/></header>
AsyouseeallJMSspecificmessageheadersusethecitrusjmsprefix.ThisprefixcomesfromSpringIntegrationmessageheadermappersthattakecareofsettingthoseheadersintheJMSmessageheaderproperly.
TypingofmessageheaderentriesmayalsobeofinterestinordertomeettheJMSstandardsoftypedmessageheaders.ForinstancethefollowingmessageheaderisoftypedoubleandisthereforetransferredviaJMSasadoublevalue.
<header><elementname="amount"value="19.75"type="double"/></header>
CitrusReferenceGuide
235Jms
SOAPoverJMS
WhensendingSOAPmessagesyouhavetodealwithproperenvelope,bodyandheaderconstruction.InCitrusyoucanaddaspecialmessageconverterthatperformstheheavyliftforyou.JustaddthemessageconvertertotheJMSendpointasshowninthenextprogramlisting:
<citrus-jms:endpointid="helloServiceSoapJmsEndpoint"destination-name="Citrus.HelloService.Request.Queue"message-converter="soapJmsMessageConverter"/>
<beanid="soapJmsMessageConverter"class="com.consol.citrus.jms.message.SoapJmsMessageConverter"
WiththismessageconverteryoucanskiptheSOAPenvelopecompletelyinyourtestcase.Youjustdealwiththemessagebodypayloadandtheheaderentries.Therestisdonebythemessageconverter.SoyougetproperSOAPmessagesontheproducerandconsumerside.
CitrusReferenceGuide
236Jms
HTTPRESTsupportRESTAPIshavegainedmoreandmoresignificanceregardingclient-serverinterfaces.TheRESTclientisnothingbutaHTTPclientsendingHTTPrequestsusuallyinJSONdataformattoaHTTPserver.AsHTTPisasynchronousprotocolbynaturetheclientreceivestheserverresponsesynchronously.CitrusisabletoconnectwithHTTPservicesandtestRESTAPIsonbothclientandserversidewithapowerfulJSONmessagedatasupport.InthenextsectionsyouwilllearnhowtoinvokeHTTPservicesasaclientandhowtohandleRESTHTTPrequestsinatestcase.WedealwithsettingupaHTTPserverinordertoacceptclientrequestsandprovideproperHTTPresponseswithGET,PUT,DELETEorPOSTrequestmethod.
NoteThehttpcomponentsinCitrusarekeptinaseparateMavenmodule.SoyoushouldaddthemoduleasMavendependencytoyourprojectaccordingly.
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-http</artifactId><version>2.7.1</version></dependency>
AsCitrusprovidesacustomizedHTTPconfigurationschemafortheSpringapplicationcontextconfigurationfileswehavetoaddnametothetoplevelbeanselement.Simplyincludethehttp-confignamespaceintheconfigurationXMLfilesasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-http="http://www.citrusframework.org/schema/http/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/http/confighttp://www.citrusframework.org/schema/http/config/citrus-http-config.xsd">
[...]
</beans>
CitrusReferenceGuide
237Http
NowwearereadytousethecustomizedCitrusHTTPconfigurationelementswiththecitrus-httpnamespaceprefix.
HTTPRESTclient
OntheclientsidewehaveasimpleHTTPmessageclientcomponentconnectingtotheserver.Therequest-urlattributedefinestheHTTPserverendpointURLtoconnectto.Asusualyoucanreferencethisclientinyourtestcaseinordertosendandreceivemessages.Citrusasclientwaitsfortheresponsemessagefromserver.Afterthattheresponsemessagegoesthroughthevalidationprocessasusual.LetusseehowaCitrusHTTPclientcomponentlookslike:
<citrus-http:clientid="helloHttpClient"request-url="http://localhost:8080/hello"request-method="GET"content-type="application/xml"timeout="60000"/>
Therequest-methoddefinestheHTTPmethodtouse.Inadditiontothatwecanspecifythecontent-typeoftherequestweareabouttosend.TheclientbuildstheHTTPrequestandsendsittotheHTTPserver.WhiletheclientiswaitingforthesynchronousHTTPresponsetoarriveweareabletopollseveraltimesfortheresponsemessageinourtestcase.Asusualaoucanusethesameclientendpointinyourtestcasetosendandreceivemessagessynchronously.Incasethereplymessagecomesintoolateaccordingtothetimeoutsettingsarespectivetimeouterrorisraised.
HttpdefinesseveralrequestmethodsthataclientcanusetoaccessHttpserverresources.IntheexampleclientaboveweareusingGETasdefaultrequestmethod.OfcourseyoucanoverwritethissettinginatestcaseactionbysettingtheHTTPrequestmethodinsidethesendingtestaction.TheHttpclientcomponentcanbeusedasnormalendpointinasendingtestaction.Usesomethinglikethisinyourtest:
XMLDSL
<sendendpoint="helloHttpClient"><message><payload><TestMessage><Text>HelloHttpServer</Text></TestMessage></payload></message>
CitrusReferenceGuide
238Http
<header><elementname="citrus_http_method"value="POST"/></header></send>
TipCitrususestheSpringRESTtemplatemechanismforsendingoutHTTPrequests.ThismeansyouhavegreatcustomizingopportunitieswithaspecialRESTtemplateconfiguration.YoucanthinkofbasicHTTPauthentication,readtimeoutsandspecialmessagefactoryimplementations.JustusethecustomRESTtemplateattributeinclientconfigurationlikethis:
<citrus-http:clientid="helloHttpClient"request-url="http://localhost:8080/hello"request-method="GET"content-type="text/plain"rest-template="customizedRestTemplate"/>
<!--Customizedresttemplate--><beanname="customizedRestTemplate"class="org.springframework.web.client.RestTemplate"><propertyname="messageConverters"><util:listid="converter"><beanclass="org.springframework.http.converter.StringHttpMessageConverter"><propertyname="supportedMediaTypes"><util:listid="types"><value>text/plain</value></util:list></property></bean></util:list></property><propertyname="errorHandler"><!--Customerrorhandler--></property><propertyname="requestFactory"><beanclass="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"><propertyname="readTimeout"value="9000"/></bean></property></bean>
UptonowwehaveusedanormalsendtestactiontosendHttprequestsasaclient.ThisiscompletelyvalidstrategyastheCitrusHttpclientisanormalendpoint.ButwemightwanttosetsomemoreHttpRESTspecificpropertiesandsettings.InordertosimplifytheHttpusageinatestcasewecanuseaspecialtestactionimplementation.TheCitrusHttpspecificactionsarelocatedinaseparateXMLnamespace.SowenneedtoaddthisnamespacetoourtestcaseXMLfirst.
CitrusReferenceGuide
239Http
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:http="http://www.citrusframework.org/schema/http/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/http/testcasehttp://www.citrusframework.org/schema/http/testcase/citrus-http-testcase.xsd">
[...]
</beans>
ThetestcaseisnowreadytousethespecificHttptestactionsbyusingtheprefixhttp:.
XMLDSL
<http:send-requestclient="httpClient"><http:POSTpath="/customer"><http:headerscontent-type="application/xml"accept="application/xml,*/*"><http:headername="X-CustomHeaderId"value="$custom_header_id"/></http:headers><http:body><http:data><![CDATA[<customer><id>citrus:randomNumber()</id><name>testuser</name></customer>]]></http:data></http:body></http:POST></http:send-request>
TheactionaboveusesseveralHttpspecificsettingssuchastherequestmethodPOSTaswellasthecontent-typeandacceptheaders.AsusualthesendactionneedsatargetHttpclientendpointcomponent.Wecanspecifyarequestpathattributethataddedasrelativepathtothebaseuriusedontheclient.
WhenusingaGETrequestwecanspecifysomerequesturiparameters.
XMLDSL
<http:send-requestclient="httpClient"><http:GETpath="/customer/$custom_header_id"><http:paramscontent-type="application/xml"accept="application/xml,*/*">
CitrusReferenceGuide
240Http
<http:paramname="type"value="active"/></http:params></http:GET></http:send-request>
ThesendactionaboveusesaGETrequestontheendpointurihttp://localhost:8080/customer/1234?type=active.
OfcoursewhensendingHttpclientrequestswearealsointerestedinreceivingHttpresponsemessages.WewanttovalidatethesuccessresponsewithHttpstatuscode.
XMLDSL
<http:receive-responseclient="httpClient"><http:headersstatus="200"reason-phrase="OK"version="HTTP/1.1"><http:headername="X-CustomHeaderId"value="$custom_header_id"/></http:headers><http:body><http:data><![CDATA[<customerResponse><success>true</success></customerResponse>]]></http:data></http:body></http:receive-response>
Thereceive-responsetestactionalsousesaclientcomponent.Wecanexpectresponsestatuscodeinformationsuchasstatusandreason-phrase.OfcourseCitruswillraiseavalidationexceptionincaseHttpstatuscodesmismatch.
UptonowwehaveusedXMLDSLtestcases.TheJavaDSLinCitrusalsoworkswithspecificHttptestactions.Seefollowingexampleandfindouthowthisworks:
XMLDSL
@CitrusTestpublicvoidhttpActionTest()http().client("httpClient").send().post("/customer").payload("<customer>"+"<id>citrus:randomNumber()</id>"+"<name>testuser</name>"+"</customer>").header("X-CustomHeaderId","$custom_header_id")
CitrusReferenceGuide
241Http
.contentType("text/xml").accept("text/xml,*/*");
http().client("httpClient").receive().response(HttpStatus.OK).payload("<customerResponse>"+"<success>true</success>"+"</customerResponse>").header("X-CustomHeaderId","$custom_header_id").version("HTTP/1.1");
Thereisonemoresettingontheclienttobeawareof.BydefaulttheclientcomponentwilladdtheAccepthttpheaderandsetitsvaluetoalistofallsupportedencodingsonthehostoperatingsystem.Asthislistcangetverylongyoumaywanttonotsetthisdefaultacceptheader.ThesettingisdoneintheSpringRestTemplate:
<beanname="customizedRestTemplate"class="org.springframework.web.client.RestTemplate"><propertyname="messageConverters"><util:listid="converter"><beanclass="org.springframework.http.converter.StringHttpMessageConverter"><propertyname="writeAcceptCharset"value="false"/></bean></util:list></property></bean>
YouwouldhaveaddthiscustomRestTemplateconfigurationandsetittotheclientcomponentwithrest-templateproperty.ButfortunatelytheCitrusclientcomponentprovidesaseparatesettingdefault-accept-headerwhichisaBooleansetting.Bydefaultitissettotruesothedefaultacceptheaderisautomaticallyaddedtoallrequests.Ifyousetthisflagtofalsetheheaderisnotset:
<citrus-http:clientid="helloHttpClient"request-url="http://localhost:8080/hello"request-method="GET"content-type="text/plain"default-accept-header="false"/>
OfcourseyoucansettheAcceptheaderoneachsendoperationinordertotelltheserverwhatkindofcontenttypesaresupportedinresponsemessages.
CitrusReferenceGuide
242Http
NowwecansendandreceivemessagesasHttpclientwithspecifictestactions.NowletsmoveontotheHttpserver.
HTTPclientinterceptors
Theclientcomponentisabletoaddcustominterceptorsthatparticipateintherequest/responseprocessing.Theinterceptorsneedtoimplementthecommoninterfaceorg.springframework.http.client.ClientHttpRequestInterceptor.
<citrus-http:clientid="helloHttpClient"request-url="http://localhost:8080/hello"request-method="GET"interceptors="clientInterceptors"/>
<util:listid="clientInterceptors"><beanclass="com.consol.citrus.http.interceptor.LoggingClientInterceptor"/></util:list>
ThesampleaboveaddstheCitrusloggingclientinterceptorthatlogsrequestsandresponsesexchangedwiththatclientcomponent.Youcanaddcustominterceptorimplementationshereinordertoparticipateintherequest/responsemessageprocessing.
HTTPRESTserver
TheHTTPclientwasquiteeasyandstraightforward.ReceivingHTTPmessagesisalittlebitmorecomplicatedbecauseCitrushastoprovideserverfunctionalitylisteningonalocalportforclientconnections.ThereforeCitrusoffersanembeddedHTTPserverwhichiscapableofhandlingincomingHTTPrequests.OnceaclientconnectionisacceptedtheHTTPservermustalsoprovideaproperHTTPresponsetotheclient.InthenextfewlinesyouwillseehowtosimulateserversideHTTPRESTservicewithCitrus.
<citrus-http:serverid="helloHttpServer"port="8080"auto-start="true"resource-base="src/it/resources"/>
CitrususesanembeddedJettyserverthatwillautomaticallystartwhentheSpringapplicationcontextisloaded(auto-start="true").Thebasicconnectorislisteningonport8080forrequests.Testcasescaninteractwiththisserverinstanceviamessage
CitrusReferenceGuide
243Http
channelsbydefault.Theserverprovidesaninboundchannelthatholdsincomingrequestmessages.Thetestcasecanreceivethoserequestsfromthechannelwithanormalreceivetestaction.InasecondstepthetestcasecanprovideasynchronousresponsemessageasreplywhichwillbeautomaticallysentbacktotheHTTPclientasresponse.
Thefigureaboveshowsthebasicsetupwithinboundchannelandreplychannel.Youasatestershouldnotworryaboutthistomuch.Bydefaultyouasatesterjustusetheserverassynchronousendpointinyourtestcase.Thismeansthatyousimplyreceiveamessagefromtheserverandsendaresponseback.
<testcasename="httpServerTest"><actions><receiveendpoint="helloHttpServer"><message><data>[...]</data></message></receive>
<sendendpoint="helloHttpServer"><message><data>[...]</data></message></send></actions></testcase>
Asyoucanseewereferencetheserveridinbothreceiveandsendactions.TheCitrusserverinstancewillautomaticallysendtheresponsebacktothecallingHTTPclient.Inmostcasesthisisexactlywhatwewanttodo-sendbackaresponsemessagethatisspecifiedinsidethetest.TheHTTPservercomponentbydefaultusesachannelendpointadapterinordertoforwardallincomingrequeststoaninmemorymessagechannel.Thisisdonecompletelybehindthescenes.TheHttpservercomponentprovidessomemorecustomizationpossibilitieswhenitcomestoendpointadapter
CitrusReferenceGuide
244Http
implementations.Thistopicisdiscussedinaseparatesectionendpoint-adapter.Uptonowwekeepitsimplebysynchronouslyreceivingandsendingmessagesinthetestcase.
TipThedefaultchannelendpointadapterautomaticallycreatesaninboundmessagechannelwhereincomingmessagesarestoredtointernally.Soifyouneedtocleanupaserverthathasalreadystoredsomeincomingmessagesyoucandothiseasilybypurgingtheinternalmessagechannel.ThemessagechannelfollowsanamingconventionserverName.inboundwhereserverNameistheSpringbeannameoftheCitrusserverendpointcomponent.Ifyoupurgethisinternalchannelinabeforetestnatureyouaresurethatobsoletemessagesonaserverinstancegetpurgedbeforeeachtestisexecuted.
Soletsgetbacktoourmissionofprovidingresponsemessagesasservertoconnectedclients.AsyoumightknowHttpRESTworkswithsomecharacteristicpropertieswhenitcomestosendandreceivemessages.ForinstanceaclientcansenddifferentrequestmethodsGET,POST,PUT,DELETE,HEADandsoon.TheCitrusservermayverifythismethodwhenreceivingclientrequests.ThereforewehaveintroducedspecialHttptestactionsforservercommunication.Havealookatasimpleexample:
<http:receive-requestserver="helloHttpServer"><http:POSTpath="/test"><http:headerscontent-type="application/xml"accept="application/xml,*/*"><http:headername="X-CustomHeaderId"value="$custom_header_id"/><http:headername="Authorization"value="Basicc29tZVVzZXJuYW1lOnNvbWVQYXNzd29yZA=="/></http:headers><http:body><http:data><![CDATA[<testRequestMessage><text>HelloHttpServer</text></testRequestMessage>]]></http:data></http:body></http:POST><http:extract><http:headername="X-MessageId"variable="message_id"/></http:extract></http:receive-request>
<http:send-responseserver="helloHttpServer"><http:headersstatus="200"reason-phrase="OK"version="HTTP/1.1"><http:headername="X-MessageId"value="$message_id"/><http:headername="X-CustomHeaderId"value="$custom_header_id"/><http:headername="Content-Type"value="application/xml"/>
CitrusReferenceGuide
245Http
</http:headers><http:body><http:data><![CDATA[<testResponseMessage><text>HelloCitrus</text></testResponseMessage>]]></http:data></http:body></http:send-response>
WereceiveaclientrequestandvalidatethattherequestmethodisPOSTonrequestpath/test.Nowwecanvalidatespecialmessageheaderssuchascontent-type.Inadditiontothatwecancheckcustomheadersandbasicauthorizationheaders.Asusualtheoptionalmessagebodyiscomparedtoanexpectedmessagetemplate.ThecustomX-MessageIdheaderissavedtoatestvariablemessage_idforlaterusageintheresponse.
TheresponsemessagedefinesHttptypicalentitiessuchasstatusandreason-phrase.Herethetestercansimulate404NOT_FOUNDerrorsorsimilarotherstatuscodesthatgetsendbacktotheclient.InourexampleeverythingisOKandwesendbackaresponsebodyandsomecustomheaderentries.
ThatisbasicallyhowCitrussimulatesHttpserveroperations.Wereceivetheclientrequestandvalidatetherequestproperties.ThenwesendbackaresponsewithaHttpstatuscode.
AsusualalltheseHttpspecificactionsarealsoavailableinJavaDSL.
@CitrusTestpublicvoidhttpServerActionTest()http().server("helloHttpServer").receive().post("/test").payload("<testRequestMessage>"+"<text<HelloHttpServer</text>"+"</testRequestMessage>").contentType("application/xml").accept("application/xml,*/*").header("X-CustomHeaderId","$custom_header_id").header("Authorization","Basicc29tZVVzZXJuYW1lOnNvbWVQYXNzd29yZA==").extractFromHeader("X-MessageId","message_id");
http().server("helloHttpServer")
CitrusReferenceGuide
246Http
.send().response(HttpStatus.OK).payload("<testResponseMessage>"+"<text<HelloCitrus</text>"+"</testResponseMessage>").version("HTTP/1.1").contentType("application/xml").header("X-CustomHeaderId","$custom_header_id").header("X-MessageId","$message_id");
ThisistheexactsameexampleinJavaDSL.Weselectserveractionsfirstandreceiveclientrequests.ThenwesendbackaresponsewithaHttpStatus.OKstatus.ThiscompletestheserveractionsonHttpmessagetransport.NowwecontinuewithsomemoreHttpspecificsettingsandfeatures.
HTTPheaders
WhendealingwithHTTPrequest/responsecommunicationwealwaysdealwithHTTPspecificheaders.TheHTTPprotocoldefinesagroupofheaderattributesthatbothclientandserverneedtobeabletohandle.YoucansetandvalidatetheseHTTPheadersinCitrusquiteeasy.LetushavealookataclientoperationinCitruswheresomeHTTPheadersareexplicitlysetbeforetherequestissentout.
<http:send-requestclient="httpClient"><http:POST><http:headers><http:headername="X-CustomHeaderId"value="$custom_header_id"/><http:headername="Content-Type"value="text/xml"/><http:headername="Accept"value="text/xml,*/*"/></http:headers><http:body><http:payload><testRequestMessage><text>HelloHttpServer</text></testRequestMessage></http:payload></http:body></http:POST></http:send-request>
Weareabletosetcustomheaders(X-CustomHeaderId)thatgodirectlyintotheHTTPheadersectionoftherequest.InadditiontothattesterscanexplicitlysetHTTPreservedheaderssuchasContent-Type.Fortunatelyyoudonothavetosetallheadersonyour
CitrusReferenceGuide
247Http
own.CitruswillautomaticallysettherequiredHTTPheadersfortherequest.SowehavethefollowingHTTPrequestwhichissenttotheserver:
POST/testHTTP/1.1Accept:text/xml,*/*Content-Type:text/xmlX-CustomHeaderId:123456789Accept-Charset:macromanUser-Agent:JakartaCommons-HttpClient/3.1Host:localhost:8091Content-Length:175<testRequestMessage><text>HelloHttpServer</text></testRequestMessage>
OnserversidetestersareinterestedinvalidatingtheHTTPheaders.WithinCitrusreceiveactionyousimplydefinetheexpectedheaderentries.TheHTTPspecificheadersareautomaticallyavailableforvalidationasyoucanseeinthisexample:
<http:receive-requestserver="httpServer"><http:POST><http:headers><http:headername="X-CustomHeaderId"value="$custom_header_id"/><http:headername="Content-Type"value="text/xml"/><http:headername="Accept"value="text/xml,*/*"/></http:headers><http:body><http:payload><testRequestMessage><text>HelloHttpServer</text></testRequestMessage></http:payload></http:body></http:POST></http:receive-request>
ThetestchecksoncustomheadersandHTTPspecificheaderstomeettheexpectedvalues.
NowthatwehaveacceptedtheclientrequestandvalidatedthecontentsweareabletosendbackaproperHTTPresponsemessage.SamethingherewithHTTPspecificheaders.TheHTTPprotocoldefinesseveralheadersmarkingthesuccessorfailureoftheserveroperation.InthetestcaseyoucansetthoseheadersfortheresponsemessagewithconventionalCitrusheadernames.Seethefollowingexampletofindouthowthatworksforyou.
CitrusReferenceGuide
248Http
<http:send-responseserver="httpServer"><http:headersstatus="200"reason-phrase="OK"><http:headername="X-CustomHeaderId"value="$custom_header_id"/><http:headername="Content-Type"value="text/xml"/></http:headers><http:body><http:payload><testResponseMessage><text>HelloCitrusClient</text></testResponseMessage></http:payload></http:body></http:send-response>
Oncemorewesetthecustomheaderentry(X-CustomHeaderId)andaHTTPreservedheader(Content-Type)fortheresponsemessage.OntopofthisweareabletosettheresponsestatusfortheHTTPresponse.Weusethereservedheadernamesstatusinordertomarkthesuccessoftheserveroperation.WiththismechanismwecaneasilysimulatedifferentserverbehavioursuchasHTTPerrorresponsecodes(e.g.404-Notfound,500-Internalerror).Letushaveacloserlookatthegeneratedresponsemessage:
HTTP/1.1200OKContent-Type:text/xml;charset=UTF-8Accept-Charset:macromanContent-Length:205Server:Jetty(7.0.0.pre5)<testResponseMessage><text>HelloCitrusClient</text></testResponseMessage>
TipYoudonothavetosetthereasonphraseallthetime.ItissufficienttoonlysettheHTTPstatuscode.CitruswillautomaticallyaddtheproperreasonphraseforwellknownHTTPstatuscodes.
TheonlythingthatismissingrightnowisthevalidationofHTTPstatuscodeswhenreceivingtheserverresponseinaCitrustestcase.ItisveryeasyasyoucanusetheCitrusreservedheadernamesforvalidation,too.
<http:receive-responseclient="httpClient"><http:headersstatus="200"reason-phrase="OK"version="HTTP/1.1"><http:headername="X-CustomHeaderId"value="$custom_header_id"/></http:headers><http:body>
CitrusReferenceGuide
249Http
<http:payload><testResponseMessage><text>HelloTestFramework</text></testResponseMessage></http:payload></http:body></http:receive-response>
UptonowwehaveusedsomeofthebasicCitrusreservedHTTPheadernames(status,version,reason-phrase).InHTTPRESTfulservicessomeotherheadernamesareessentialforvalidation.Thesearerequestattributeslikequeryparameters,contextpathandrequestURI.TheCitrusserversideRESTmessagecontrollerwillautomaticallyaddallthisinformationtothemessageheaderforyou.Soallyouneedtodoisvalidatetheheaderentriesinyourtest.
ThenextexamplereceivesaHTTPGETmethodrequestonserverside.HeretheGETrequestdoesnothaveanymessagepayload,sothevalidationjustworksontheinformationgiveninthemessageheader.Weassumetheclienttocallhttp://localhost:8080/app/users?id=123456789.Asatesterweneedtovalidatetherequestmethod,requestURI,contextpathandthequeryparameters.
<http:receive-requestserver="httpServer"><http:GETpath="/app/users"context-path="/app"><http:params><http:paramname="id"value="123456789"/></http:params><http:headers><http:headername="Host"value="localhost:8080"/><http:headername="Content-Type"value="text/html"/><http:headername="Accept"value="text/xml,*/*"/></http:headers><http:body><http:data></http:data></http:body></http:GET></http:receive-request>
TipBeawareoftheslightdifferencesinrequestURIandcontextpath.Thecontextpathgivesyouthewebapplicationcontextpathwithintheservletcontainerforyourwebapplication.TherequestURIalwaysgivesyouthecompletepaththatwascalledforthisrequest.
CitrusReferenceGuide
250Http
AsyoucanseeweareabletovalidateallpartsoftheinitialrequestendpointURItheclientwascalling.ThiscompletestheHTTPheaderprocessingwithinCitrus.OnbothclientandserversideCitrusisabletosetandvalidateHTTPspecificheaderentrieswhichisessentialforsimulatingHTTPcommunication.
HTTPserverinterceptors
Theservercomponentisabletoaddcustominterceptorsthatparticipateintherequest/responseprocessing.Theinterceptorsneedtoimplementthecommoninterfaceorg.springframework.web.servlet.HandlerInterceptor.
<citrus-http:serverid="httpServer"port="8080"auto-start="true"interceptors="serverInterceptors"/>
<util:listid="serverInterceptors"><beanclass="com.consol.citrus.http.interceptor.LoggingHandlerInterceptor"/></util:list>
ThesampleaboveaddstheCitruslogginghandlerinterceptorthatlogsrequestsandresponsesexchangedwiththatservercomponent.Youcanaddcustominterceptorimplementationshereinordertoparticipateintherequest/responsemessageprocessing.
HTTPformurlencodeddata
HTMLformdatacanbesenttotheserverusingdifferentmethodsandcontenttypes.OneofthemisaPOSTmethodwithx-www-form-urlencodedbodycontent.Theformdataelementsaresenttotheserverusingkey-valuepairsPOSTdatawheretheformcontrolnameisthekeyandthecontroldataistheurlencodedvalue.
Formurlencodedformdatacontentcouldlooklikethis:
password=s%21cr%21t&username=foo
Ayoucanseetheformdataisautomaticallyencoded.Intheexampleabovewetransmittwoformcontrolspasswordandusernamewithrespectivevaluess$cr$tandfoo.IncasewewouldvalidatethisformdatainCitrusweareabletodothiswithplaintextmessagevalidation.
CitrusReferenceGuide
251Http
<receiveendpoint="httpServer"><messagetype="plaintext"><data><![CDATA[password=s%21cr%21t&username=$username]]></data></message><header><elementname="citrus_http_method"value="POST"/><elementname="citrus_http_request_uri"value="/form-test"/><elementname="Content-Type"value="application/x-www-form-urlencoded"/></header></receive>
Obviouslyvalidatingthesekey-valuepaircharactersequencescanbehardespeciallywhenhavingHTMLformswithlotsofformcontrols.ThisiswhyCitrusprovidesaspecialmessagevalidatorforx-www-form-urlencodedcontents.Firstofallwehavetoaddcitrus-httpmoduleasdependencytoourprojectifnotdonesoyet.AfterthatwecanaddthevalidatorimplementationtothelistofmessagevalidatorsusedinCitrus.
<citrus:message-validators><citrus:validatorclass="com.consol.citrus.http.validation.FormUrlEncodedMessageValidator"/></citrus:message-validators>
Nowweareabletoreceivetheurlencodedformdatamessageinatest.
<receiveendpoint="httpServer"><messagetype="x-www-form-urlencoded"><payload><form-dataxmlns="http://www.citrusframework.org/schema/http/message"><content-type>application/x-www-form-urlencoded</content-type><action>/form-test</action><controls><controlname="password"><value>$password</value></control><controlname="username"><value>$username</value></control></controls></form-data></payload></message><header><elementname="citrus_http_method"value="POST"/>
CitrusReferenceGuide
252Http
<elementname="citrus_http_request_uri"value="/form-test"/><elementname="Content-Type"value="application/x-www-form-urlencoded"/></header></receive>
Weuseaspecialmessagetypex-www-form-urlencodedsothenewmessagevalidatorwilltakeaction.TheformurlencodedmessagevalidatorisabletohandleaspecialXMLrepresentationoftheformdata.ThisenablestheverypowerfulXMLmessagevalidationcapabilitiesofCitrussuchasignoringelementsandusageoftestvariablesinline.
Eachformcontrolistranslatedtoacontrolelementwithrespectivenameandvalueproperties.Theformdataisvalidatedinamorecomfortablewayastheplaintextmessagevalidatorwouldbeabletooffer.
HTTPerrorhandling
SofarwehavereceivedresponsemessageswithHTTPstatuscode200OK.Howtodealwithservererrorslike404NotFoundor500Internalservererror?ThedefaultHTTPmessageclienterrorstrategyistopropagateservererrorresponsemessagestothereceiveactionforvalidation.WesimplycheckonHTTPstatuscodeandstatustextforerrorvalidation.
<http:send-requestclient="httpClient"><http:body><http:payload><testRequestMessage><text>HelloHttpServer</text></testRequestMessage></http:payload></http:body></http:send-request>
<http:receive-requestclient="httpClient"><http:body><http:data><![CDATA[]]></http:data></http:body><http:headersstatus="403"reason-phrase="FORBIDDEN"/></http:receive>
Themessagedatacanbeemptydependingontheserverlogicfortheseerrorsituations.Ifwereceiveadditionalerrorinformationasmessagepayloadjustaddvalidationassertionsasusual.
CitrusReferenceGuide
253Http
InsteadofreceivingsuchemptymessageswithchecksonHTTPstatusheaderinformationwecanchangetheerrorstrategyinthemessagesendercomponentinordertoautomaticallyraiseexceptionsonresponsemessagesotherthan200OK.ThereforewegobacktotheHTTPmessagesenderconfigurationforchangingtheerrorstrategy.
<citrus-http:clientid="httpClient"request-url="http://localhost:8080/test"error-strategy="throwsException"/>
Nowweexpectanexceptiontobethrownbecauseoftheerrorresponse.Followingfromthatwehavetochangeourtestcase.InsteadofreceivingtheerrormessagewithreceiveactionweasserttheclientexceptionandcheckontheHTTPstatuscodeandstatustext.
<assertexception="org.springframework.web.client.HttpClientErrorException"message="403Forbidden"><when><http:send-requestclient="httpClient"><http:body><http:payload><testRequestMessage><text>HelloHttpServer</text></testRequestMessage></http:payload></http:body></http:send-request></when></assert>
BothwaysofhandlingHTTPerrormessagesonclientsidearevalidforexpectingtheservertoraiseHTTPerrorcodes.Choosethepreferredwayaccordingtoyourtestprojectrequirements.
HTTPclientbasicauthentication
Asclientyoumayhavetousebasicauthenticationinordertoaccessaresourceontheserver.Inmostcasesthiswillbeusername/passwordauthenticationwherethecredentialsaretransmittedintherequestheadersectionasbase64encoding.
TheeasiestapproachtosettheAuthorizationheaderforabasicauthenticationHTTPrequestwouldbetosetitonyourowninthesendactiondefinition.Ofcourseyouhavetousethecorrectbasicauthenticationheadersyntaxwithbase64encodingforthe
CitrusReferenceGuide
254Http
username:passwordphrase.Seethissimpleexample.
<http:headers><http:headername="Authorization"value="Basicc29tZVVzZXJuYW1lOnNvbWVQYXNzd29yZA=="/></http:headers>
CitruswilladdthisheadertotheHTTPrequestsandtheserverwillreadtheAuthorizationusernameandpassword.Formoreconvenientbase64encodingyoucanalsouseaCitrusfunction,seefunctions-encode-base64
NowthereisamorecomfortablewaytosetthebasicauthenticationheaderinalltheCitrusrequests.AsCitrususesSpring'sRESTsupportwiththeRestTemplateandClientHttpRequestFactorythebasicauthenticationisalreadycoveredthereinamoregenericway.YousimplyhavetoconfigurethebasicauthenticationcredentialsontheRestTemplate'sClientHttpRequestFactory.Justseethefollowingexampleandlearnhowtodothat.
<citrus-http:clientid="httpClient"request-method="POST"request-url="http://localhost:8080/test"request-factory="basicAuthFactory"/>
<beanid="basicAuthFactory"class="com.consol.citrus.http.client.BasicAuthClientHttpRequestFactory"><propertyname="authScope"><beanclass="org.apache.http.auth.AuthScope"><constructor-argvalue="localhost"/><constructor-argvalue="8072"/><constructor-argvalue=""/><constructor-argvalue="basic"/></bean></property><propertyname="credentials"><beanclass="org.apache.http.auth.UsernamePasswordCredentials"><constructor-argvalue="someUsername"/><constructor-argvalue="somePassword"/></bean></property></bean>
Theadvantagesofthismethodisobvious.Nowallsendingtestactionsthatreferencetheclientcomponentwillautomaticallyaddthebasicauthenticationheader.
CitrusReferenceGuide
255Http
ImportantSinceCitrushasupgradedtoSpring3.1.xtheJakartacommonsHTTPclientisdeprecatedwithCitrusversion1.2.TheformerlyusedUserCredentialsClientHttpRequestFactoryisthereforealsodeprecatedandwillnotcontinuewithnextversions.PleaseupdateyourconfigurationifyouarecomingfromCitrus1.1orearlierversions.
TheaboveconfigurationresultsinHTTPclientrequestswithauthenticationheadersproperlysetforbasicauthentication.TheclientrequestfactorytakescareonaddingtheproperbasicauthenticationheadertoeachrequestthatissentwiththisCitrusmessagesender.Citrususespreemtiveauthentication.Themessagesenderonlysendsasinglerequesttotheserverwithallauthenticationinformationsetinthemessageheader.Therequestwhichdeterminestheauthenticationschemeontheserverisskipped.ThisiswhyyouhavetoaddsomeauthscopeintheclientrequestfactorysoCitruscansetupanauthenticationcachewithintheHTTPcontextinordertohavepreemtiveauthentication.
AsaresultofthebasicauthclientrequestfactorythefollowingexamplerequestthatiscreatedbytheCitrusHTTPclienthastheAuthorizationheaderset.ThisisdonenowautomaticallyforallrequestswiththisHTTPclient.
POST/testHTTP/1.1Accept:text/xml,*/*Content-Type:text/xmlAccept-Charset:iso-8859-1,us-ascii,utf-8Authorization:Basicc29tZVVzZXJuYW1lOnNvbWVQYXNzd29yZA==User-Agent:JakartaCommons-HttpClient/3.1Host:localhost:8080Content-Length:175<testRequestMessage><text>HelloHttpServer</text></testRequestMessage>
HTTPserverbasicauthentication
Citrusasaservercanalsosetbasicauthenticationsoclientsneedtoauthenticateproperlywhenaccessingserverresources.
<citrus-http:serverid="basicAuthHttpServer"port="8090"auto-start="true"resource-base="src/it/resources"security-handler="basicSecurityHandler"/>
CitrusReferenceGuide
256Http
<beanid="securityHandler"class="com.consol.citrus.http.security.SecurityHandlerFactory"><propertyname="users"><list><beanclass="com.consol.citrus.http.security.User"><propertyname="name"value="citrus"/><propertyname="password"value="secret"/><propertyname="roles"value="CitrusRole"/></bean></list></property><propertyname="constraints"><map><entrykey="/foo/*"><beanclass="com.consol.citrus.http.security.BasicAuthConstraint"><constructor-argvalue="CitrusRole"/></bean></entry></map></property></bean>
Wehavesetasecurityhandlerontheserverwebcontainerwithaconstraintonallresourceswith/foo/*.Followingfromthattheserverrequiresbasicauthenticationfortheseresources.Thegrantedusersandrolesarespecifiedwithinthesecurityhandlerbeandefinition.ConnectingclientshavetosetthebasicauthHTTPheaderproperlyusingthecorrectuserandroleforaccessingtheCitrusservernow.
Youcancustomizethesecurityhandlerforyourveryspecificneeds(e.g.loadusersandroleswithJDBCfromadatabase).Justhavealookatthecodebaseandinspectthesettingsandpropertiesofferedbythesecurityhandlerinterface.
TipThismechanismisnotrestrictedtobasicauthenticationonly.Withothersettingsyoucanalsosetupdigestorform-basedauthenticationconstraintsveryeasy.
HTTPGzipcompression
Gzipisaverypopularcompressionmechanismforoptimizingthemessagetransportationforlargecontent.TheCitrushttpclientandservercomponentssupportgzipcompressionoutofthebox.Thismeansthatyouonlyneedtosetthespecificencodingheadersinyourhttprequest/responsemessage.
Accept-Encoding=gzipSettingforclientswhenrequestinggzipcompressedresponsecontent.TheHttpservermustsupportgzipcompressiontheninordertoprovidetheresponseaszippedbytestream.TheCitrushttpservercomponentautomaticallyrecognizesthisheaderinarequestandappliesgzipcompressionto
CitrusReferenceGuide
257Http
theresponse.Content-Encoding=gzipWhenahttpserversendscompressedmessagecontenttotheclientthisheaderissettogzipinordertomarkthecompression.TheHttpclientmustsupportgzipcompressiontheninordertounzipthemessagecontent.TheCitrushttpclientcomponentautomaticallyrecognizesthisheaderinaresponseandappliesgzipunziplogicbeforepassingthemessagetothetestcase.
TheCitrusclientandserverautomaticallytakecareongzipcompressionwhenthoseheadersareset.Inthetestcaseyoudonotneedtoziporunzipthecontentthenasitisautomaticallydonebefore.
ThismeansthatyoucanrequestgzippedcontentfromaserverwithjustaddingthemessageheaderAccept-Encodinginyourhttprequestoperation.
<echo><message>SendHttpclientrequestforgzipcompresseddata</message></echo>
<http:send-requestclient="gzipClient"><http:POST><http:headerscontent-type="text/html"><http:headername="Accept-Encoding"value="gzip"/><http:headername="Accept"value="text/plain"/></http:headers></http:POST></http:send-request>
<echo><message>Receivetextautomaticallygzipunzipped</message></echo>
<http:receive-responseclient="gzipClient"><http:headersstatus="200"reason-phrase="OK"><http:headername="Content-Type"value="text/plain"/></http:headers><http:bodytype="plaintext"><http:data>$text</http:data></http:body></http:receive-response>
OntheserversideifwereceiveamessageandtheresponseshouldbecompressedwithGzipwejusthavetosettheContent-Encodingheaderintheresponseoperation.
<echo><message>Receivegzipcompressedasbase64encodedtext</message></echo>
CitrusReferenceGuide
258Http
<http:receive-requestserver="echoHttpServer"><http:POSTpath="/echo"><http:headers><http:headername="Content-Type"value="text/html"/><http:headername="Accept-Encoding"value="gzip"/><http:headername="Accept"value="text/plain"/></http:headers></http:POST></http:receive-request>
<echo><message>SendHttpservergzipcompressedresponse</message></echo>
<http:send-responseserver="echoHttpServer"><http:headersstatus="200"reason-phrase="OK"><http:headername="Content-Encoding"value="gzip"/><http:headername="Content-Type"value="text/plain"/></http:headers><http:body><http:data>$text</http:data></http:body></http:send-response>
SotheCitrusserverwillautomaticallyaddgzipcompressiontotheresponseforus.
Ofcourseyoucanalsosendgzippedcontentasaclient.ThenyouwouldjustsettheContent-Encodingheadertogzipinyourrequest.Theclientwillautomaticallyapplycompressionforyou.
HTTPservletcontextcustomization
TheCitrusHTTPserverusesSpringapplicationcontextloadingonstartup.ForhighcustomizationsyoucanprovideacustomservletcontextfilewhichholdsallcustomconfigurationsasSpringbeansfortheserver.HereisasampleservletcontextwithsomebasicSpringMVCcomponentsandthecentralHttpMessageControllerwhichisresponsibleforhandlingincomingrequests(GET,PUT,DELETE,POST,etc.).
<beanid="citrusHandlerMapping"class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"
<beanid="citrusMethodHandlerAdapter"class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"<propertyname="messageConverters"><util:listid="converters"><beanclass="org.springframework.http.converter.StringHttpMessageConverter"><propertyname="supportedMediaTypes"><util:list>
CitrusReferenceGuide
259Http
<value>text/xml</value></util:list></property></bean></util:list></property></bean>
<beanid="citrusHttpMessageController"class="com.consol.citrus.http.controller.HttpMessageController"<propertyname="endpointAdapter"><beanclass="com.consol.citrus.endpoint.adapter.EmptyResponseEndpointAdapter"/></property></bean>
ThebeansaboveareresponsibleforproperHTTPserverconfiguration.Ingeneralyoudonotneedtoadjustthosebeans,butwehavethepossibilitytodosowhichgivesusagreatcustomizationandextensionpoints.TheimportantpartistheendpointadapterdefinitioninsidetheHttpMessageController.Onceaclientrequestwasacceptedtheadapterisresponsibleforgeneratingaproperresponsetotheclient.
YoucanaddthecustomservletcontextasfileresourcetotheCitrusHTTPservercomponent.Justusethecontext-config-locationattributeasfollows:
<citrus-http:serverid="helloHttpServer"port="8080"auto-start="true"context-config-location="classpath:com/consol/citrus/http/custom-servlet-context.xml"resource-base="src/it/resources"/>
CitrusReferenceGuide
260Http
WebSocketsupportTheWebSocketmessageprotocolbuildsontopofHttpstandardandbringsbidirectionalcommunicationtotheHttpclient-serverworld.CitrusisabletosendandreceivemessageswithWebSocketconnectionsasclientandserver.TheHttpserverimplementationisnowabletodefinemultipleWebSocketendpoints.ThenewCitrusWebSocketclientisabletopublishandconsumermessagesviabidirectionalWebSocketprotocol.
ThenewWebSocketsupportislocatedinthemodulecitrus-websocket.ThereforeweneedtoaddthismoduletoourprojectasdependencywhenweareabouttousetheWebSocketfeaturesinCitrus.
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-websocket</artifactId><version>2.7.1</version></dependency>
AsCitrusprovidesacustomizedWebSocketconfigurationschemafortheSpringapplicationcontextconfigurationfileswehavetoaddnametothetoplevelbeanselement.Simplyincludethewebsocket-confignamespaceintheconfigurationXMLfilesasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-websocket="http://www.citrusframework.org/schema/websocket/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/websocket/confighttp://www.citrusframework.org/schema/websocket/config/citrus-websocket-config.xsd"
[...]
</beans>
CitrusReferenceGuide
261HttpWebsockets
NowourprojectisreadytousetheCitrusWebSocketsupport.FirstofallletussendamessageviaWebSocketconnectiontosomeserver.
WebSocketclient
OntheclientsideCitrusoffersaclientcomponentthatgoesdirectlytotheSpringbeanapplicationcontext.Theclientneedsaserverendpointuri.ThisisaWebSocketprotocolendpointuri.
<citrus-websocket:clientid="helloWebSocketClient"url="http://localhost:8080/hello"timeout="5000"/>
Theurldefinestheendpointtosendmessagesto.TheserverhastobeaWebSocketreadywebserverthatsupportsHttpconnectionupgradeforWebSocketprotocols.WebSocketbyitsnatureisanasynchronousbidirectionalprotocol.Thismeansthattheconnectionbetweenclientandserverremainsopenandbothserverandclientcansendandreceivemessages.SowhentheCitrusclientiswaitingforamessageweneedatimeoutthatstopstheasynchronouswaiting.Thereceivingtestactionandthetestcasewillfailwhensuchatimeoutisraised.
TheWebSocketclientwillautomaticallyopenaconnectiontotheserverandaskforaconnectionupgradetoWebSocketprotocol.Thishandshakeisdoneoncewhentheconnectiontotheserverisestablished.Afterthattheclientcanpushmessagestotheserverandontheothersidetheservercanpushmessagestotheclient.Nowletsfirstpushsomemessagestotheserver:
<sendendpoint="helloWebSocketClient"><message><payload><TestMessage><Text>HelloWebSocketServer</Text></TestMessage></payload></message></send>
Theconnectionhandshakeandtheconnectionupgradeisdoneautomaticallybytheclient.Afterthatthemessageispushedtotheserver.AsWebSocketisabidirectionalprotocolwecanalsoreceivemessagesontheWebSocketclient.Thesemessagesarepushedfromservertoallconnectedclients.
CitrusReferenceGuide
262HttpWebsockets
<receiveendpoint="helloWebSocketClient"><message><payload><TestMessage><Text>HelloWebSocketClient</Text></TestMessage></payload></message></receive>
Wejustusetheverysameclientendpointcomponentinamessagereceiveaction.Theclientwillwaitformessagesfromtheserverandoncereceivedperformthewellknownmessagevalidation.HereweexpectsomeXMLmessagepayload.ThiscompletestheclientsideasweareabletopushandconsumermessagesviaWebSocketconnections.
TipUptonowwehaveusedstaticWebSocketendpointURIsinourclientcomponentconfigurations.ThiscanbedonewithamorepowerfuldynamicendpointURIinWebSocketclient.SimilartotheendpointresolvingmechanisminSOAPyoucandynamicallysetthecalledendpointuriattestruntimethroughmessageheadervalues.BydefaultCitruswillcheckaspecificheaderentryfordynamicendpointURIwhichissimplydefinedforeachmessagesendingactioninsidethetest.
ThedynamicEndpointResolverbeanmustimplementtheEndpointUriResolverinterfaceinordertoresolvedynamicendpointurivalues.Citrusoffersadefaultimplementation,theDynamicEndpointUriResolver,whichusesaspecificmessageheaderforsettingdynamicendpointuri.Themessageheaderneedstospecifytheheadercitrus_endpoint_uriwithavalidrequesturi.
<header><elementname="citrus_endpoint_uri"value="ws://localhost:8080/customers/$customerId"</header>
Thespecificsendactionabovewillsenditsmessagetothedynamicendpoint(ws://localhost:8080/customers/$customerId)whichissetintheheadercitrus_endpoint_uri.
WebSocketserverendpoints
CitrusReferenceGuide
263HttpWebsockets
OntheserversideCitrushasaHttpserverimplementationthatwecaneasilystartduringtestruntime.TheHttpserveracceptsconnectionsfromclientsandalsosupportsWebSocketupgradestrategies.ThismeansclientscanaskforaupgradetotheWebSocketstandard.InthishandshaketheserverwillupgradetheconnectiontoWebSocketandafterwardsclientandservercanexchangemessagesoverthisconnection.Thismeanstheconnectioniskeptaliveandmultiplemessagescanbeexchanged.LetsseehowWebSocketendpointsareaddedtoaHttpservercomponentinCitrus.
<citrus-websocket:serverid="helloHttpServer"port="8080"auto-start="true"resource-base="src/it/resources"><citrus-websocket:endpoints><citrus-websocket:endpointref="websocket1"/><citrus-websocket:endpointref="websocket2"/></citrus-websocket:endpoints></citrus-websocket:server>
<citrus-websocket:endpointid="websocket1"path="/test1"/><citrus-websocket:endpointid="websocket2"path="/test2"timeout="10000"/>
TheembeddedJettyWebSocketservercomponentinCitrusnowisabletodefinemultipleWebSocketendpoints.TheWebSocketendpointsmatchtoarequestpathontheserverandarereferencedbyauniqueid.EachWebSocketendpointcanfollowindividualtimeoutsettings.Inatestwecanusetheseendpointsdirectlytoreceivemessages.
<testcasename="httpWebSocketServerTest"><actions><receiveendpoint="websocket1"><message><data>[...]</data></message></receive>
<sendendpoint="websocket1"><message><data>[...]</data></message></send>
CitrusReferenceGuide
264HttpWebsockets
</actions></testcase>
Asyoucanseewereferencetheendpointidinbothreceiveandsendactions.EachWebSocketendpointholdsoneormoreopenconnectionstoitsclients.Eachmessagethatissentispushedtoallconnectedclients.EachclientcansendmessagestotheWebSocketendpoint.
TheWebSocketendpointcomponenthandlesconnectionhandshakesautomaticallyandcachesallopensessionsinmemory.Bydefaultallconnectedclientswillreceivethemessagespushedfromserver.Thisisdonecompletelybehindthescenes.TheCitrusserverisabletohandlemultipleWebSocketendpointswithdifferentclientsconnectedtoitatthesametime.ThisiswhywehavetochoosetheWebSocketendpointontheserverbyitsidentifierwhensendingandreceivingmessages.
WiththisWebSocketendpointswechangetheCitrusserverbehaviorsothatclientscanupgradetoWebSocketconnection.Nowwehaveabidirectionalconnectionwheretheservercanpushmessagestotheclientandviceversa.
WebSocketheaders
TheWebSocketstandarddefinessomedefaultheaderstouseduringconnectionupgrade.Theseheadersaremadeavailabletothetestcaseinbothdirections.CitruswillhandletheseheadervalueswithspecialcarewhenWebSocketsupportisactivatedonaserverorclient.NowWebSocketmessagescanalsobesplitintomultiplepieces.Eachmessagepartispushedseparatelytotheserverbutstillisconsideredtobeasinglemessagepayload.TheserverhastocollectandaggregateallmessagesuntilaspecialmessageheaderisLastissetinoneofthemessageparts.
TheCitrusWebSocketclientcanslicemessagesintoseveralparts.
<sendendpoint="webSocketClient"><messagetype="json"><data>["event":"client_message_1","timestamp":"citrus:currentDate()",</data></message><header><elementname="citrus_websocket_is_last"value="false"/>
CitrusReferenceGuide
265HttpWebsockets
</header></send>
<sleepmilliseconds="500"/>
<sendendpoint="webSocketClient"><messagetype="json"><data>"event":"client_message_2","timestamp":"citrus:currentDate()"]</data></message><header><elementname="citrus_websocket_is_last"value="true"/></header></send>
ThetestabovehastwoseparatesendoperationsbothsendingtoaWebSocketendpoint.Thefirstsendingactionsetstheheadercitrus_websocket_is_lasttofalsewhichindicatesthatthemessageisnotcompleteyet.The2ndsendactionpushestherestofthemessagetotheserverandsetthecitrus_websocket_is_lastheadertotrue.Nowtheserverisabletoaggregatethemessagepiecestoasinglemessagepayload.TheresultisavalidaJSONarraywithbotheventsinit.
["event":"client_message_1","timestamp":"2015-01-01","event":"client_message_2","timestamp":"2015-01-01"]
NowtheserverpartinCitrusisabletohandletheseslicedmessages,too.Theserverwillautomaticallyaggregatethosemessagepartsbeforepassingittothetestcaseforvalidation.
CitrusReferenceGuide
266HttpWebsockets
SOAPWebServicesSOAPWebServicesoverHTTPisawidelyusedcommunicationscenarioinmodernenterpriseapplications.ASOAPWebServiceclientispostingaSOAPrequestviaHTTPtoaserver.SOAPviaHTTPisasynchronousmessageprotocolbydefaultsotheclientiswaitingsynchronouslyfortheresponsemessage.CitrusprovidesbothSOAPclientandservercomponentsinordertomeetbothdirectionsofthisscenario.ThecomponentsusedareverysimilartotheHTTPcomponentsthatwerehavediscussedinthesectionsbefore.
NoteTheSOAPWebServicecomponentsinCitrusarekeptinaseparateMavenmodule.SoyoushouldaddthemoduleasMavendependencytoyourprojectaccordingly.
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-ws</artifactId><version>2.7.1</version></dependency>
InordertousetheSOAPWebServicesupportyouneedtoincludethespecificXMLconfigurationschemaprovidedbyCitrus.SeefollowingXMLdefinitiontofindouthowtoincludethecitrus-wsnamespace.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-ws="http://www.citrusframework.org/schema/ws/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/ws/confighttp://www.citrusframework.org/schema/ws/config/citrus-ws-config.xsd">
[...]
</beans>
CitrusReferenceGuide
267Soap
Nowyouarereadytousethecustomizedsoapconfigurationelements-allusingthecitrus-wsprefix-inyourSpringconfiguration.
SOAPclient
CitrusisabletoformaproperSOAPrequestinordertopassittotheserverviaHTTPandvalidatetherespectiveSOAPresponsemessage.LetusseehowamessageclientforSOAPlookslikeintheSpringconfiguration:
<citrus-ws:clientid="soapClient"request-url="http://localhost:8090/test"timeout="60000"/>
Theclientcomponentusestherequest-urlinordertoaccesstheserverresource.TheclientwillautomaticallybuildaproperSOAPrequestmessageincludingtheSOAPenvelope,SOAPheaderandthemessagepayloadasSOAPbody.ThismeansthatyouasatesterdonotcareaboutSOAPenvelopespecificlogicinthetestcase.TheclientendpointcomponentsavesthesynchronousSOAPresponsesothetestcasecanreceivethismessagewithanormalreceivetestaction.
IndetailyouasatesterjustsendandreceiveusingthesameclientendpointreferencejustasyouwoulddowithasynchronousJMSorchannelcommunication.IncasenoresponsemessageisavailableintimeaccordingtothetimeoutsettingsCitrusraisesatimeouterrorandthetestwillfail.
ImportantTheSOAPclientcomponentusesaSoapMessageFactoryimplementationinordertocreatetheSOAPmessages.ThisisaSpringbeanaddedtotheCitrusSpringapplicationcontext.Springoffersseveralreferenceimplementationsasmessagefactoriessoyoucanchooseoneofthem(e.g.forSOAP1.1or1.2implementations).
<!--DefaultSOAPMessageFactory(SOAP1.1)--><beanid="messageFactory"class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<!--SOAP1.2MessageFactory--><beanid="soap12MessageFactory"class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"<propertyname="soapVersion"><util:constantstatic-field="org.springframework.ws.soap.SoapVersion.SOAP_12"/></property></bean>
CitrusReferenceGuide
268Soap
BydefaultCitruswillsearchforabeanwithid'messageFactory'.IncaseyouintendtousedifferentidentifiersyouneedtotelltheSOAPclientcomponentwhichmessagefactorytouse:
<citrus-ws:clientid="soapClient"request-url="http://localhost:8090/test"message-factory="soap12MessageFactory"/>
TipUptonowwehaveusedastaticendpointrequesturlfortheSOAPmessagesender.Besidesthatwecanusedynamicendpointuriinconfiguration.Wejustuseanendpointuriresolverinsteadofthestaticrequesturllikethis:
<citrus-ws:clientid="soapClient"endpoint-resolver="dynamicEndpointResolver"message-factory="soap12MessageFactory"/>
<beanid="dynamicEndpointResolver"class="com.consol.citrus.endpoint.resolver.DynamicEndpointUriResolver"/>
ThedynamicEndpointResolverbeanmustimplementtheEndpointUriResolverinterfaceinordertoresolvedynamicendpointurivalues.Citrusoffersadefaultimplementation,theDynamicEndpointUriResolver,whichusesaspecificmessageheaderforsettingthedynamicendpointuriforeachmessage.Themessageheaderneedstospecifytheheadercitrus_endpoint_uriwithavalidrequesturi.Justlikethis:
<header><elementname="citrus_endpoint_uri"value="http://localhost:$port/$context"/></header>
Asyoucanseeyoucanusedynamictestvariablestheninordertobuildtherequesturitouse.TheSOAPclientevaluatestheendpointuriheaderandsendsthemessagetothisserverresource.Youcanuseadifferenturivaluethenindifferenttestcasesandsendactions.
SOAPclientinterceptors
Theclientcomponentisabletoaddcustominterceptorsthatparticipateintherequest/responseprocessing.Theinterceptorsneedtoimplementthecommoninterfaceorg.springframework.ws.client.support.interceptor.ClientInterceptor.
CitrusReferenceGuide
269Soap
<citrus-ws:clientid="secureSoapClient"request-url="http://localhost:8080/services/ws/todolist"interceptors="clientInterceptors"/>
<util:listid="clientInterceptors"><beanclass="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"><propertyname="securementActions"value="TimestampUsernameToken"/><propertyname="securementUsername"value="admin"/><propertyname="securementPassword"value="secret"/></bean><beanclass="com.consol.citrus.ws.interceptor.LoggingClientInterceptor"/></util:list>
ThesampleaboveaddsWss4JWsSecurityinterceptorsinordertoaddsecurityconstraintstotherequestmessages.
NoteWhencustomizingtheinterceptorchainalldefaultinterceptors(likelogginginterceptor)arelost.Youneedtoaddtheseinterceptorsexlicitlyasshownwiththecom.consol.citrus.ws.interceptor.LoggingClientInterceptorwhichisabletologrequest/responsemessagesduringcommunication.
SOAPserver
Everyclientneedaservertotalkto.WhenreceivingSOAPmessageswerequireawebserverinstancelisteningonaport.CitrusisusinganembeddedJettyserverinstanceincombinationwiththeSpringWebServiceAPIinordertoacceptSOAPrequestcallsasaserver.SeehowtheCitrusSOAPserverisconfiguredintheSpringconfiguration.
<citrus-ws:serverid="helloSoapServer"port="8080"auto-start="true"resource-base="src/it/resources"/>
Theservercomponentisabletostartautomaticallywhenapplicationstartsup.Intheexampleabovetheserverislisteningforrequestsonport8080.ThissetupusesthestandardconnectorconfigurationfortheJettyserver.FordetailedcustomizationtheCitrusJettyserverconfigurationalsosupportsexplicitconnectorconfigurations(@connectorand@connectorsattributes).FormoreinformationpleaseseetheJettyconnectordocumentation.
Testcasesinteractwiththisserverinstanceviamessagechannelsbydefault.Theservercomponentprovidesaninboundchannelthatholdsincomingrequestmessages.Thetestcasecanreceivethoserequestsfromthechannelwithanormalreceivetest
CitrusReferenceGuide
270Soap
action.InasecondstepthetestcasecanprovideasynchronousresponsemessageasreplywhichwillbeautomaticallysentbacktothecallingSOAPclientasresponse.
Thefigureaboveshowsthebasicsetupwithinboundchannelandreplychannel.Youasatestershouldnotworryaboutthistomuch.Bydefaultyouasatesterjustusetheserverassynchronousendpointinyourtestcase.Thismeansthatyousimplyreceiveamessagefromtheserverandsendaresponseback.
<testcasename="soapServerTest"><actions><receiveendpoint="helloSoapServer"><message><data>[...]</data></message></receive>
<sendendpoint="helloSoapServer"><message><data>[...]</data></message></send></actions></testcase>
Asyoucanseewereferencetheserveridinbothreceiveandsendactions.TheCitrusserverinstancewillautomaticallysendtheresponsebacktothecallingclient.InmostcasesthisiswhatyouneedtosimulateaSOAPserverinstanceinCitrus.Ofcoursewehavesomemorecustomizationpossibilitiesthatwewillgooverlateron.ThiscustomizationsareoptionalsoyoucanalsoskipthenextdescriptiononendpointadaptersifyouarehappywithjustwhatyouhavelearnedabouttheSOAPservercomponentinCitrus.
JustliketheHTTPservercomponenttheSOAPservercomponentbydefaultusesthechannelendpointadapterinordertoforwardallincomingrequeststoaninmemorymessagechannel.Thisisdonecompletelybehindthescenes.TheCitrusconfiguration
CitrusReferenceGuide
271Soap
hasbecomealoteasierheresoyoudonothavetoconfigurethisbydefault.Whennothingelseissetthetestcasedoesnotworryaboutthatsettingsontheserverandjustusestheserveridreferenceassynchronousendpoint.
TipThedefaultchannelendpointadapterautomaticallycreatesaninboundmessagechannelwhereincomingmessagesarestoredtointernally.Soifyouneedtocleanupaserverthathasalreadystoredsomeincomingmessagesyoucandothiseasilybypurgingtheinternalmessagechannel.ThemessagechannelfollowsanamingconventionserverName.inboundwhereserverNameistheSpringbeannameoftheCitrusserverendpointcomponent.Ifyoupurgethisinternalchannelinabeforetestnatureyouaresurethatobsoletemessagesonaserverinstancegetpurgedbeforeeachtestisexecuted.
HoweverwedonotwanttoloosethegreatextendabilityandcustomizingcapabilitiesoftheCitrusservercomponent.ThisiswhyyoucanoptionallydefinetheendpointadapterimplementationusedbytheCitrusSOAPserver.Weprovideseveralmessageendpointadapterimplementationsfordifferentsimulationstrategies.WiththeseendpointadaptersyoushouldbeabletogenerateproperSOAPresponsemessagesfortheclientinvariousways.Beforewehaveacloserlookatthedifferentadapterimplementationswewanttoshowhowyoucansetacustomendpointadapterontheservercomponent.
<citrus-ws:serverid="helloSoapServer"port="8080"auto-start="true"endpoint-adapter="emptyResponseEndpointAdapter"resource-base="src/it/resources"/>
<citrus:empty-response-adapterid="emptyResponseEndpointAdapter"/>
WiththisendpointadapterconfigurationabovewechangetheCitrusserverbehaviorfromscratch.NowtheserverautomaticallysendsbackanemptySOAPresponsemessageeverytime.SettingacustomendpointadapterimplementationwithcustomlogiciseasyasdefiningacustomendpointadapterSpringbeanandreferenceitintheserverattribute.Youcanreadmoreaboutendpointadaptersinendpoint-adapter.
SOAPsendandreceive
Citrusprovidestestactionsforsendingandreceivingmessagesofallkind.Differentmessagecontentanddifferentmessagetransportsareavailabletothesesendandreceiveactions.WhenusingSOAPmessagetransportwemightneedtosetspecial
CitrusReferenceGuide
272Soap
informationonthatmessages.ThesearespecialSOAPheaders,SOAPfaultsandsoon.SowehavecreatedaspecialSOAPnamespaceforallyourSOAPrelatedsendandreceiveoperationsinaXMLDSLtest:
<spring:beansxmlns="http://www.citrusframework.org/schema/testcase"xmlns:spring="http://www.springframework.org/schema/beans"xmlns:ws="http://www.citrusframework.org/schema/ws/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/testcasehttp://www.citrusframework.org/schema/testcase/citrus-testcase.xsdhttp://www.citrusframework.org/schema/ws/testcasehttp://www.citrusframework.org/schema/ws/testcase/citrus-ws-testcase.xsd">
Onceyouhaveaddedthewsnamespacefromabovetoyourtestcaseyouarereadytousespecialsendandreceiveoperationsinthetest.
XMLDSL
<ws:sendendpoint="soapClient"soap-action="MySoapService/sayHello"><message>[...]</message></ws:send>
<ws:receiveendpoint="soapServer"soap-action="MySoapService/sayHello"><message>[...]</message></ws:receive>
Thespecialnamespacecontainsfollowingelements:
send:SpecialsendoperationforsendingoutSOAPmessagecontent.receive:SpecialreceiveoperationforvalidatingSOAPmessagecontent.send-fault:SpecialsendoperationforsendingoutSOAPfaultmessagecontent.assert-fault:SpecialassertionoperationforexpectingaSOAPfaultmessageasresponse.
ThespecialSOAPrelatedsendandreceiveactionscancoexistwithnormalCitrusactions.Infactyoucanmixthoseactiontypesasyouwantinsideofatestcase.AlltestactionsthatworkwithSOAPmessagecontentonclientandserversideshouldusethisspecialnamespace.
CitrusReferenceGuide
273Soap
InJavaDSLwehavesomethingsimilartothat.TheJavaDSLprovidesspecialSOAPrelatedfeatureswhencallingthesoap()method.WithafluentAPIyouareabletothensendandreceiveSOAPmessagecontentasclientandserver.
JavaDSL
@CitrusTestpublicvoidsoapTest()
soap().client("soapClient").send().soapAction("MySoapService/sayHello").payload("...");
soap().client("soapClient").receive().payload("...");
InthefollowingsectionstheSOAPrelatedcapabilitiesarediscussedinmoredetail.
SOAPheaders
SOAPdefinesseveralheadervariationsthatwediscussinthefollowingsections.FirstofallwedealwiththespecialSOAPactionheader.IncaseweneedtosetthisSOAPactionheaderwesimplyneedtousethespecialsoap-actionattributeinourtest.ThespecialheaderkeyincombinationwithaunderlyingSOAPclientendpointcomponentconstructstheSOAPactionintheSOAPmessage.
XMLDSL
<ws:sendendpoint="soapClient"soap-action="MySoapService/sayHello"><message>[...]</message></ws:send>
<ws:receiveendpoint="soapServer"soap-action="MySoapService/sayHello"><message>[...]</message></ws:receive>
JavaDSL
CitrusReferenceGuide
274Soap
@CitrusTestpublicvoidsoapActionTest()
soap().client("soapClient").send().soapAction("MySoapService/sayHello").payload("...");
soap().server("soapClient").receive().soapAction("MySoapService/sayHello").payload("...");
TheSOAPactionheaderisaddedtothemessagebeforesendingandvalidatedwhenusedinareceiveoperation.
NoteThesoap-actionattributeisdefinedinthespecialSOAPnamespaceinCitrus.WerecommendtousethisnamespaceforallyoursendandreceiveoperationsthatdealwithSOAPmessagecontent.HoweveryoucanalsosetthespecialSOAPactionheaderwhennotusingthespecialSOAPnamespace:Justsetthisheaderinyourtestaction:
<header><elementname="citrus_soap_action"value="sayHello"/></header>
SecondlyaSOAPmessageisabletocontaincustomizedSOAPheaders.Thesearekey-valuepairswherethekeyisaqualifiedname(QName)andthevalueanormalStringvalue.
<header><elementname="http://www.consol.de/sayHelloh1:Operation"value="sayHello"/><elementname="http://www.consol.de/sayHelloh1:Request"value="HelloRequest"/></header>
ThekeyisdefinedasqualifiedQNamecharactersequencewhichhasamandatoryXMLnamespaceandaprefixalongwithaheadername.LastnotleastaSOAPheadercancontainwholeXMLfragmentvalues.ThenextexampleshowshowtosettheseXMLfragmentsasSOAPheaderinCitrus:
<header><data><![CDATA[<Userxmlns="http://www.consol.de/schemas/sayHello">
CitrusReferenceGuide
275Soap
<UserId>123456789</UserId><Handshake>S123456789</Handshake></User>]]></data></header>
YoucanalsouseexternalfileresourcestosetthisSOAPheaderXMLfragmentasshowninthislastexamplecode:
<header><resourcefile="classpath:request-soap-header.xml"/></header>
ThiscompletestheSOAPheaderpossibilitiesforsendingSOAPmessageswithCitrus.OfcourseyoucanalsousethesevariantsinSOAPmessageheadervalidation.YoudefineexpectedSOAPheaders,SOAPactionandXMLfragmentsandCitruswillmatchincomingrequesttothat.Justusecitrus_soap_actionheaderkeyinyourreceivingmessageactionandyouvalidatethisSOAPheaderaccordingly.
WhenvalidatingSOAPheaderXMLfragmentsyouneedtodefinethewholeXMLheaderfragmentasexpectedheaderdatalikethis:
<receiveendpoint="soapMessageEndpoint"><message><data><![CDATA[<ResponseMessagexmlns="http://citrusframework.org/schema"><resultCode>OK</resultCode></ResponseMessage>]]></data></message><header><data><![CDATA[<SOAP-ENV:Headerxmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><customHeaderxmlns="http://citrusframework.org/headerschema"><correlationId>$correlationId</correlationId><applicationId>$applicationId</applicationId><trackingId>$trackingId</trackingId><serviceId>$serviceId</serviceId><interfaceVersion>1.0</interfaceVersion><timestamp>@ignore@</timestamp></customHeader>
CitrusReferenceGuide
276Soap
</SOAP-ENV:Header>]]></data><elementname="citrus_soap_action"value="doResponse"/></header></receive>
AsyoucanseetheSOAPXMLheadervalidationcancombineheaderelementandXMLfragmentvalidation.ThisisalsolikelytobeusedwhendealingwithWS-Securitymessageheaders.
SOAPHTTPmimeheaders
BesidestheSOAPspecificheaderelementstheHTTPmimeheaders(e.g.Content-Type,Content-Length,Authorization)mightbecandidatesforvalidation,too.WhenusingHTTPastransportlayertheSOAPmessagemaydefinethosemimeheaders.Thetesterisabletosendandvalidatetheseheadersinsidethetestcase,althoughtheseHTTPheadersarelocatedoutsideoftheSOAPenvelope.LetusfirstofallspeakaboutvalidatingtheHTTPmimeheaders.Thisfeatureisnotenabledbydefault.WehaveenablethisinourSOAPserverconfiguration.
<citrus-ws:serverid="helloSoapServer"port="8080"auto-start="true"handle-mime-headers="true"resource-base="src/it/resources"/>
WiththisconfigurationCitruswillhandleallavailablemimeheadersandpassthosetothetestcasefornormalheadervalidation.
<ws:receiveendpoint="helloSoapServer"><message><payload><SoapMessageRequestxmlns="http://www.consol.de/schemas/sample.xsd"><Operation>Validatemimeheaders</Operation></SoapMessageRequest></payload></message><header><elementname="Content-Type"value="text/xml;charset=utf-8"/></header></ws:receive>
CitrusReferenceGuide
277Soap
ThevalidationoftheseHTTPmimeheadersisasusualnowthatwehaveenabledthemimeheaderhandlingintheSOAPserver.ThetransportHTTPheadersareavailableintheheaderjustlikethenormalSOAPheaderelementsdo.Soyoucanvalidatetheheadersasusual.
SomuchforreceivingandvalidatingHTTPmimemessageheaderswithSOAPcommunication.Nowwewanttosendspecialmimeheadersonclientside.Weoverwriteoraddmimeheaderstooursendingaction.Wemarksomeheaderswithfollowingprefix"citrushttp".ThistellstheSOAPclienttoaddtheseheaderstotheHTTPheadersectionoutsidetheSOAPenvelope.KeepinmindthatheaderelementswithoutthisprefixgorightintotheSOAPheadersectionbydefault.
<ws:sendendpoint="soapClient">[...]<header><elementname="citrus_http_operation"value="foo"/></header>[...]</ws:send>
ThelistingabovedefinesaHTTPmimeheaderoperation.TheheaderprefixcitrushttpiscutoffbeforetheheadergoesintotheHTTPheadersection.Withthisfeaturewecandecidewhereexactlyourheaderinformationislocatedinourresultingclientmessage.
SOAPEnvelopehandling
BydefaultCitruswillremovetheSOAPenvelopeinmessageconverter.FollowingfromthattheCitrustestcaseisindependentfromSOAPmessageformatsandisnotbotheredwithhandlingofSOAPenvelopeatall.ThisisgreatinmostcasesbutsometimesitmightbemandatorytoalsoseethewholeSOAPenvelopeinsidethetestcasereceiveaction.ThereforeyoucankeeptheSOAPenvelopeforincomingmessagesbyconfigurationontheSOAPserverside.
<citrus-ws:serverid="helloSoapServer"port="8080"auto-start="true"keep-soap-envelope="true"/>
WiththisconfigurationCitruswillhandleallavailablemimeheadersandpassthosetothetestcasefornormalheadervalidation.
CitrusReferenceGuide
278Soap
<ws:receiveendpoint="helloSoapServer"><message><payload><SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><SoapMessageRequestxmlns="http://www.consol.de/schemas/sample.xsd"><Operation>Validatemimeheaders</Operation></SoapMessageRequest></SOAP-ENV:Body></SOAP-ENV:Envelope></payload></message></ws:receive>
SonowyouareabletovalidatethewholeSOAPenvelopeasis.Thismightbeofinterestinveryspecialcases.AsmentionedbydefaulttheCitrusserverwillautomaticallyremovetheSOAPenvelopeandtranslatetheSOAPbodytothemessagepayloadforstraightforwardvalidationinsidethetestcases.
SOAPserverinterceptors
TheCitrusSOAPserversupportstheconceptofinterceptorsinordertoaddcustomlogictotherequest/responseprocessingsteps.Theinterceptorsneedtoimplementacommoninterface:org.springframework.ws.server.EndpointInterceptor.Weareabletocustomizetheinterceptorchainontheservercomponentasfollows:
<citrus-ws:serverid="secureSoapServer"port="8080"auto-start="true"interceptors="serverInterceptors"/>
<util:listid="serverInterceptors"><beanclass="com.consol.citrus.ws.interceptor.SoapMustUnderstandEndpointInterceptor"><propertyname="acceptedHeaders"><list><value>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsdSecurity</list></property></bean><beanclass="com.consol.citrus.ws.interceptor.LoggingEndpointInterceptor"/><beanclass="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"><propertyname="validationActions"value="TimestampUsernameToken"/><propertyname="validationCallbackHandler"><beanid="passwordCallbackHandler"class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler"<propertyname="usersMap">
CitrusReferenceGuide
279Soap
<map><entrykey="admin"value="secret"/></map></property></bean></property></bean></util:list>
ThecustominterceptorsareusedtoenableWsSecurityfeaturesonthesoapservercomponentviaWss4J.
NoteWhencustomizingtheinterceptorchainofthesoapservercomponentalldefaultinterceptors(likelogginginterceptors)arelost.Youcanseethatwehadtoaddthecom.consol.citrus.ws.interceptor.LoggingEndpointInterceptorexplicitlyinordertologrequest/responsemessagesfortheservercommunication.
SOAP1.2
BydefaultCitruscomponentsuseSOAP1.1version.FortunatelySOAP1.2issupportedsameway.AswealreadymentionedbeforetheCitrusSOAPcomponentsdouseaSOAPmessagefactoryforcreatingmessagesinSOAPformat.
<!--SOAP1.1MessageFactory--><beanid="soapMessageFactory"class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"<propertyname="soapVersion"><util:constantstatic-field="org.springframework.ws.soap.SoapVersion.SOAP_11"/></property></bean>
<!--SOAP1.2MessageFactory--><beanid="soap12MessageFactory"class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"<propertyname="soapVersion"><util:constantstatic-field="org.springframework.ws.soap.SoapVersion.SOAP_12"/></property></bean>
AsyoucanseetheSOAPmessagefactorycaneithercreateSOAP1.1orSOAP1.2messages.ThisishowCitruscancreatebothSOAP1.1andSOAP1.2messages.Ofcourseyoucanhavemultiplemessagefactoriesconfiguredinyourproject.JustsetthemessagefactoryonaWebServiceclientorservercomponentinordertodefinewhichversionshouldbeused.
CitrusReferenceGuide
280Soap
<citrus-ws:clientid="soap12Client"request-url="http://localhost:8080/echo"message-factory="soap12MessageFactory"timeout="1000"/>
<citrus-ws:serverid="soap12Server"port="8080"auto-start="true"root-parent-context="true"message-factory="soap12MessageFactory"/>
BydefaultCitruscomponentsdoconnectwithamessagefactorycalledmessageFactorynomatterwhatSOAPversionthisfactoryisusing.
SOAPfaults
SOAPfaultsdescribeafailedcommunicationinSOAPWebServicesworld.CitrusisabletosendandreceiveSOAPfaultmessages.OnserversideCitruscansimulateSOAPfaultswithfault-code,fault-reason,fault-actorandfault-detail.OnclientsideCitrusisabletohandleandvalidateSOAPfaultsinresponsemessages.ThenextsectiondescribeshowtodealwithSOAPfaultsinCitrus.
SendSOAPfaults
AsCitrussimulatesSOAPserverendpointsyoualsoneedtothinkaboutsendingaSOAPfaulttothecallingclient.IncaseCitrusreceivesaSOAPrequestasaserveryoucanrespondwithaproperSOAPfaultifnecessary.
Pleasekeepinmindthatweusethecitrus-wsextensionforsendingSOAPfaultsinourtestcase,asshowninthisverysimpleexample:
XMLDSL
<ws:send-faultendpoint="helloSoapServer"><ws:fault><ws:fault-code>http://www.citrusframework.org/faultscitrus:TEC-1000</ws:fault-code><ws:fault-string>Invalidrequest</ws:fault-string><ws:fault-actor>SERVER</ws:fault-actor><ws:fault-detail><![CDATA[<FaultDetailxmlns="http://www.consol.de/schemas/sayHello.xsd"><MessageId>$messageId</MessageId><CorrelationId>$correlationId</CorrelationId><ErrorCode>TEC-1000</ErrorCode>
CitrusReferenceGuide
281Soap
<Text>Invalidrequest</Text></FaultDetail>]]></ws:fault-detail></ws:fault><ws:header><ws:elementname="citrus_soap_action"value="sayHello"/></ws:header></ws:send-fault>
TheexamplegeneratesasimpleSOAPfaultthatissentbacktothecallingclient.Thefault-actorandthefault-detailelementsareoptional.SamewiththesoapactiondeclaredinthespecialCitrusheadercitrus_soap_action.Inthesampleabovethefault-detaildataisplacedinlineasXMLdata.Asanalternativetothatyoucanalsosetthefault-detailviaexternalfileresource.JustusethefileattributeasfaultdetailinsteadoftheinlineCDATAdefinition.
XMLDSL
<ws:send-faultendpoint="helloSoapServer"><ws:fault><ws:fault-code>http://www.citrusframework.org/faultscitrus:TEC-1000</ws:fault-code><ws:fault-string>Invalidrequest</ws:fault-string><ws:fault-actor>SERVER</ws:fault-actor><ws:fault-detailfile="classpath:myFaultDetail.xml"/></ws:fault><ws:header><ws:elementname="citrus_soap_action"value="sayHello"/></ws:header></ws:send-fault>
ThegeneratedSOAPfaultlookslikefollows:
HTTP/1.1500InternalServerErrorAccept:text/xml,text/html,image/gif,image/jpeg,*;q=.2,*/*;q=.2SOAPAction:"sayHello"Content-Type:text/xml;charset=utf-8Content-Length:680Server:Jetty(7.0.0.pre5)
<SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><SOAP-ENV:Fault>
CitrusReferenceGuide
282Soap
<faultcodexmlns:citrus="http://www.citrusframework.org/faults">citrus:TEC-1000</<faultstringxml:lang="en">Invalidrequest</faultstring><detail><FaultDetailxmlns="http://www.consol.de/schemas/sayHello.xsd"><MessageId>9277832563</MessageId><CorrelationId>4346806225</CorrelationId><ErrorCode>TEC-1000</ErrorCode><Text>Invalidrequest</Text></FaultDetail></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>
ImportantNoticethatthesendactionusesaspecialXMLnamespace(ws:send).ThiswsnamespacebelongstotheCitrusWebServiceextensionandaddsSOAPspecificfeaturestothenormalsendaction.Whenyouusesuchwsextensionsyouneedtodefinetheadditionalnamespaceinyourtestcase.Thisisusuallydoneintheroot<spring:beans>elementwherewesimplydeclarethecitrus-wsspecificnamespacelikefollows.```xml
###ReceiveSOAPfaults
IncaseyoureceiveSOAPresponsemessagesasaclientendpointyoumayneedtohandleandvalidateSOAPfaultsinerrorsituations.CitruscanvalidateSOAPfaultswithfault-code,fault-actor,fault-stringandfault-detailvalues.
AsaclientwesendoutarequestandreceiveaSOAPfaultasresponse.BydefaulttheclientsendingactioninCitrusthrowsaspecificexceptionwhentheSOAPresponseisaSOAPfaultelement.Thisexceptioniscalled***SoapFaultClientException***comingfromtheSpringAPI.YouasatestercanassertthiskindofexceptioninatestcaseinordertoexpecttheSOAPerror.
**XMLDSL**
```xml<assertclass="org.springframework.ws.soap.client.SoapFaultClientException"><sendendpoint="soapClient"><message><payload><SoapFaultForcingRequestxmlns="http://www.consol.de/schemas/soap"><Message>Thisisinvalid</Message></SoapFaultForcingRequest></payload></message></send></assert>
CitrusReferenceGuide
283Soap
TheSOAPmessagesendingactionissurroundedbyasimpleassertaction.TheassertedexceptionclassistheSoapFaultClientExceptionthatwehavementionedbefore.Thismeansthatthetestexpectstheexceptiontobethrownduringthecommunication.Incasetheexceptionismissingthetestisfails.
SofarwehaveusedtheCitruscorecapabilitiesofassertinganexception.ThisbasicassertiontestactionisnotabletoofferdirectaccesstotheSOAPfault-codeandfault-stringvaluesforvalidation.ThebasicassertactionsimplyhasnoaccesstotheactualSOAPfaultelements.Fortunatelywecanusethecitrus-wsnamespaceagainwhichoffersaspecialassertactionimplementationespeciallydesignedforSOAPfaultsinthiscase.
XMLDSL
<ws:assert-faultfault-code="http://www.citrusframework.org/faultsTEC-1001"fault-string="Invalidrequest">fault-actor="SERVER"><ws:when><sendendpoint="soapClient"><message><payload><SoapFaultForcingRequestxmlns="http://www.consol.de/schemas/soap"><Message>Thisisinvalid</Message></SoapFaultForcingRequest></payload></message></send></ws:when></ws:assert-fault>
ThespecialassertactionoffersseveralattributestovalidatetheexpectedSOAPfault.Namelytheseare"fault-code","fault-string"and"fault-actor".Thefault-codeisdefinedasaQNamestringandismandatoryforthevalidation.Thefaultassertionalsosupportstestvariablereplacementasusual(e.g.fault-code="http://www.citrusframework.org/faults$myFaultCode").
ThetimeyouuseSOAPfaultvalidationyouneedtotellCitrushowtovalidatetheSOAPfaults.CitrusneedsaninstanceofaSoapFaultValitatorthatweneedtoaddtotheSpringapplicationcontext.BydefaultCitrusissearchingforabeanwiththeid'soapFaultValidator'.
<beanid="soapFaultValidator"class="com.consol.citrus.ws.validation.SimpleSoapAttachmentValidator"
CitrusReferenceGuide
284Soap
CitrusoffersseveralreferenceimplementationsfortheseSOAPfaultvalidators.Theseare:
com.consol.citrus.ws.validation.SimpleSoapAttachmentValidatorcom.consol.citrus.ws.validation.SimpleSoapFaultValidatorcom.consol.citrus.ws.validation.XmlSoapFaultValidator
PleaseseetheAPIdocumentationfordetailsontheavailablereferenceimplementations.OfcourseyoucanalsodefineyourownSOAPvalidatorlogic(wouldbegreatifyoucouldshareyourideas!).Inthetestcaseyoucanexplicitlychoosethevalidatortouse:
XMLDSL
<ws:assert-faultfault-code="http://www.citrusframework.org/faultsTEC-1001"fault-string="Invalidrequest"fault-validator="mySpecialSoapFaultValidator">[...]</ws:assert-fault>
ImportantAnotherimportantthingtonoticewhenassertingSOAPfaultsisthefact,thatCitrusneedstohaveaSoapMessageFactoryavailableintheSpringapplicationcontext.IfyoudealwithSOAPmessagingingeneralyouwillalreadyhavesuchabeaninthecontext.
<beanid="messageFactory"class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
ChooseoneofSpring'sreferenceimplementationsorsomeotherimplementationasSOAPmessagefactory.Citruswillsearchforabeanwithid'messageFactory'bydefault.IncaseyouhaveotherbeanswithdifferentidentifierspleasechoosethemessageFactoryinthetestcaseassertaction:
XMLDSL
<ws:assert-faultfault-code="http://www.citrusframework.org/faultsTEC-1001"fault-string="Invalidrequest"message-factory="mySpecialMessageFactory">[...]</ws:assert-fault>
CitrusReferenceGuide
285Soap
ImportantNoticethewsspecificnamespacethatbelongstotheCitrusWebServiceextensions.Asthews:assertactionusesSOAPspecificfeaturesweneedtorefertothecitrus-wsnamespace.Youcanfindthenamespacedeclarationintherootelementinyourtestcase.```xml
CitrusisalsoabletovalidateSOAPfaultdetails.Seethefollowingexampleforunderstandinghowtodoit:
**XMLDSL**
```xml<ws:assert-faultfault-code="http://www.citrusframework.org/faultsTEC-1001"fault-string="Invalidrequest"><ws:fault-detail><![CDATA[<FaultDetailxmlns="http://www.consol.de/schemas/soap"><ErrorCode>TEC-1000</ErrorCode><Text>Invalidrequest</Text></FaultDetail>]]></ws:fault-detail><ws:when><sendendpoint="soapClient"><message><payload><SoapFaultForcingRequestxmlns="http://www.consol.de/schemas/soap"><Message>Thisisinvalid</Message></SoapFaultForcingRequest></payload></message></send></ws:when></ws:assert-fault>
TheexpectedSOAPfaultdetailcontentissimplyaddedtothews:assertaction.TheSoapFaultValidatorimplementationdefinedintheSpringapplicationcontextisresponsibleforcheckingtheSOAPfaultdetailwithvalidationalgorithm.Thevalidatorimplementationchecksthedetailcontenttomeettheexpectedtemplate.CitrusprovidessomedefaultSoapFaultValidatorimplementations.SupportedalgorithmsarepureStringcomparison(com.consol.citrus.ws.validation.SimpleSoapFaultValidator)aswellasXMLtreewalk-through(com.consol.citrus.ws.validation.XmlSoapFaultValidator).
CitrusReferenceGuide
286Soap
WhenusingtheXMLvalidationalgorithmyouhavethecompletepowerasknownfromnormalmessagevalidationinreceiveactions.Thisincludesschemavalidationorignoringelementsforinstance.Onthefault-detailelementyouareabletoaddsomevalidationsettingssuchasschema-validation=enabled/disabled,customschema-repositoryandsoon.
XMLDSL
<ws:assert-faultfault-code="http://www.citrusframework.org/faultsTEC-1001"fault-string="Invalidrequest"><ws:fault-detailschema-validation="false"><![CDATA[<FaultDetailxmlns="http://www.consol.de/schemas/soap"><ErrorCode>TEC-1000</ErrorCode><Text>Invalidrequest</Text></FaultDetail>]]></ws:fault-detail><ws:when><sendendpoint="soapClient">[...]</send></ws:when></ws:assert-fault>
PleaseseealsotheCitrusAPIdocumentationforavailablevalidatorimplementationsandvalidationalgorithms.
SofarwehaveusedassertactionwrapperinordertocatchSOAPfaultexceptionsandvalidatetheSOAPfaultcontent.NowwehaveanalternativewayofhandlingSOAPfaultsinCitrus.WithexceptionsthesendactionabortsandwedonothaveareceiveactionfortheSOAPfault.ThismightbeinadequateifweneedtovalidatetheSOAPmessagecontent(SOAPHeaderandSOAPBody)comingwiththeSOAPfault.Thereforethewebservicemessagesendercomponentoffersseveralfaultstrategyoptions.InthefollowingwediscussthepropagationofSOAPfaultasmessagestothereceiveactionaswewoulddowithnormalSOAPmessages.
<citrus-ws:clientid="soapClient"request-url="http://localhost:8090/test"fault-strategy="propagateError"/>
CitrusReferenceGuide
287Soap
WehaveconfiguredafaultstrategypropagateErrorsothemessagesenderwillnotraiseclientexceptionsbutinformthereceiveactionwithSOAPfaultmessagecontents.Bydefaultthefaultstrategyraisesclientexceptions(fault-strategy=throwsException).
Sonowthatwedonotraiseexceptionswecanleaveouttheassertactionwrapperinourtest.InsteadwesimplyuseareceiveactionandvalidatetheSOAPfaultlikethis.
<sendendpoint="soapClient"><message><payload><SoapFaultForcingRequestxmlns="http://www.consol.de/schemas/sample.xsd"><Message>Thisisinvalid</Message></SoapFaultForcingRequest></payload></message></send>
<receiveendpoint="soapClient"timeout="5000"><message><payload><SOAP-ENV:Faultxmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><faultcodexmlns:CITRUS="http://citrus.org/soap">CITRUS:$soapFaultCode</faultcode<faultstringxml:lang="en">$soapFaultString</faultstring></SOAP-ENV:Fault></payload></message></receive>
SochoosethepreferredwayofhandlingSOAPfaultseitherbyassertingclientexceptionsorpropagatingfaultmessagestothereceiveactiononaSOAPclient.
MultipleSOAPfaultdetails
SOAPfaultmessagescanholdmultipleSOAPfaultdetailelements.IntheprevioussectionswehaveusedSOAPfaultdetailsinsendingandreceivingactionsassingleelement.InordertomeettheSOAPspecificationCitrusisalsoabletohandlemultipleSOAPfaultdetailelementsinamessage.Youjustusemultiplefault-detailelementsinyourtestactionlikethis:
<ws:send-faultendpoint="helloSoapServer"><ws:fault><ws:fault-code>http://www.citrusframework.org/faultscitrus:TEC-1000</ws:fault-code><ws:fault-string>Invalidrequest</ws:fault-string><ws:fault-actor>SERVER</ws:fault-actor>
CitrusReferenceGuide
288Soap
<ws:fault-detail><![CDATA[<FaultDetailxmlns="http://www.consol.de/schemas/sayHello.xsd"><MessageId>$messageId</MessageId><CorrelationId>$correlationId</CorrelationId><ErrorCode>TEC-1000</ErrorCode><Text>Invalidrequest</Text></FaultDetail>]]></ws:fault-detail><ws:fault-detail><![CDATA[<ErrorDetailxmlns="http://www.consol.de/schemas/sayHello.xsd"><ErrorCode>TEC-1000</ErrorCode></ErrorDetail>]]></ws:fault-detail></ws:fault><ws:header><ws:elementname="citrus_soap_action"value="sayHello"/></ws:header></ws:send-fault>
ThiswillresultinfollowingSOAPenvelopemessage:
HTTP/1.1500InternalServerErrorAccept:text/xml,text/html,image/gif,image/jpeg,*;q=.2,*/*;q=.2SOAPAction:"sayHello"Content-Type:text/xml;charset=utf-8Content-Length:680Server:Jetty(7.0.0.pre5)
<SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><SOAP-ENV:Fault><faultcodexmlns:citrus="http://www.citrusframework.org/faults">citrus:TEC-1000</<faultstringxml:lang="en">Invalidrequest</faultstring><detail><FaultDetailxmlns="http://www.consol.de/schemas/sayHello.xsd"><MessageId>9277832563</MessageId><CorrelationId>4346806225</CorrelationId><ErrorCode>TEC-1000</ErrorCode><Text>Invalidrequest</Text></FaultDetail><ErrorDetailxmlns="http://www.consol.de/schemas/sayHello.xsd"><ErrorCode>TEC-1000</ErrorCode></ErrorDetail></detail>
CitrusReferenceGuide
289Soap
</SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>
OfcoursewecanalsoexpectseveralfaultdetailelementswhenreceivingaSOAPfault.
XMLDSL
<ws:assert-faultfault-code="http://www.citrusframework.org/faultsTEC-1001"fault-string="Invalidrequest"><ws:fault-detailschema-validation="false"><![CDATA[<FaultDetailxmlns="http://www.consol.de/schemas/soap"><ErrorCode>TEC-1000</ErrorCode><Text>Invalidrequest</Text></FaultDetail>]]></ws:fault-detail><ws:fault-detail><![CDATA[<ErrorDetailxmlns="http://www.consol.de/schemas/soap"><ErrorCode>TEC-1000</ErrorCode></ErrorDetail>]]></ws:fault-detail><ws:when><sendendpoint="soapClient">[...]</send></ws:when></ws:assert-fault>
Asyoucanseewecanindividuallyusevalidationsettingsforeachfaultdetail.Intheexampleabovewedisabledschemavalidationforthefirstfaultdetailelement.
SendHTTPerrorcodeswithSOAP
TheSOAPserverlogicinCitrusisabletosimulatepureHTTPerrorcodessuchas404"Notfound"or500"Internalservererror".ThegoodthingisthattheCitrusserverisabletoreceivearequestforpropervalidationinareceiveactionandthensimulateHTTPerrorsondemand.
CitrusReferenceGuide
290Soap
ThemechanismonHTTPerrorcodesimulationisnotdifferenttotheusualSOAPrequest/responsehandlinginCitrus.Wereceivetherequestasusualandweprovidearesponse.TheHTTPerrorsituationissimulatedaccordingtothespecialHTTPheadercitrus_http_statusintheCitrusSOAPresponsedefinition.Incasethisheaderissettoavalueotherthan200OKtheCitrusSOAPserversendsanemptySOAPresponsewithHTTPerrorstatuscodesetaccordingly.
<receiveendpoint="helloSoapServer"><message><payload><Messagexmlns="http://consol.de/schemas/sample.xsd"><Text>HelloSOAPserver</Text></Message></payload></message></receive>
<sendendpoint="helloSoapServer"><message><data></data></message><header><elementname="citrus_http_status_code"value="500"/></header></send>
TheSOAPresponsemustbeemptyandtheHTTPstatuscodeissettoavalueotherthan200,like500.ThisresultsinaHTTPerrorsenttothecallingclientwitherror500"Internalservererror".
SOAPattachmentsupport
CitrusisabletoaddattachmentstoaSOAPrequestonclientandserverside.AsusualyoucanvalidatetheSOAPattachmentcontentonareceivedSOAPmessage.ThenextchaptersdescribehowtohandleSOAPattachmentsinCitrus.
SendSOAPattachments
AsclientCitrusisabletoaddattachmentstotheSOAPmessage.Ithinkitisbesttogostraightintoanexampleinordertounderstandhowitworks.
<ws:sendendpoint="soapClient"><message>
CitrusReferenceGuide
291Soap
<payload><SoapMessageWithAttachmentxmlns="http://consol.de/schemas/sample.xsd"><Operation>Readtheattachment</Operation></SoapMessageWithAttachment></payload></message><ws:attachmentcontent-id="MySoapAttachment"content-type="text/plain"><ws:resourcefile="classpath:com/consol/citrus/ws/soapAttachment.txt"/></ws:attachment></ws:send>
NoteInthepreviouschaptersyoumayhavealreadynoticedthecitrus-wsnamespacethatstandsfortheSOAPextensionsinCitrus.Pleaseincludethecitrus-wsnamespaceinyourtestcaseasdescribedearlierinthischaptersoyoucanusetheattachmentsupport.
ThespecialsendactionoftheSOAPextensionnamespaceisawareofSOAPattachments.Theattachmentcontentusuallyconsistsofacontent-idacontent-typeandtheactualcontentasplaintextorbinarycontent.InsidethetestcaseyoucanuseexternalfileresourcesorinlineCDATAsectionsfortheattachmentcontent.AsyouarefamiliarwithCitrusyoumayknowthisalreadyfromotheractions.
CitruswillconstructaSOAPmessagewiththeSOAPattachment.Currentlyonlyoneattachmentpermessageissupported.
ReceiveSOAPattachments
WhenCitruscallsSOAPWebServicesasaclientwemayreceiveSOAPresponseswithattachments.ThetestercanvalidatethosereceivedSOAPmessageswithattachmentcontentquiteeasy.Asusualletushavealookatanexamplefirst.
<ws:receiveendpoint="soapClient"><message><payload><SoapMessageWithAttachmentRequestxmlns="http://consol.de/schemas/sample.xsd"><Operation>Readtheattachment</Operation></SoapMessageWithAttachmentRequest></payload></message><ws:attachmentcontent-id="MySoapAttachment"content-type="text/plain"validator="mySoapAttachmentValidator"><ws:resourcefile="classpath:com/consol/citrus/ws/soapAttachment.txt"/></ws:attachment></ws:receive>
CitrusReferenceGuide
292Soap
AgainweusetheCitrusSOAPextensionnamespacewiththespecificreceiveactionthatisawareofSOAPattachmentvalidation.Thetestercanvalidatethecontent-id,thecontent-typeandtheattachmentcontent.InsteadofusingtheexternalfileresourceyoucouldalsodefineanexpectedattachmenttemplatedirectlyinthetestcaseasinlineCDATAsection.
NoteThews:attachmentelementspecifiesavalidatorinstance.Thisvalidatordetermineshowtovalidatetheattachmentcontent.SOAPattachmentsarenotlimitedtoXMLcontent.Plaintextcontentandbinarycontentispossible,too.SoeachSOAPattachmentvalidatingactioncanuseadifferentSoapAttachmentValidatorinstancewhichisresponsibleforvalidatingandcomparingreceivedattachmentstoexpectedtemplateattachments.IntheCitrusconfigurationthevalidatorissetasnormalSpringbeanwiththerespectiveidentifier.
<beanid="soapAttachmentValidator"class="com.consol.citrus.ws.validation.SimpleSoapAttachmentValidator"<beanid="mySoapAttachmentValidator"class="com.company.ws.validation.MySoapAttachmentValidator"
YoucandefineseveralvalidatorinstancesintheCitrusconfiguration.Thevalidatorwiththegeneralid"soapAttachmentValidator"isthedefaultvalidatorforallactionsthatdonotexplicitlysetavalidatorinstance.Citrusoffersasetofreferencevalidatorimplementations.TheSimpleSoapAttachmentValidatorwilluseasimpleplaintextcomparison.Ofcourseyouareabletoaddindividualvalidatorimplementations,too.
SOAPMTOMsupport
MTOM(MessageTransmissionOptimizationMechanism)enablesyoutosendandreceivelargeSOAPmessagecontentusingstreameddatahandlers.Thisoptimizestheresourceallocationonserverandclientsidewherenotalldataisloadedintomemorywhenmarshalling/unmarshallingthemessagepayloaddata.IndetailMTOMenabledmessagesdohaveaXOPpackageinsidethemessagepayloadreplacingtheactuallargecontentdata.Thecontentisthenstreamedaasseparateattachment.Serverandclientcanoperatewithadatahandlerprovidingaccesstothestreamedcontent.ThisisveryhelpfulwhenusinglargebinarycontentinsideaSOAPmessageforinstance.
CitrusisabletobothsendandreceiveMTOMenabledSOAPmessagesonclientandserver.Justusethemtom-enabledflagwhensendingaSOAPmessage:
<ws:sendendpoint="soapMtomClient"mtom-enabled="true"><message>
CitrusReferenceGuide
293Soap
<data><![CDATA[<image:addImagexmlns:image="http://www.citrusframework.org/imageService/"><image>cid:IMAGE</image></image:addImage>]]></data></message><ws:attachmentcontent-id="IMAGE"content-type="application/octet-stream"><ws:resourcefile="classpath:com/consol/citrus/hugeImageData.png"/></ws:attachment></ws:send>
AsyoucanseetheexampleabovesendsaSOAPmessagethatcontainsalargebinaryimagecontent.Theactualbinaryimagedataisreferencedwithacontentidmarkercid:IMAGEinsidethemessagepayload.Theactualimagecontentisaddedasattachmentwithaseparatefileresource.Importantisherethecontent-idwhichmatchestheidmarkerintheSOAPmessagepayload(IMAGE).
CitrusbuildsaproperSOAPMTOMenabledmessageautomaticallyaddingtheXOPpackageinsidethemessage.ThebinarydataissentasseparateSOAPattachmentaccordingly.TheresultingSOAPmessagelookslikethis:
<SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header></SOAP-ENV:Header><SOAP-ENV:Body><image:addImagexmlns:image="http://www.citrusframework.org/imageService/"><image><xop:Includexmlns:xop="http://www.w3.org/2004/08/xop/include"href="cid:IMAGE"/></image:addImage></SOAP-ENV:Body></SOAP-ENV:Envelope>
OntheserversideCitrusisalsoabletohandleMTOMenabledSOAPmessages.InaserverreceiveactionyoucanspecifytheMTOMSOAPattachmentcontentasfollows.
<ws:receiveendpoint="soapMtomServer"mtom-enabled="true"><messageschema-validation="false"><data><![CDATA[<image:addImagexmlns:image="http://www.citrusframework.org/imageService/"><image><xop:Includexmlns:xop="http://www.w3.org/2004/08/xop/include"href="cid:IMAGE"/></image></image:addImage>]]></data></message>
CitrusReferenceGuide
294Soap
<ws:attachmentcontent-id="IMAGE"content-type="application/octet-stream"><ws:resourcefile="classpath:com/consol/citrus/hugeImageData.png"/></ws:attachment></ws:receive>
WedefinetheMTOMattachmentcontentasseparateSOAPattachment.Thecontent-idisreferencedsomewhereintheSOAPmessagepayloaddata.AtruntimeCitruswilladdtheXOPpackagedefinitionautomaticallyandperformvalidationonthemessageanditsstreamedMTOMattachmentdata.
NextthingthatwehavetotalkaboutisinlineMTOMdata.Thismeansthatthecontentshouldbeaddedaseitherbase64BinaryorhexBinaryencodedStringdatadirectlytothemessagecontent.Seethefollowingexamplethatusesthemtom-inlinesetting:
<ws:sendendpoint="soapMtomClient"mtom-enabled="true"><message><data><![CDATA[<image:addImagexmlns:image="http://www.citrusframework.org/imageService/"><image>cid:IMAGE</image><icon>cid:ICON</icon></image:addImage>]]></data></message><ws:attachmentcontent-id="IMAGE"content-type="application/octet-stream"mtom-inline="true"encoding-type="base64Binary"><ws:resourcefile="classpath:com/consol/citrus/image.png"/></ws:attachment><ws:attachmentcontent-id="ICON"content-type="application/octet-stream"mtom-inline="true"encoding-type="hexBinary"><ws:resourcefile="classpath:com/consol/citrus/icon.ico"/></ws:attachment></ws:send>
ThelistingabovedefinestwoinlineMTOMattachments.Thefirstattachmentcid:IMAGEusestheencodingtypebase64Binarywhichisthedefault.Thesecondattachmentcid:ICONuseshexBinaryencoding.Bothattachmentsareaddedasinlinedatabeforethemessageissent.ThefinalSOAPmessagelookslikefollows:
<SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header></SOAP-ENV:Header><SOAP-ENV:Body><image:addImagexmlns:image="http://www.citrusframework.org/imageService/"><image>VGhpcyBpcyBhIGJpbmFyeSBpbWFnZSBhdHRhY2htZW50IQpWYXJpYWJsZXMgJXt0ZXN0fSBzaG91bGQgbm90IGJlIHJlcGxhY2VkIQ==
CitrusReferenceGuide
295Soap
<icon>5468697320697320612062696E6172792069636F6E206174746163686D656E74210A5661726961626C657320257B746573747D2073686F756C64206E6F74206265207265706C6163656421</image:addImage></SOAP-ENV:Body></SOAP-ENV:Envelope>
Theimagecontentisabase64BinaryStringandtheiconaheyBinaryString.OfcoursethismechanismalsoissupportedinreceiveactionsontheserversidewheretheexpectedmessagecontentisaddedalsinlineMTOMdatabeforevalidationtakesplace.
SOAPclientbasicauthentication
AsaSOAPclientyoumayhavetousebasicauthenticationinordertoaccessaserverresource.BasicauthenticationviaHTTPstandsforusername/passwordauthenticationwherethecredentialsaretransmittedintheHTTPrequestheadersectionasbase64encodedentry.AsCitrususestheSpringWebServicestackwecanusethebasicauthenticationsupportthere.WesettheusercredentialsontheHttpClientmessagesenderwhichisusedinsidetheSpringWebServiceTemplate.
CitrusprovidesacomfortablewaytosettheHTTPmessagesenderwithbasicauthenticationcredentialsontheWebServiceTemplate.Justseethefollowingexampleandlearnhowtodothat.
<citrus-ws:clientid="soapClient"request-url="http://localhost:8090/test"message-sender="basicAuthClient"/>
<beanid="basicAuthClient"class="org.springframework.ws.transport.http.HttpComponentsMessageSender"<propertyname="authScope"><beanclass="org.apache.http.auth.AuthScope"><constructor-argvalue="localhost"/><constructor-argvalue="8090"/><constructor-argvalue=""/><constructor-argvalue="basic"/></bean></property><propertyname="credentials"><beanclass="org.apache.http.auth.UsernamePasswordCredentials"><constructor-argvalue="someUsername"/><constructor-argvalue="somePassword"/></bean></property></bean>
CitrusReferenceGuide
296Soap
TheaboveconfigurationresultsinSOAPrequestswithauthenticationheadersproperlysetforbasicauthentication.ThespecialmessagesendertakescareonaddingtheproperbasicauthenticationheadertoeachrequestthatissentwiththisCitrusmessagesender.Bydefaultpreemtiveauthenticationisused.Themessagesenderonlysendsasinglerequesttotheserverwithallauthenticationinformationsetinthemessageheader.Therequestwhichdeterminestheauthenticationschemeontheserverisskipped.ThisiswhyyouhavetoaddsomeauthscopesoCitruscansetupanauthenticationcachewithintheHTTPcontextinordertohavepreemtiveauthentication.
TipYoucanalsoskipthemessagesenderconfigurationandsettheAuthorizationheaderoneachrequestinyoursendactiondefinitiononyourown.BeawareofsettingtheheaderasHTTPmimeheaderusingthecorrectprefixandtakecareonusingthecorrectbasicauthenticationwithbase64encodingfortheusername:passwordphrase.
<header><elementname="citrus_http_Authorization"value="Basicc29tZVVzZXJuYW1lOnNvbWVQYXNzd29yZA=="</header>
Forbase64encodingyoucanalsouseaCitrusfunction,seefunctions-encode-base64
SOAPserverbasicauthentication
WhenprovidingSOAPWebServiceserverfunctionalityCitruscanalsosetbasicauthenticationsoallclientsneedtoauthenticateproperlywhenaccessingtheserverresource.
<citrus-ws:serverid="simpleSoapServer"port="8080"auto-start="true"resource-base="src/it/resources"security-handler="basicSecurityHandler"/>
<beanid="securityHandler"class="com.consol.citrus.ws.security.SecurityHandlerFactory"><propertyname="users"><list><beanclass="com.consol.citrus.ws.security.User"><propertyname="name"value="citrus"/><propertyname="password"value="secret"/><propertyname="roles"value="CitrusRole"/></bean></list></property><propertyname="constraints">
CitrusReferenceGuide
297Soap
<map><entrykey="/foo/*"><beanclass="com.consol.citrus.ws.security.BasicAuthConstraint"><constructor-argvalue="CitrusRole"/></bean></entry></map></property></bean>
Wehavesetasecurityhandlerontheserverwebcontainerwithaconstraintonallresourceswith/foo/*.Followingfromthattheserverrequiresbasicauthenticationfortheseresources.Thegrantedusersandrolesarespecifiedwithinthesecurityhandlerbeandefinition.ConnectingclientshavetosetthebasicauthHTTPheaderproperlyusingthecorrectuserandroleforaccessingtheCitrusservernow.
Youcancustomizethesecurityhandlerforyourveryspecificneeds(e.g.loadusersandroleswithJDBCfromadatabase).Justhavealookatthecodebaseandinspectthesettingsandpropertiesofferedbythesecurityhandlerinterface.
TipThismechanismisnotrestrictedtobasicauthenticationonly.Withothersettingsyoucanalsosetupdigestorform-basedauthenticationconstraintsveryeasy.
WS-Addressingsupport
ThewebservicestackoffersalotofdifferenttechnologiesandstandardswithinthecontextofSOAPWebServices.WespeakofWS-*specificationsinparticular.Oneofthesespecificationsdealswithaddressing.OnclientsideyoumayaddwsaheaderinformationtotherequestinordertogivetheserverinstructionshowtodealwithSOAPfaultsforinstance.
InCitrusWebServiceclientyoucanaddthoseheaderinformationusingthecommonconfigurationlikethis:
<citrus-ws:clientid="soapClient"request-url="http://localhost:8090/test"message-converter="wsAddressingMessageConverter"/>
<beanid="wsAddressingMessageConverter"class="com.consol.citrus.ws.message.converter.WsAddressingMessageConverter"<constructor-arg><beanid="wsAddressing200408"class="com.consol.citrus.ws.addressing.WsAddressingHeaders"<propertyname="version"value="VERSION200408"/><propertyname="action"value="http://citrus.sample/sayHello"/><propertyname="to"value="http://citrus.sample/server"/>
CitrusReferenceGuide
298Soap
<propertyname="from"><beanclass="org.springframework.ws.soap.addressing.core.EndpointReference"><constructor-argvalue="http://citrus.sample/client"/></bean></property><propertyname="replyTo"><beanclass="org.springframework.ws.soap.addressing.core.EndpointReference"><constructor-argvalue="http://citrus.sample/client"/></bean></property><propertyname="faultTo"><beanclass="org.springframework.ws.soap.addressing.core.EndpointReference"><constructor-argvalue="http://citrus.sample/fault/resolver"/></bean></property></bean></constructor-arg></bean>
TheWsAddressingheadervalueswillbeusedforallrequestmessagesthataresentwiththesoapclientcomponentsoapClient.YoucanoverwritetheWsAddressingheaderineachsendtestactioninyourtestthough.JustsetthespecialWsAddressingmessageheaderonyourrequest.Youcanusethefollowingmessageheadernamesinordertooverwritethedefaultaddressingheadersspecifiedinthemessageconverterconfiguration(alsoseetheclasscom.consol.citrus.ws.addressing.WsAddressingMessageHeaders).
citrus_soap_ws_addressing_messageIdaddressingmessageidasURIcitrus_soap_ws_addressing_fromaddressingfromendpointreferenceasURIcitrus_soap_ws_addressing_toaddressingtoURIcitrus_soap_ws_addressing_actionaddressingactionURIcitrus_soap_ws_addressing_replyToaddressingreplytoendpointreferenceasURIcitrus_soap_ws_addressing_faultToaddressingfaulttoendpointreferenceasURI
WhenusingthismessageheadersyouareabletoexplicitlyoverwritetheWsAddressingheaders.Testvariablesaresupportedofcoursewhenspecifyingthevalues.MostofthevaluesareparsedtoaURIvalueattheendsopleasemakesuretousecorrectURIStringrepresentations.
NoteTheWS-Addressingspecificationknowsseveralversions.Supportedversionare:
VERSION10(WS-Addressing1.0May2006)
CitrusReferenceGuide
299Soap
VERSION200408(August2004editionoftheWS-Addressingspecification)
TheaddressingheadersfindaplaceintheSOAPmessageheaderwithrespectivenamespacesandvalues.ApossibleSOAPrequestwithWSaddressingheaderslookslikefollows:
<SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Headerxmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"><wsa:ToSOAP-ENV:mustUnderstand="1">http://citrus.sample/server</wsa:To><wsa:From><wsa:Address>http://citrus.sample/client</wsa:Address></wsa:From><wsa:ReplyTo><wsa:Address>http://citrus.sample/client</wsa:Address></wsa:ReplyTo><wsa:FaultTo><wsa:Address>http://citrus.sample/fault/resolver</wsa:Address></wsa:FaultTo><wsa:Action>http://citrus.sample/sayHello</wsa:Action><wsa:MessageID>urn:uuid:4c4d8af2-b402-4bc0-a2e3-ad33b910e394</wsa:MessageID></SOAP-ENV:Header><SOAP-ENV:Body><cit:HelloRequestxmlns:cit="http://citrus/sample/sayHello"><cit:Text>HelloCitrus!</cit:Text></cit:HelloRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>
ImportantBydefaultwhennotsetexplicitlyonthemessageheaderstheWsAddressingmessageidpropertyisautomaticallygeneratedforeachrequest.YoucansetthemessageidgenerationstrategyintheSpringapplicationcontextmessageconverterconfiguration:
<beanid="wsAddressingMessageConverter"class="com.consol.citrus.ws.message.converter.WsAddressingMessageConverter"<propertyname="messageIdStrategy"><beanclass="org.springframework.ws.soap.addressing.messageid.UuidMessageIdStrategy"/></property></bean>
BydefaultthestrategywillcreateanewJavaUUIDforeachrequest.Thestrategyalsousesacommonresourcenameprefixurn:uuid:.Youcanoverwritethemessageidanytimeforeachrequestexplicitlybysettingthemessageheadercitrus_soap_ws_addressing_messageIdwitharespectivevalueonthemessageinyourtest.
CitrusReferenceGuide
300Soap
SOAPclientforkmode
SOAPoverHTTPusessynchronouscommunicationbynature.ThismeansthatsendingaSOAPmessageinCitrusoverHTTPwillautomaticallyblockfurthertestactionsuntilthesynchronousHTTPresponsehasbeenreceived.Intestcasesthissynchronousblockingmightcauseproblemsforseveralreasons.AsimplereasonwouldbethatyouneedtodofurthertestactionsinparalleltothesynchronousHTTPSOAPcommunication(e.g.simulateanotherbackendsysteminthetestcase).
YoucanseparatetheSOAPsendactionfromtherestofthetestcasebyusingthe"fork"mode.TheSOAPclientwillautomaticallyopenanewJavaThreadforthesynchronouscommunicationandthetestisabletocontinuewithexecutionalthoughthesynchronousHTTPSOAPresponsehasnotarrivedyet.
<ws:sendendpoint="soapClient"fork="true"><message><payload><SoapRequestxmlns="http://www.consol.de/schemas/sample.xsd"><Operation>Readtheattachment</Operation></SoapRequest></payload></message></ws:send>
Withthe"fork"modeenabledthetestcontinueswithexecutionwhilethesendingactionwaitsforthesynchronousresponseinaseparateJavaThread.Youcouldreachthesamebehaviourwithacomplex/containerconstruct,butforkingthesendactionismuchmorestraightforward.
ImportantItishighlyrecommendedtouseaproper"timeout"settingontheSOAPreceiveactionwhenusingforkmode.Theforkedsendoperationmighttakesometimeandthecorrespondingreceiveactionmightrunintofailureastheresponsewashasnotbeenreceivedyet.Theresultwouldbeabrokentestbecauseofthemissingresponsemessage.Aproper"timeout"settingforthereceiveactionsolvesthisproblemastheactionwaitsforthistimeperiodandoccasionallyrepeatedlyasksfortheSOAPresponsemessage.Thefollowinglistingsetsthereceivetimeoutto10seconds,sotheactionwaitsfortheforkedsendactiontodelivertheSOAPresponseintime.
<ws:receiveendpoint="soapClient"timeout="10000"><message><payload><SoapResponsexmlns="http://www.consol.de/schemas/sample.xsd">
CitrusReferenceGuide
301Soap
<Operation>Didsomething</Operation><Success>true</Success></SoapResponse></payload></message></ws:receive>
SOAPservletcontextcustomization
ForhighlycustomizedSOAPservercomponentsinCitrusyoucandefineafullservletcontextconfigurationfile.HereyouhavethefullpowertoaddSpringendpointmappingsandcustomendpointimplementations.Youcansetthecustomservletcontextasexternalfileresourceontheservercomponent:
<citrus-ws:clientid="soapClient"context-config-location="classpath:citrus-ws-servlet.xml"message-factory="soap11MessageFactory"/>
Nowletushaveacloserlookatthecontext-config-locationattribute.ThisconfigurationdefinestheSpringapplicationcontextfileforendpoints,requestmappingsandotherSpringWSspecificinformation.PleaseseetheofficialSpringWSdocumentationfordetailsonthisSpringbasedconfiguration.Youcanalsojustcopythefollowingexampleapplicationcontextwhichshouldworkforyouingeneral.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
<beanid="loggingInterceptor"class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"><description>Thisinterceptorlogsthemessagepayload.</description></bean>
<beanid="helloServicePayloadMapping"class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping"><propertyname="mappings"><props><propkey="http://www.consol.de/schemas/sayHelloHelloRequest">helloServiceEndpoint</prop>
CitrusReferenceGuide
302Soap
</props></property><propertyname="interceptors"><list><refbean="loggingInterceptor"/></list></property></bean>
<beanid="helloServiceEndpoint"class="com.consol.citrus.ws.server.WebServiceEndpoint"><propertyname="endpointAdapter"ref="staticResponseEndpointAdapter"/></bean>
<citrus:static-response-adapterid="staticResponseEndpointAdapter"><citrus:payload><![CDATA[<HelloResponsexmlns="http://www.consol.de/schemas/sayHello"><MessageId>123456789</MessageId><CorrelationId>CORR123456789</CorrelationId><User>WebServer</User><Text>HelloUser</Text></HelloResponse>]]></citrus:payload><citrus:header><citrus:elementname="http://www.consol.de/schemas/samples/sayHello.xsdns0:Operation"value="sayHelloResponse"/><citrus:elementname="http://www.consol.de/schemas/samples/sayHello.xsdns0:Request"value="HelloRequest"/><citrus:elementname="citrus_soap_action"value="sayHello"/></citrus:header></citrus:static-response-adapter></beans>
TheprogramlistingabovedescribesanormalSpringWSrequestmappingwithendpointconfigurations.Themappingisresponsibletoforwardincomingrequeststotheendpointwhichwillhandletherequestandprovideaproperresponsemessage.Firstofallweaddalogginginterceptortothecontextsoallincomingrequestsgetloggedtotheconsolefirst.Thenweuseapayloadmapping(PayloadRootQNameEndpointMapping)inordertomapallincoming'HelloRequest'SOAPmessagestothe'helloServiceEndpoint'.EndpointsareofessentialnatureinCitrusSOAPWebServicesimplementation.Theyareresponsibleforprocessingarequestinordertoprovideaproperresponsemessagethatissentbacktothecallingclient.Citrususestheendpointincombinationwithamessageendpointadapterimplementation.
CitrusReferenceGuide
303Soap
Theendpointworkstogetherwiththemessageendpointadapterthatisresponsibleforprovidingaresponsemessagefortheclient.ThevariousmessageendpointadapterimplementationsinCitruswerealreadydiscussedinendpoint-adapter.
Inthisexamplethe'helloServiceEndpoint'usesthe'static-response-adapter'whichisalwaysreturningastaticresponsemessage.Inmostcasesstaticresponseswillnotfitthetestscenarioandyouwillhavetorespondmoredynamically.
RegardlessofwhichmessageendpointadaptersetupyouareusinginyourtestcasetheendpointtransformstheresponseintoaproperSOAPmessage.Youcanaddasmanyrequestmappingsandendpointsasyouwanttotheservercontextconfiguration.SoyouareabletohandledifferentrequesttypeswithonesingleJettyserverinstance.
That'sitforconnectingwithSOAPWebServices!WesawhowtosendandreceiveSOAPmessageswithJettyandSpringWebServices.HavealookatthesamplescomingwithyourCitrusarchiveinordertolearnmoreabouttheSOAPmessagehandling.
CitrusReferenceGuide
304Soap
FTPsupportCitrusisabletostartalittleftpserveracceptingincomingclientrequests.AlsoCitrusisabletocallFTPcommandsasaclient.ThenextsectionsdealwithFTPconnectivity.
NoteTheFTPcomponentsinCitrusarekeptinaseparateMavenmodule.SoyoushouldaddthemoduleasMavendependencytoyourprojectaccordingly.
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-ftp</artifactId><version>2.7.1</version></dependency>
AsCitrusprovidesacustomizedFTPconfigurationschemafortheSpringapplicationcontextconfigurationfileswehavetoaddnametothetoplevelbeanselement.Simplyincludetheftp-confignamespaceintheconfigurationXMLfilesasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-ftp="http://www.citrusframework.org/schema/ftp/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/http/confighttp://www.citrusframework.org/schema/ftp/config/citrus-ftp-config.xsd">
[...]
</beans>
NowwearereadytousethecustomizedCitrusFTPconfigurationelementswiththecitrus-ftpnamespaceprefix.
FTPclient
CitrusReferenceGuide
305Ftp
WewanttouseCitrusfoconnecttodomeFTPserverasaclientsendingcommandssuchascreatingadirectoryorlistingallfiles.CitrusoffersaclientcomponentdoingexactlythisFTPclientconnection.
<citrus-ftp:clientid="ftpClient"host="localhost"port="22222"username="admin"password="admin"timeout="10000"/>
TheconfigurationabovedescribesaCitrusftpclientconnectedtoaftpserverwithftp://localhost:22222.Forauthenticationusernameandpasswordaredefinedaswellastheglobalconnectiontimeout.Theclientwillautomaticallysendusernameandpasswordforproperauthenticationtotheserverwhenopeninganewconnection.
Inatestcaseyouarenowabletousetheclienttopushcommandstotheserver.
<sendendpoint="ftpClient"fork="true"><message><data></data></message><header><elementname="citrus_ftp_command"value="PWD"/><elementname="citrus_ftp_arguments"value="test"/></header></send>
<receiveendpoint="ftpClient"><messagetype="plaintext"><data>PWD</data></message><header><elementname="citrus_ftp_command"value="PWD"/><elementname="citrus_ftp_arguments"value="test"/><elementname="citrus_ftp_reply_code"value="257"/><elementname="citrus_ftp_reply_string"value="@contains('iscurrentdirectory')@"/></header></receive>
Asyoucanseemostoftheftpcommunicationparametersarespecifiedasspecialheaderelementsinthemessage.CitrusautomaticallyconvertsthoseinformationtoproperFTPcommandsandresponsemessages.
FTPserver
CitrusReferenceGuide
306Ftp
NowthatweareabletoaccessFTPasaclientwemightalsowanttosimulatetheserverside.ThereforeCitrusoffersaservercomponentthatislisteningonaportforincomingFTPconnections.Theserverhasadefaulthomedirectoryonthelocalfilesystemspecified.Butyoucanalsodefinehomedirectoriesperuser.Fornowletushavealookattheserverconfigurationcomponent:
<citrus-ftp:serverid="ftpServer">port="22222"auto-start="true"user-manager-properties="classpath:ftp.server.properties"/>
Theftpserverconfigurationisquitesimple.Theserverstartsautomaticallyandbindstoaport.Theuserconfigurationisreadfromauser-manager-propertyfile.Letushavealookatthecontentofthisusermanagementfile:
#Passwordis"admin"ftpserver.user.admin.userpassword=21232F297A57A5A743894A0E4A801FC3ftpserver.user.admin.homedirectory=target/ftp/user/adminftpserver.user.admin.enableflag=trueftpserver.user.admin.writepermission=trueftpserver.user.admin.maxloginnumber=0ftpserver.user.admin.maxloginperip=0ftpserver.user.admin.idletime=0ftpserver.user.admin.uploadrate=0ftpserver.user.admin.downloadrate=0
ftpserver.user.anonymous.userpassword=ftpserver.user.anonymous.homedirectory=target/ftp/user/anonymousftpserver.user.anonymous.enableflag=trueftpserver.user.anonymous.writepermission=falseftpserver.user.anonymous.maxloginnumber=20ftpserver.user.anonymous.maxloginperip=2ftpserver.user.anonymous.idletime=300ftpserver.user.anonymous.uploadrate=4800ftpserver.user.anonymous.downloadrate=4800
Asyoucanseeyouareabletodefineasmanyuserfortheftpserverasyoulike.Usernameandpassworddefinetheauthenticationontheserver.Inadditiontothatyouhaveplentyofconfigurationpossibilitiesperuser.CitrususestheApacheftpserverimplementation.SoformoredetailsonconfigurationcapabilitiespleaseconsulttheofficialApacheftpserverdocumentation.
CitrusReferenceGuide
307Ftp
Nowwewouldliketousetheserverinatestcase.Veryeasyyoujusthavetodefineareceivemessageactionwithinyourtestcasethatusestheserveridasendpointreference:
<echo><message>ReceiveuserloginonFTPserver</message></echo>
<receiveendpoint="ftpServer"><messagetype="plaintext"><data>USER</data></message><header><elementname="citrus_ftp_command"value="USER"/><elementname="citrus_ftp_arguments"value="admin"/></header></receive>
<sendendpoint="ftpServer"><messagetype="plaintext"><data>OK</data></message></send>
<echo><message>ReceiveuserpasswordonFTPserver</message></echo>
<receiveendpoint="ftpServer"><messagetype="plaintext"><data>PASS</data></message><header><elementname="citrus_ftp_command"value="PASS"/><elementname="citrus_ftp_arguments"value="admin"/></header></receive>
<sendendpoint="ftpServer"><messagetype="plaintext""><data>OK</data></message></send>
Thelistingaboveshowstwoincomingcommandsrepresentingauserlogin.Weindicatewithresendactionsthatwewouldlinktheservertorespondwithpositivefeedbackandtoacceptthelogin.Aswehaveafullyqualifiedftpserverrunningtheclientcanalso
CitrusReferenceGuide
308Ftp
pushfilesreaddirectoriesandmore.Allincomingcommandscanbevalidatedinsideatestcase.
CitrusReferenceGuide
309Ftp
MessagechannelsupportMessagechannelsrepresenttheinmemorymessagingsolutioninCitrus.Producerandconsumercomponentsarelinkedviachannelsexchangingmessagesinmemory.AsthistransportmechanismcomesfromSpringIntegrationAPI(http://www.springsource.org/spring-integration)andCitrusitselfusesalotofSpringAPIs,especiallythosefromSpringIntegrationyouareabletoconnecttoallSpringmessagingadaptersviatheseinmemorychannels.
CitrusoffersachannelcomponentsthatcanbeusedbothbyCitrusandSpringIntegration.TheconclusionisthatCitrussupportsthesendingandreceivingofmessagesbothtoandfromSpringIntegrationmessagechannelcomponents.ThisopensupalotofgreatpossibilitiestointeractwiththeSpringIntegrationtransportadaptersforFTP,TCP/IPandsoon.Inadditiontothatthemessagechannelsupportprovidesusagoodwaytoexchangemessagesinmemory.
CitrusprovidessupportforsendingandreceivingJMSmessages.Wehavetoseparatebetweensynchronousandasynchronouscommunication.SointhischapterweexplainhowtosetupJMSmessageendpointsforsynchronousandasynchronousoutboundandinboundcommunication
NoteThemessagechannelconfigurationcomponentsusethedefault"citrus"configurationnamespaceandschemadefinition.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitrusconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus-jms="http://www.citrusframework.org/schema/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsd">
[...]
</beans>
AfterthatyouareabletousecustomizedCitrusXMLelementsinordertodefinetheSpringbeans.
CitrusReferenceGuide
310Messagechannel
Channelendpoint
Citrusoffersachannelendpointcomponentthatisabletocreateproducerandconsumercomponents.Producerandconsumersendandreceivemessagesbothtoandfromachannelendpoint.BydefaulttheendpointisasynchronouswhenconfiguredintheCitrusapplicationcontext.Withthiscomponentyouareabletoaccessmessagechannelsdirectly:
<citrus:channel-endpointid="helloEndpoint"channel="helloChannel"/>
<si:channelid="helloChannel"/>
TheCitruschannelendpointreferencesaSpringIntegrationchanneldirectly.InsideyourtestcaseyoucanreferencetheCitrusendpointasusualtosendandreceivemessages.Wewillseethislaterinsomeexamplecodelistings.
NoteTheSpringIntegrationconfigurationcomponentsuseaspecificnamespacethathastobeincludedintoyourSpringapplicationcontext.Youcanusethefollowingtemplatewhichholdsallnecessarynamespacesandschemalocations:
<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:si="http://www.springframework.org/schema/integration"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.springframework.org/schema/integrationhttp://www.springframework.org/schema/integration/spring-integration.xsd"></beans>
TheCitruschannelendpointalsosupportsacustomizedmessagechanneltemplatethatwillactuallysendthemessages.Thecustomizedtemplatemightgiveyouaccesstospecialconfigurationpossibilities.Howeveritisoptional,soifnomessagechanneltemplateisdefinedintheconfigurationCitruswillcreateadefaulttemplate.
<citrus:channel-endpointid="helloEndpoint"channel="helloChannel"message-channel-template="myMessageChannelTemplate"/>
CitrusReferenceGuide
311Messagechannel
Themessagesenderisnowreadytopublishmessagestothedefinedchannel.Thecommunicationissupposedtobeasynchronous,sotheproducerisnotabletoprocessareplymessage.Wewilldealwithsynchronouscommunicationandreplymessageslaterinthischapter.Themessageproducerjustpublishesmessagestothechannelandisdone.Interactingwiththeendpointsinatestcaseisquiteeasy.Justreferencetheidoftheendpointinyoursendandreceivetestactions
<sendendpoint="helloEndpoint"><message><payload><v1:HelloRequestxmlns:v1="http://citrusframework.org/schemas/HelloService.xsd"><v1:Text>HelloWorld!</v1:Text></v1:HelloRequest></payload></message></send>
<receiveendpoint="helloEndpoint"><message><payload><v1:HelloResponsexmlns:v1="http://citrusframework.org/schemas/HelloService.xsd"><v1:Text>HelloCitrus!</v1:Text></v1:HelloResponse></payload></message></receive>
AsyoucanseeCitrusisalsoabletoreceivemessagesfromthesameSpringIntegrationmessagechanneldestination.Wejustreferencesthesamechannel-endpointinthereceiveaction.
Asusualthereceiverconnectstothemessagedestinationandwaitsformessagestoarrive.Theusercansetareceivetimeoutwhichissetto5000millisecondsbydefault.Incasenomessagewasreceivedinthistimeframethereceiverraisestimeouterrorsandthetestfails.
Synchronouschannelendpoints
Thesynchronouschannelproducerpublishesmessagesandwaitssynchronouslyfortheresponsetoarriveonsomereplychanneldestination.Thereplychannelnameissetinthemessage'sheaderattributessothecounterpartinthiscommunicationcansendits
CitrusReferenceGuide
312Messagechannel
replytothatchannel.Thebasicconfigurationforasynchronouschannelendpointcomponentlookslikefollows:
<citrus:channel-sync-endpointid="helloSyncEndpoint"channel="helloChannel"reply-timeout="1000"polling-interval="1000"/>
Synchronousmessagechannelendpointsusuallydopollforsynchronousreplymessagesforprocessingthereplymessages.Thepollintervalisanoptionalsettinginordertomanagetheamountofreplymessagehandshakeattempts.Oncetheendpointwasabletoreceivethereplymessagesynchronouslythetestcasecanreceivethereply.Incaseallmessagehandshakeattemptsdofailbecausethereplymessageisnotavailableintimeweraisesometimeouterrorandthetestwillfail.
NoteBydefaultthechannelendpointusestemporaryreplychanneldestinations.Thetemporaryreplychannelsareonlyusedonceforasinglecommunicationhandshake.Afterthatthereplychannelisdeletedagain.Staticreplychannelsarenotsupportedasithasnotbeeninscopeyet.
Whensendingamessagetothisendpointinthefirstplacetheproducerwillwaitsynchronouslyfortheresponsemessagetoarriveonthereplydestination.Youcanreceivethereplymessageinyourtestcaseusingthesameendpointcomponent.Sowehavetwoactionsonthesameendpoint,firstsendthenreceive.
<sendendpoint="helloSyncEndpoint"><message><payload><v1:HelloRequestxmlns:v1="http://citrusframework.org/schemas/HelloService.xsd"><v1:Text>HelloWorld!</v1:Text></v1:HelloRequest></payload></message></send>
<receiveendpoint="helloSyncEndpoint"><message><payload><v1:HelloResponsexmlns:v1="http://citrusframework.org/schemas/HelloService.xsd"><v1:Text>HelloCitrus!</v1:Text></v1:HelloResponse></payload></message></receive>
CitrusReferenceGuide
313Messagechannel
Inthelastsectionwesawthatsynchronouscommunicationisbasedonreplymessagesontemporaryreplychannels.WesawthatCitrusisabletopublishmessagestochannelsandwaitforreplymessagestoarriveontemporaryreplychannels.Thissectiondealswiththesamesynchronouscommunicationoverreplymessages,butnowCitrushastosenddynamicreplymessagestotemporarychannels.
ThescenariowearetalkingaboutisthatCitrusreceivesamessageandweneedtoreplytoatemporaryreplychannelthatisstoredinthemessageheaderattributes.Wehandlethissynchronouscommunicationwiththesamesynchronouschannelendpointcomponent.Wheninitiatingthecommunicationbyreceivingamessagefromasynchronouschannelendpointyouareabletosendasynchronousresponseback.Againjustusethesameendpointreferenceinyourtestcase.Thehandlingoftemporaryreplydestinationsisdoneautomaticallybehindthescenes.Sowehaveagaintwoactionsinourtestcase,butthistimefirstreceivethensend.
<receiveendpoint="helloSyncEndpoint"><message><payload><v1:HelloRequestxmlns:v1="http://citrusframework.org/schemas/HelloService.xsd"><v1:Text>HelloWorld!</v1:Text></v1:HelloRequest></payload></message></receive>
<sendendpoint="helloSyncEndpoint"><message><payload><v1:HelloResponsexmlns:v1="http://citrusframework.org/schemas/HelloService.xsd"><v1:Text>HelloCitrus!</v1:Text></v1:HelloResponse></payload></message></send>
Thesynchronousmessagechannelendpointwillhandleallreplychanneldestinationsandprovidethosebehindthescenes.
Messageselectorsonchannels
CitrusReferenceGuide
314Messagechannel
UnfortunatelySpringIntegrationmessagechannelsdonotsupportmessageselectorsonheadervaluesasdescribedinmessage-selector.WithCitrusversion1.2wefoundawaytoalsoaddmessageselectorsupportonmessagechannels.Wehadtointroduceaspecialqueuemessagechannelimplementation.Sofirstofallweusethisnewmessagechannelimplementationinourconfiguration.
<citrus:channelid="orderChannel"capacity="5"/>
TheCitrusmessagechannelimplementationextendsthequeuechannelimplementationfromSpringIntegration.Sowecanaddacapacityattributeforthischannel.That'sit!Nowweusethemessagechannelthatsupportsmessageselection.Inourtestwedefinemessageselectorsonheadervaluesasdescribedinmessage-selectorandyouwillseethatitworks.
Inadditiontothatwehaveimplementedothermessagefilterpossibilitiesonmessagechannelsthatwediscussinthenextsections.
RootQNameMessageSelector
YoucanusetheXMLrootQNameofyourmessageasselectioncriteria.Let'sseehowthisworksinasmallexample:
WehavetwodifferentXMLmessagesonamessagechannelwaitingtobepickedupbyaconsumer.
<HelloMessagexmlns="http://citrusframework.org/schema">HelloCitrus</HelloMessage><GoodbyeMessagexmlns="http://citrusframework.org/schema">GoodbyeCitrus</GoodbyeMessage>
WewouldliketopickuptheGoodbyeMessageinourtestcase.TheHelloMessageshouldbeleftonthemessagechannelaswearenotinterestedinitrightnow.Wecandefinearootqnamemessageselectorinthereceiveactionlikethis:
<receiveendpoint="orderChannelEndpoint"><selector><elementname="root-qname"value="GoodbyeMessage"/></selector><message><payload><GoodbyeMessagexmlns="http://citrusframework.org/schema">GoodbyeCitrus</GoodbyeMessage</payload></message></receive>
CitrusReferenceGuide
315Messagechannel
TheCitrusreceiverpicksuptheGoodbyeMessagefromthechannelselectedviatherootqnameoftheXMLmessagepayload.OfcourseyoucanalsocombinemessageheaderselectorsandrootqnameselectorsasshowninthisexamplebelowwhereamessageheadersequenceIdisaddedtotheselectionlogic.
<selector><elementname="root-qname"value="GoodbyeMessage"/><elementname="sequenceId"value="1234"/></selector>
AswedealwithXMLqnamevalues,wecanalsousenamespacesinourselectorrootqnameselection.
<selector><elementname="root-qname"value="http://citrusframework.org/schemaGoodbyeMessage"/></selector>
XPathEvaluatingMessageSelector
ItisalsopossibletoevaluatesomeXPathexpressiononthemessagepayloadinordertoselectamessagefromamessagechannel.TheXPathexpressionoutcomemustmatchanexpectedvalueandonlythenthemessageisconsumedformthechannel.
ThesyntaxfortheXPathexpressionistobedefinedastheelementnamelikethis:
<selector><elementname="xpath://Order/status"value="pending"/></selector>
Themessageselectorlooksforordermessageswithstatus="pending"inthemessagepayload.Thismeansthatfollowingmessageswouldgetaccepted/declinedbythemessageselector.
<Order><status>pending</status></Order>=ACCEPTED<Order><status>finished</status></Order>=NOTACCEPTED
OfcourseyoucanalsouseXMLnamespacesinyourXPathexpressionswhenselectingmessagesfromchannels.
CitrusReferenceGuide
316Messagechannel
<selector><elementname="xpath://ns1:Order/ns1:status"value="pending"/></selector>
Namespaceprefixesmustmatchtheincomingmessage-otherwisetheXPathexpressionwillnotworkasexpected.Inourexamplethemessageshouldlooklikethis:
<ns1:Orderxmlns:ns1="http://citrus.org/schema"><ns1:status>pending</ns1:status></ns1:Order>
KnowingthecorrectXMLnamespaceprefixisnotalwayseasy.IfyouarenotsurewhichnamespaceprefixtochooseCitrusshipswithadynamicnamespacereplacementforXPathexpressions.TheXPathexpressionlookslikethisandismostflexible:
<selector><elementname="xpath://http://citrus.org/schema:Order/http://citrus.org/schema:status"value="pending"/></selector>
ThiswillmatchallincomingmessagesregardlesstheXMLnamespaceprefixthatisused.
CitrusReferenceGuide
317Messagechannel
FilesupportInchaptermessage-channelwediscussedthenativeSpringIntegrationchannelsupportwhichenablesCitrustointeractwithallSpringIntegrationmessagingadapterimplementations.ThisisafantasticwaytoextendCitrusforadditionaltransports.ThisinteractionnowcomeshandywhenwritingandreadingfilesfromthefilesysteminCitrus.
Writefiles
WewanttousetheSpringIntegrationfileadapterforbothreadingandwritingfileswithalocaldirectory.Citruscaneasilyconnecttothisfileadapterimplementationwithitsmessagechannelsupport.CitrusmessagesenderandreceiverspeaktomessagechannelsthatareconnectedtotheSpringIntegrationfileadapters.
<citrus:channel-endpointid="fileEndpoint"channel="fileChannel"/>
<file:outbound-channel-adapterid="fileOutboundAdapter"channel="fileChannel"directory="file:$some.directory.property"/>
<si:channelid="fileChannel"/>
TheconfigurationabovedescribesaCitrusmessagechannelendpointconnectedtoaSpringIntegrationoutboundfileadapterthatwritesmessagestoastoragedirectory.WiththiscombinationyouareabletowritefilestoadirectoryinyourCitrustestcase.ThetestcaseusesthechannelendpointinitssendactionandtheendpointinteractswiththeSpringIntegrationfileadaptersosendingoutthefile.
NoteTheSpringIntegrationfileadapterconfigurationcomponentsaddanewnamespacetoourSpringapplicationcontext.Seethistemplatewhichholdsallnecessarynamespacesandschemalocations:
<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:si="http://www.springframework.org/schema/integration"xmlns:file="http://www.springframework.org/schema/integration/file"xsi:schemaLocation="http://www.springframework.org/schema/beans
CitrusReferenceGuide
318File
http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.springframework.org/schema/integrationhttp://www.springframework.org/schema/integration/spring-integration.xsdhttp://www.springframework.org/schema/integration/filehttp://www.springframework.org/schema/integration/file/spring-integration-file.xsd"></beans>
Readfiles
Thenextprogramlistingshowsapossibleinboundfilecommunication.SotheSpringIntegrationfileinboundadapterwillreadfilesfromastoragedirectoryandpublishthefilecontentstoamessagechannel.Citruscanthenreceivethosefilesasmessagesinatestcaseviathechannelendpointandvalidatethefilecontentsforinstance.
<file:inbound-channel-adapterid="fileInboundAdapter"channel="fileChannel"directory="file:$some.directory.property"><si:pollerfixed-rate="100"/></file:inbound-channel-adapter>
<si:channelid="fileChannel"><si:queuecapacity="25"/><si:interceptors><beanclass="org.springframework.integration.transformer.MessageTransformingChannelInterceptor"<constructor-arg><beanclass="org.springframework.integration.file.transformer.FileToStringTransformer"</constructor-arg></bean></si:interceptors></si:channel>
<citrus:channel-endpointid="fileEndpoint"channel="fileChannel"/>
ImportantThefileinboundadapterconstructsJavafileobjectsasthemessagepayloadbydefault.CitruscanonlyworkonStringmessagepayloads.SoweneedafiletransformerthatconvertsthefileobjectstoStringpayloadsrepresentingthefile'scontent.
ThisfileadapterexampleshowshoweasyCitruscanworkhandinhandwithSpringIntegrationadapterimplementations.ThemessagechannelsupportisafantasticwaytoextendthetransportandprotocolsupportinCitrusbyconnectingwiththeverygood
CitrusReferenceGuide
319File
SpringIntegrationadapterimplementations.HaveacloserlookattheSpringIntegrationprojectformoredetailsandotheradapterimplementationsthatyoucanusewithCitrusintegrationtesting.
CitrusReferenceGuide
320File
ApacheCamelsupportApacheCamelprojectimplementstheenterpriseintegrationpatternsforbuildingmediationandroutingrulesinyourenterpriseapplication.WiththeCitrusCamelsupportyouareabletodirectlyinteractwiththeApacheCamelcomponentsandroutedefinitions.YoucancallCamelroutesandreceivesynchronousresponsemessages.YoucanalsosimulatetheCamelrouteendpointwithreceivingmessagesandprovidingsimulatedresponsemessages.
NoteThecamelcomponentsinCitrusarekeptinaseparateMavenmodule.SoyoushouldaddthemoduleasMavendependencytoyourprojectaccordingly.
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-camel</artifactId><version>2.7.1</version></dependency>
CitrusprovidesaspecialApacheCamelconfigurationschemathatisusedinourSpringconfigurationfiles.Youhavetoincludethecitrus-camelnamespaceinyourSpringconfigurationXMLfilesasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-camel="http://www.citrusframework.org/schema/camel/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/camel/confighttp://www.citrusframework.org/schema/camel/config/citrus-camel-config.xsd">
[...]
</beans>
NowyouarereadytousetheCitrusApacheCamelconfigurationelementsusingthecitrus-camelnamespaceprefix.
ThenextsectionsexplaintheCitruscapabilitieswhileworkingwithApacheCamel.
CitrusReferenceGuide
321Camel
Camelendpoint
CamelandCitrusbothusetheendpointpatterninordertodefinemessagedestinations.Userscaninteractwiththeseendpointswhencreatingthemediationandroutinglogic.TheCitrusendpointcomponentforCamelinteractionisdefinedasfollowsinyourCitrusSpringconfiguration.
<citrus-camel:endpointid="directCamelEndpoint"endpoint-uri="direct:news"/>
RightnexttothatCitrusendpointweneedtheApacheCamelroutethatislocatedinsideacamelcontextcomponent.
<camelContextid="camelContext"xmlns="http://camel.apache.org/schema/spring"><routeid="newsRoute"><fromuri="direct:news"/><touri="log:com.consol.citrus.camel?level=INFO"/><touri="seda:news-feed"/></route></camelContext>
AsyoucanseetheCitruscamelendpointisabletointeractwiththeCamelroute.IntheexampleabovetheCamelcontextisplacedasSpringbeanCamelcontext.ThiswouldbetheeasiestsetuptouseCamelwithCitrusasyoucanaddtheCamelcontextstraighttotheSpringbeanapplicationcontext.OfcourseyoucanalsoimportyourCamelcontextandroutesfromotherSpringbeancontextfilesoryoucanstarttheCamelcontextrouteswithJavacode.
IntheexampletheApacheCamelrouteislisteningontherouteendpointuridirect:news.IncomingmessageswillbeloggedtotheconsoleusingalogCamelcomponent.AfterthatthemessageisforwardedtoasedaCamelcomponentwhichisasimplequeueinmemory.SowehaveasmallCamelroutinglogicwithtwodifferentmessagetransports.
TheCitrusendpointcaninteractwiththissampleroutedefinition.TheendpointconfigurationholdstheendpointuriinformationthattellsCitrushowtoaccesstheApacheCamelroutedestination.ThisendpointuricanbeanyCamelendpointurithatisusedinaCamelroute.Herewejustusethedirectendpointuridirect:newssothesampleCamelroutegetscalleddirectly.Inyourtestcaseyoucanusethisendpoint
CitrusReferenceGuide
322Camel
componentreferencedbyitsidornameinordertosendandreceivemessagesontherouteaddressdirect:news.TheCamelroutelisteningonthisdirectaddresswillbeinvokedaccordingly.
TheApacheCamelroutessupportasynchronousandsynchronousmessagecommunicationpatterns.BydefaultCitrususesasynchronouscommunicationwithCamelroutes.ThismeansthattheCitrusproducersendstheexchangemessagetotherouteendpointuriandisfinishedimmediately.Thereisnosynchronousresponsetoawait.IncontrarytothatthesynchronousendpointwillsendandreceiveasynchronousmessageontheCameldestinationroute.Wewilldiscussthislateroninthischapter.FornowwehavealookonhowtousetheCitruscamelendpointinatestcaseinordertosendamessagetotheCamelroute:
<sendendpoint="directCamelEndpoint"><messagetype="plaintext"><payload>HellofromCitrus!</payload></message></send>
TheCitruscamelendpointcomponentcanalsobeusedinareceivemessageactioninyourtestcase.Inthissituationyouwouldreceiveamessagefromtherouteendpoint.ThisisespeciallydesignedforqueueingendpointroutessuchastheCamelsedacomponent.InourexampleCamelrouteabovethesedaCamelcomponentiscalledwiththeendpointuriseda:news-feed.ThismeansthattheCamelrouteissendingamessagetothesedacomponent.Citrusisabletoreceivethisroutemessagewithaendpointcomponentlikethis:
<citrus-camel:endpointid="sedaCamelEndpoint"endpoint-uri="seda:news-feed"/>
YoucanusetheCitruscamelendpointinyourtestcasereceiveactioninordertoconsumethemessageonthesedacomponent.
<receiveendpoint="sedaCamelEndpoint"><messagetype="plaintext"><payload>HellofromCitrus!</payload></message></receive>
CitrusReferenceGuide
323Camel
TipInsteadofdefiningastaticCitruscamelcomponentyoucouldalsousethedynamicendpointcomponentsinCitrus.Thiswouldenableyoutosendyourmessagedirectlyusingtheendpointuridirect:newsinyourtestcase.Readmoreaboutthisinendpoint-components.
CitrusisabletosendandreceivemessageswithCamelrouteendpointuri.ThisenablesyoutoinvokeaCamelroute.TheCamelcomponentsusedisdefinedbytheendpointuriasusual.WheninteractingwithCamelroutesyoumightneedtosendbacksomeresponsemessagesinordertosimulateboundaryapplications.Wewilldiscussthesynchronouscommunicationinthenextsection.
SynchronousCamelendpoint
ThesynchronousApacheCamelproducersendsamessagetosomerouteandwaitssynchronouslyfortheresponsetoarrive.InCamelthiscommunicationisrepresentedwiththeexchangepatternInOut.ThebasicconfigurationforasynchronousApacheCamelendpointcomponentlookslikefollows:
<citrus-camel:sync-endpointid="camelSyncEndpoint"endpoint-uri="direct:hello"timeout="1000"polling-interval="300"/>
Synchronousendpointspollforsynchronousreplymessagestoarrive.Thepollintervalisanoptionalsettinginordertomanagetheamountofreplymessagehandshakeattempts.Oncetheendpointwasabletoreceivethereplymessagesynchronouslythetestcasecanreceivethereply.Incasethereplymessageisnotavailableintimeweraisesometimeouterrorandthetestwillfail.
Inafirsttestscenariowewriteatestcasethesendsamessagetothesynchronousendpointandwaitsforthesynchronousreplymessagetoarrive.SowehavetwoactionsonthesameCitrusendpoint,firstsendthenreceive.
<sendendpoint="camelSyncEndpoint"><messagetype="plaintext"><payload>HellofromCitrus!</payload></message></send>
<receiveendpoint="camelSyncEndpoint"><messagetype="plaintext"><payload>ThisisthereplyfromApacheCamel!</payload>
CitrusReferenceGuide
324Camel
</message></receive>
Thenextvariationdealswiththesamesynchronouscommunication,butsendandreceiverolesareswitched.NowCitrusreceivesamessagefromaCamelrouteandhastoprovideareplymessage.WehandlethissynchronouscommunicationwiththesamesynchronousApacheCamelendpointcomponent.Onlydifferenceisthatweinitiallystartthecommunicationbyreceivingamessagefromtheendpoint.KnowingthisCitrusisabletosendasynchronousresponseback.Againjustusethesameendpointreferenceinyourtestcase.Sowehaveagaintwoactionsinourtestcase,butthistimefirstreceivethensend.
<receiveendpoint="camelSyncEndpoint"><messagetype="plaintext"><payload>HellofromApacheCamel!</payload></message></receive>
<sendendpoint="camelSyncEndpoint"><messagetype="plaintext"><payload>ThisisthereplyfromCitrus!</payload></message></send>
Thisisprettysimple.CitrustakescareonsettingtheApacheCamelexchangepatternInOutwhileusingsynchronouscommunications.TheCamelroutesdorespondandCitrusisabletoreceivethesynchronousmessagesaccordingly.WiththispatternyoucaninteractwithApacheCamelrouteswhereCitrussimulatessynchronousclientsandconsumers.
Camelexchangeheaders
ApacheCamelusesexchangeswhensendingandreceivingmessagestoandfromroutes.Theseexchangesholdspecificinformationonthecommunicationoutcome.Citrusautomaticallyconvertstheseexchangeinformationtospecialmessageheaderentries.Youcanvalidatethoseexchangeheaderstheneasilyinyourtestcase:
<receiveendpoint="sedaCamelEndpoint"><messagetype="plaintext"><payload>HellofromCamel!</payload></message><header>
CitrusReferenceGuide
325Camel
<elementname="citrus_camel_route_id"value="newsRoute"/><elementname="citrus_camel_exchange_id"value="ID-local-50532-1402653725341-0-3"/><elementname="citrus_camel_exchange_failed"value="false"/><elementname="citrus_camel_exchange_pattern"value="InOnly"/><elementname="CamelCorrelationId"value="ID-local-50532-1402653725341-0-1"/><elementname="CamelToEndpoint"value="seda://news-feed"/></header></receive>
BesidestheCamelspecificexchangeinformationtheCamelexchangedoesalsoholdsomecustomproperties.ThesepropertiessuchasCamelToEndpointorCamelCorrelationIdarealsoaddedautomaticallytotheCitrusmessageheadersocanexpecttheminareceivemessageaction.
Camelexceptionhandling
Letussupposefollowingroutedefinition:
<camelContextid="camelContext"xmlns="http://camel.apache.org/schema/spring"><routeid="newsRoute"><fromuri="direct:news"/><touri="log:com.consol.citrus.camel?level=INFO"/><touri="seda:news-feed"/><onException><exception>com.consol.citrus.exceptions.CitrusRuntimeException</exception><touri="seda:exceptions"/></onException></route></camelContext>
Theroutehasanexceptionhandlingblockdefinedthatiscalledassoonastheexchangeprocessingendsupinsomeerrororexception.WithCitrusyoucanalsosimulateaexchangeexceptionwhensendingbackasynchronousresponsetoacallingroute.
<sendendpoint="sedaCamelEndpoint"><messagetype="plaintext"><payload>Somethingwentwrong!</payload></message><header><elementname="citrus_camel_exchange_exception"value="com.consol.citrus.exceptions.CitrusRuntimeException"/><elementname="citrus_camel_exchange_exception_message"value="Somethingwentwrong!"/><elementname="citrus_camel_exchange_failed"value="true"/></header>
CitrusReferenceGuide
326Camel
</send>
Thismessageasresponsetotheseda:news-feedroutewouldcauseCameltoentertheexceptionhandlingintheroutedefinition.Theexceptionhandlingisactivatedandcallstheerrorhandlingrouteendpointseda:exceptions.OfcourseCitruswouldbeabletoreceivesuchanexceptionexchangevalidatingtheexceptionhandlingoutcome.
InsuchfailurescenariostheApacheCamelexchangeholdstheexceptioninformation(CamelExceptionCaught)suchascausingexceptionclassanderrormessage.TheseheadersarepresentinanerrorscenarioandcanbevalidatedinCitruswhenreceivingerrormessagesasfollows:
<receiveendpoint="errorCamelEndpoint"><messagetype="plaintext"><payload>Somethingwentwrong!</payload></message><header><elementname="citrus_camel_route_id"value="newsRoute"/><elementname="citrus_camel_exchange_failed"value="true"/><elementname="CamelExceptionCaught"value="com.consol.citrus.exceptions.CitrusRuntimeException:Somethingwentwrong!"/></header></receive>
ThiscompletesthebasicexceptionhandlinginCitruswhenusingtheApacheCamelendpoints.
Camelcontexthandling
IntheprevioussampleswehaveusedtheApacheCamelcontextasSpringbeancontextthatisautomaticallyloadedwhenCitrusstartsup.NowwhenusingasingleCamelcontextinstanceCitrusisabletoautomaticallypickthisCamelcontextforrouteinteraction.IfyouusemorethatoneCamelcontextyouhavetotelltheCitrusendpointcomponentwhichcontexttouse.Theendpointoffersanoptionalattributecalledcamel-context.
<citrus-camel:endpointid="directCamelEndpoint"camel-context="newsContext"endpoint-uri="direct:news"/>
<camelContextid="newsContext"xmlns="http://camel.apache.org/schema/spring">
CitrusReferenceGuide
327Camel
<routeid="newsRoute"><fromuri="direct:news"/><touri="log:com.consol.citrus.camel?level=INFO"/><touri="seda:news-feed"/></route></camelContext>
<camelContextid="helloContext"xmlns="http://camel.apache.org/schema/spring"><routeid="helloRoute"><fromuri="direct:hello"/><touri="log:com.consol.citrus.camel?level=INFO"/><touri="seda:hello"/></route></camelContext>
IntheexampleabpovewehavetwoCamelcontextinstancesloaded.Theendpointhastopickthecontexttousewiththeattributecamel-contextwhichresidestotheSpringbeanidoftheCamelcontext.
Camelrouteactions
SinceCitrus2.4weintroducedsomeCamelspecifictestactionsthatenableeasyinteractionwithCamelroutesandtheCamelcontext.ThetestactionsdofollowaspecificXMLnamespacesowehavetoaddthisnamespacetothetestcasewhenusingtheactions.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:camel="http://www.citrusframework.org/schema/camel/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/camel/testcasehttp://www.citrusframework.org/schema/camel/testcase/citrus-camel-testcase.xsd">
[...]
</beans>
Weaddedaspecialcamelnamespacewithprefixcamel:sonowwecanstarttoaddCameltestactionstothetestcase:
XMLDSL
<testcasename="CamelRouteIT">
CitrusReferenceGuide
328Camel
<actions><camel:create-routes><routeContextxmlns="http://camel.apache.org/schema/spring"><routeid="route_1"><fromuri="direct:test1"/><touri="mock:test1"/></route>
<routeid="route_2"><fromuri="direct:test2"/><touri="mock:test2"/></route></routeContext></camel:create-routes>
<camel:create-routescamel-context="camelContext"><routeContextxmlns="http://camel.apache.org/schema/spring"><route><fromuri="direct:test3"/><touri="mock:test3"/></route></routeContext></camel:create-routes></actions></testcase>
Intheexampleabovewehaveusedthecamel:create-routetestactionthatwillcreatenewCamelroutesatruntimeintheCamelcontext.ThetargetCamelcontextisspecifiedwiththeoptionalcamel-contextattribute.BydefaultCitruswillsearchforaCamelcontextavailableintheSpringbeanapplicationcontext.Removingroutesatruntimeisalsosupported.
XMLDSL
<testcasename="CamelRouteIT"><actions><camel:remove-routescamel-context="camelContext"><routeid="route_1"/><routeid="route_2"/><routeid="route_3"/></camel:remove-routes></actions></testcase>
NextoperationwewilldiscussisthestartandstopofexistingCamelroutes:
XMLDSL
CitrusReferenceGuide
329Camel
<testcasename="CamelRouteIT"><actions><camel:start-routescamel-context="camelContext"><routeid="route_1"/></camel:start-routes>
<camel:stop-routescamel-context="camelContext"><routeid="route_2"/><routeid="route_3"/></camel:stop-routes></actions></testcase>
StartingandstoppingCamelroutesatruntimeisimportantwhentemporarilyCitrusneedtoreceiveamessageonaCamelendpointURI.Wecanstoparoute,useaCitruscamelendpointinsteadforvalidationandstarttherouteafterthetestisdone.ThiswaywencanalsosimulateerrorsandfailurescenariosinaCamelrouteinteraction.
OfcourseallCamelrouteactionsarealsoavailableinJavaDSL.
JavaDSL
@AutowiredprivateCamelContextcamelContext;
@CitrusTestpublicvoidcamelRouteTest()camel().context(camelContext).create(newRouteBuilder(camelContext)@Overridepublicvoidconfigure()throwsExceptionfrom("direct:news").routeId("route_1").autoStartup(false).setHeader("headline",simple("ThisisBIGnews!")).to("mock:news");
from("direct:rumors").routeId("route_2").autoStartup(false).setHeader("headline",simple("Thisisjustarumor!")).to("mock:rumors"););
camel().context(camelContext).start("route_1","route_2");
camel().context(camelContext).stop("route_2");
camel().context(camelContext).remove("route_2");
CitrusReferenceGuide
330Camel
AsyoucanseewehaveaccesstotheCamelroutebuilderthatadds1-nnewCamelroutestothecontext.Afterthatwecanstart,stopandremovetherouteswithinthetestcase.
Camelcontrolbusactions
TheCamelcontrolbuscomponentisagoodwaytoaccessroutestatisticsandroutestatusinformationwithinaCamelcontext.Citrusprovidescontrolbustestactionstoeasilyaccessthecontrolbusoperationsatruntime.
XMLDSL
<testcasename="CamelControlBusIT"><actions><camel:control-bus><camel:routeid="route_1"action="start"/></camel:control-bus>
<camel:control-buscamel-context="camelContext"><camel:routeid="route_2"action="status"/><camel:result>Stopped</camel:result></camel:control-bus>
<camel:control-bus><camel:languagetype="simple">$camelContext.stop()</camel:language></camel:control-bus>
<camel:control-buscamel-context="camelContext"><camel:languagetype="simple">$camelContext.getRouteStatus('route_3')</camel:language<camel:result>Started</camel:result></camel:control-bus></actions></testcase>
Theexampletestcaseshowsthecontrolbusaccess.Camelprovidestwodifferentwaystospecifyoperationsandparameters.Thefirstoptionistheuseofanactionattribute.TheCamelrouteidhastobespecifiedasmandatoryattribute.Asaresultthecontrolbusactionwillbeexecutedonthetargetrouteduringtestruntime.ThiswaywecanalsostartandstopCamelroutesinaCamelcontext.
CitrusReferenceGuide
331Camel
Incaseancontrolbusoperationhasaresultsuchasthestatusactionwecanspecifyacontrolresultthatiscompared.Citruswillraisevalidationexceptionswhentheresultsdiffer.Thesecondoptionforexecutingacontrolbusactionisthelanguageexpression.WecanuseCamellanguageexpressionsontheCamelcontextforaccessingacontrolbusoperation.Alsoherewecandefineanoptionaloutcomeasexpectedresult.
TheJavaDSLalsosupportsthesecontrolbusoperationsasthenextexampleshows:
JavaDSL
@AutowiredprivateCamelContextcamelContext;
@CitrusTestpublicvoidcamelRouteTest()camel().controlBus().route("my_route","start");
camel().controlBus().language(SimpleBuilder.simple("$camelContext.getRouteStatus('my_route')")).result(ServiceStatus.Started);
TheJavaDSLworkswithCamellanguageexpressionbuildersaswellasServiceStatusenumvaluesasexpectedresult.
CitrusReferenceGuide
332Camel
Vert.xeventbussupportVert.xisanapplicationplatformfortheJVMthatprovidesanetworkeventbusforlightweightscalablemessagingsolutions.TheCitrusVert.xcomponentsdoparticipateonthateventbusmessagingasproducerorconsumer.WiththesecomponentsyoucanaccessVert.xinstancesavailableinyournetworkinordertotestthoseVert.xapplicationsinsomeintegrationtestscenario.
NoteTheVert.xcomponentsinCitrusarekeptinaseparateMavenmodule.SoyoushouldaddthemoduleasMavendependencytoyourprojectaccordingly.
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-vertx</artifactId><version>2.7.1</version></dependency>
CitrusprovidesaspecialVert.xconfigurationschemathatisusedinourSpringconfigurationfiles.Youhavetoincludethecitrus-vertxnamespaceinyourSpringconfigurationXMLfilesasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-vertx="http://www.citrusframework.org/schema/vertx/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/vertx/confighttp://www.citrusframework.org/schema/vertx/config/citrus-vertx-config.xsd">
[...]
</beans>
NowyouarereadytousetheCitrusVert.xconfigurationelementsusingthecitrus-vertxnamespaceprefix.
ThenextsectionsdiscusssendingandreceivingoperationsontheVert.xeventbuswithCitrus.
CitrusReferenceGuide
333Vertx
Vert.xendpoint
AsusualCitrususesanendpointcomponentinordertospecifysomemessagedestinationtosendandreceivemessagestoandfrom.TheVert.xendpointcomponentisdefinedasfollowsinyourCitrusSpringconfiguration.
<citrus-vertx:endpointid="simpleVertxEndpoint"host="localhost"port="5001"pubSubDomain="false"address="news-feed"/>
<beanid="vertxInstanceFactory"class="com.consol.citrus.vertx.factory.CachingVertxInstanceFactory"
TheendpointholdssomegeneralinformationhowtoaccesstheVert.xeventbus.HostandportvaluesdefinetheVert.xHazelcastclusterhostnameandport.CitrusstartsanewVert.xinstanceusingthiscluster.SoallotherVert.xinstancesconnectedtothisclusterhostwillreceivetheeventbusmessagesfromCitrusduringthetest.Inyourtestcaseyoucanusethisendpointcomponentreferencedbyitsidornameinordertosendandreceivemessagesontheeventbusaddressnews-feed.InVert.xtheeventbusaddressdefinesthedestinationforeventconsumerstolistenon.Asalreadymentionedclusterhostnameandportareoptional,soCitruswilluselocalhostandanewrandomportontheclusterhostifnothingisspecified.
TheVert.xeventbussupportspublish-subscribeandpoint-to-pointmessagecommunicationpatterns.BydefaultthepubSubDomaininCitrusisfalsesotheeventbussenderwillinitiateapoint-to-pointcommunicationontheeventbusaddress.Thismeansthatonlyonesingleconsumerontheeventbusaddresswillreceivethemessage.Iftherearemoreconsumersontheaddressthefirsttocomewinsandreceivesthemessage.Incontrarytothatthepublish-subscribescenariowoulddeliverthemessagetoallavailableconsumersontheeventbusaddresssimultaneously.YoucanenablethepubSubDomainontheVert.xendpointcomponentforthiscommunicationpattern.
TheVert.xendpointneedsainstancefactoryimplementationinordertocreatetheembeddedVert.xinstance.BydefaultthebeannamevertxInstanceFactoryisrecognizedbyallVert.xendpointcomponents.WewilltalkaboutVert.xinstancefactoriesinmoredetaillateroninthischapter.
CitrusReferenceGuide
334Vertx
AsmessagecontentyoucansendandreceiveJSONobjectsorsimplecharactersequencestotheeventbus.LetushavealookatasimplesamplesendingactionthatusesthenewVert.xendpointcomponent:
<sendendpoint="simpleVertxEndpoint"><messagetype="plaintext"><payload>HellofromCitrus!</payload></message></send>
AstheVert.xCitrusendpointisbidirectionalyoucanalsoreceivemessagesfromtheeventbus.
<receiveendpoint="simpleVertxEndpoint"><messagetype="plaintext"><payload>HellofromVert.x!</payload></message><header><elementname="citrus_vertx_address"value="news-feed"/></header></receive>
Citrusautomaticallyaddssomespecialmessageheaderstothemessage,soyoucanvalidatetheVert.xeventbusaddress.ThiscompletesthesimplesendandreceiveoperationsonaVert.xeventbus.NowletsmoveontosynchronousendpointswhereCitruswaitsforareplyontheeventbus.
SynchronousVert.xendpoint
ThesynchronousVert.xeventbusproducersendsamessageandwaitssynchronouslyfortheresponsetoarriveonsomereplyaddressdestination.Thereplyaddressnameisgeneratedautomaticallyandsetintherequestmessageheaderattributessothereceivingcounterpartinthiscommunicationcansenditsreplytothateventbusaddress.ThebasicconfigurationforasynchronousVert.xendpointcomponentlookslikefollows:
<citrus-vertx:sync-endpointid="vertxSyncEndpoint"address="hello"timeout="1000"polling-interval="300"/>
CitrusReferenceGuide
335Vertx
Synchronousendpointspollforsynchronousreplymessagestoarriveontheeventbusreplyaddress.Thepollintervalisanoptionalsettinginordertomanagetheamountofreplymessagehandshakeattempts.Oncetheendpointwasabletoreceivethereplymessagesynchronouslythetestcasecanreceivethereply.Incaseallmessagehandshakeattemptsdofailbecausethereplymessageisnotavailableintimeweraisesometimeouterrorandthetestwillfail.
NoteTheVert.xendpointusestemporaryreplyaddressdestinations.Thetemporaryreplyaddressingeneratedandisonlyusedonceforasinglecommunicationhandshake.Afterthatthereplyaddressisdismissedagain.
WhensendingamessagetothesynchronousVert.xendpointtheproducerwillwaitsynchronouslyfortheresponsemessagetoarriveonthereplyaddress.Youcanreceivethereplymessageinyourtestcaseusingthesameendpointcomponent.Sowehavetwoactionsonthesameendpoint,firstsendthenreceive.
<sendendpoint="vertxSyncEndpoint"><messagetype="plaintext"><payload>HellofromCitrus!</payload></message></send>
<receiveendpoint="vertxSyncEndpoint"><messagetype="plaintext"><payload>ThisisthereplyfromVert.x!</payload></message></receive>
Inthelastsectionwesawthatsynchronouscommunicationisbasedonreplymessagesontemporaryreplyeventbusaddress.WesawthatCitrusisabletosendmessagestoeventbusaddressandwaitforreplymessagestoarrive.Thisnextsectiondealswiththesamesynchronouscommunication,butsendandreceiverolesareswitched.NowCitrusreceivesamessageandhastosendareplymessagetoatemporaryreplyaddress.
WehandlethissynchronouscommunicationwiththesamesynchronousVert.xendpointcomponent.Onlydifferenceisthatweinitiallystartthecommunicationbyreceivingamessagefromtheendpoint.KnowingthisCitrusisabletosendasynchronousresponseback.Againjustusethesameendpointreferenceinyourtestcase.Thehandlingofthetemporaryreplyaddressisdoneautomaticallybehindthescenes.Sowehaveagaintwoactionsinourtestcase,butthistimefirstreceivethensend.
<receiveendpoint="vertxSyncEndpoint">
CitrusReferenceGuide
336Vertx
<messagetype="plaintext"><payload>HellofromVert.x!</payload></message></receive>
<sendendpoint="vertxSyncEndpoint"><messagetype="plaintext"><payload>ThisisthereplyfromCitrus!</payload></message></send>
ThesynchronousmessageendpointforVert.xeventbuscommunicationwillhandleallreplyaddressdestinationsandprovidethosebehindthescenes.
Vert.xinstancefactory
CitrusstartsanembeddedVert.xinstanceatruntimeinordertoparticipateintheVert.xcluster.WithinthisclustermultipleVert.xinstancesareconnectedviatheeventbus.ForstartingtheVert.xeventbusCitrususesaclusterhostnameandportdefinition.Youcancustomizethisclusterhostinordertoconnecttoaveryspecialclusterinyournetwork.
NowCitrusneedstomanagetheVert.xinstancescreatedduringthetestrun.BydefaultCitruswilllookforainstancefactorybeannamedvertxInstanceFactory.Youcanchoosethefactoryimplementationtouseinyourproject.BydefaultyoucanusethecachingfactoryimplementationthatcachestheVert.xinstancessowedonotconnectmorethanoneVert.xinstancetothesameclusterhost.Citrusoffersfollowinginstancefactoryimplementations:
com.consol.citrus.vertx.factory.CachingVertxInstanceFactory-defaultimplementationthatreusestheVert.xinstancebasedongivenclusterhostandport.Withthisimplementationweensureto
connectasingleCitrusVert.xinstancetoaclusterhost.
com.consol.citrus.vertx.factory.SingleVertxInstanceFactory-createsasingleVert.xinstanceandreusesthisinstanceforallendpoints.YoucanalsosetyourverycustomVert.xinstanceviaconfiguration
forcustomVert.xinstantiation.
CitrusReferenceGuide
337Vertx
TheinstancefactoryimplementationsdoimplementtheVertxInstanceFactoryinterface.Soyoucanalsoprovideyourveryspecialimplementation.BydefaultCitruslooksforabeannamedvertxInstanceFactorybutyoucanalsodefineyourveryspecialfactoryimplementationonmanendpointcomponent.TheVert.xinstancefactoryissetontheVert.xendpointasfollows:
<citrus-vertx:endpointid="vertxHelloEndpoint"address="hello"vertx-factory="singleVertxInstanceFactory"/>
<beanid="singleVertxInstanceFactory"class="com.consol.citrus.vertx.factory.SingleVertxInstanceFactory"/>
CitrusReferenceGuide
338Vertx
MailsupportSendingandreceivingmailsisthenextinterestwearegoingtotalkabout.WhendealingwithmailcommunicationyoumostcertainlyneedtointeractwithsomesortofIMAPorPOPmailserver.ButinCitruswedonotwanttomanagemailsinapersonalinbox.Wejustneedtobeabletoexchangemailmessagesthepersistinginauserinboxisnotpartofourbusiness.
ThisiswhyCitrusprovidesjustaSMTPmailserverwhichacceptsmailmessagesfromclients.OncetheSMTPserverhasacceptedanincomingmailitforwardsthosedatatotherunningtestcase.Inthetestcaseyoucanreceivetheincomingmailmessageandperformmessagevalidationasusual.ThemailsendingpartiseasyasCitrusoffersamailclientthatconnectstosomeSMTPserverforsendingmailstotheoutsideworld.
NoteThemailcomponentsinCitrusarekeptinaseparateMavenmodule.SoyoushouldcheckthatthemoduleisavailableasMavendependencyinyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-mail</artifactId><version>2.7.1</version></dependency>
AsusualCitrusprovidesacustomizedmailconfigurationschemathatisusedinSpringconfigurationfiles.Simplyincludethecitrus-mailnamespaceintheconfigurationXMLfilesasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-mail="http://www.citrusframework.org/schema/mail/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/mail/confighttp://www.citrusframework.org/schema/mail/config/citrus-mail-config.xsd">
[...]
</beans>
CitrusReferenceGuide
339Mail
NowyouarereadytousethecustomizedHttpconfigurationelementswiththecitrus-mailnamespaceprefix.
ReadthenextsectioninordertofindoutmoreaboutthemailmessagesupportinCitrus.
Mailclient
Themailsendingpartisquiteeasyandstraightforward.WejustneedtosendamailmessagetosomeSMTPserver.SoCitrusprovidesamailclientthatsendsoutmailmessages.
<citrus-mail:clientid="simpleMailClient"host="localhost"port="25025"/>
ThisishowaCitrusmailclientcomponentisdefinedintheSpringapplicationcontext.Youcanusethisclientreferencedbyitsidornameinyourtestcaseinamessagesendingaction.TheclientdefinesahostandportattributewhichshouldconnecttheclienttosomeSMTPserverinstance.
Weallknowmailmessagecontents.Themailmessagehassomegeneralpropertiessetbytheuser:
from:Themessagesendermailaddressto:Themessagerecipientmailaddress.Youcanaddmultiplerecipientsbyusingacommaseparatedlist.cc:Copyrecipientmailaddress.Youcanaddmultiplerecipientsbyusingacommaseparatedlist.bcc:Blindcopyrecipientmailaddress.Youcanaddmultiplerecipientsbyusingacommaseparatedlist.subject:Somesubjectusedasmailheadline.
Asatesteryouareabletosetthesepropertiesinyourtestcase.CitrusdefinesaXMLmailmessagerepresentationthatyoucanuseinsideyoursendaction.Letushavealookatthis:
<sendendpoint="simpleMailClient"><message><payload><mail-messagexmlns="http://www.citrusframework.org/schema/mail/message"><from>[email protected]</from>
CitrusReferenceGuide
340Mail
<to>[email protected]</to><cc></cc><bcc></bcc><subject>Thisisatestmailmessage</subject><body><contentType>text/plain;charset=utf-8</contentType><content>HelloCitrusmailserver!</content></body></mail-message></payload></message></send>
ThebasicXMLmailmessagerepresentationdefinesalistofbasicmailpropertiessuchasfrom,toorsubject.InadditiontothatwedefineatextbodywhichiseitherplaintextorHTML.Youcanspecifythecontenttypeofthemailbodyveryeasy(e.g.text/plainortext/html).BydefaultCitrususestext/plaincontenttype.
Nowwhendealingwithmailmessagesyouoftencometousemultipartstructuresforattachments.InCitrusyoucandefineattachmentcontentasbase64charactersequence.TheCitrusmailclientwillautomaticallycreateapropermultipartmailmimemessageusingthecontenttypesandbodypartsspecified.
<sendendpoint="simpleMailClient"><message><payload><mail-messagexmlns="http://www.citrusframework.org/schema/mail/message"><from>[email protected]</from><to>[email protected]</to><cc></cc><bcc></bcc><subject>Thisisatestmailmessage</subject><body><contentType>text/plain;charset=utf-8</contentType><content>HelloCitrusmailserver!</content><attachments><attachment><contentType>text/plain;charset=utf-8</contentType><content>Thisisattachmentdata</content><fileName>attachment.txt</fileName></attachment></attachments></body></mail-message></payload></message></send>
CitrusReferenceGuide
341Mail
Thatcompletesthebasicmailclientcapabilities.Butwaitwehavenottalkedabouterrorscenarioswheremailcommunicationresultsinerror.Whenrunningintomailerrorscenarioswehavetohandletheerrorrespectivelywithexceptionhandling.WhenthemailserverrespondedwitherrorsCitruswillraisemailexceptionsautomaticallyandyourtestcasefailsaccordingly.
Asatesteryoucancatchandassertthesemailexceptionsverifyingyourerrorscenario.
<assertexception="org.springframework.mail.MailSendException"><when><sendendpoint="simpleMailClient"><message><payload><mail-messagexmlns="http://www.citrusframework.org/schema/mail/message">[...]</mail-message></payload></message></send></when><assert/>
WeasserttheMailSendExceptionfromSpringtobethrownwhilesendingthemailmessagetotheSMTPserver.Withexceptionmessagevalidationyouareabletoexpectveryspecificmailsenderrorsontheclientside.Thisishowyoucanhandlesomesortoferrorsituationreturnedbythemailserver.SpeakingofmailserversweneedtoalsotalkaboutprovidingamailserverendpointinCitrusforclients.Thisispartofournextsection.
Mailserver
Consumingmailmessagesisamorecomplicatedtaskasweneedtohavesomesortofserverthatclientscanconnectto.InyourmailclientsoftwareyoutypicallypointtosomeIMAPorPOPinboxandreceivemailsfromthatendpoint.InCitruswedonotwanttomanageawholepersonalmailinboxsuchasIMAPorPOPwouldprovide.WejustneedaSMTPserverendpointforclientstosendmailsto.TheSMTPserveracceptsmailmessagesandforwardsthosetoarunningtestcaseforfurthervalidation.
NoteWehavenouserinboxwhereincomingmailsarestored.Themailserverjustforwardsincomingmailstotherunningtestforvalidation.Afterthetesttheincomingmailmessageisgone.
CitrusReferenceGuide
342Mail
AndthisisexactlywhattheCitrusmailserveriscapableof.TheserverisaverylightweightSMTPserver.AllincomingmailclientconnectionsareacceptedbydefaultandthemaildataisconvertedintoaCitrusXMLmailinterfacerepresentation.TheXMLmailmessageisthenpassedtotherunningtestforvalidation.
LetushavealookattheCitrusmailservercomponentandhowyoucanaddittotheSpringapplicationcontext.
<citrus-mail:serverid="simpleMailServer"port="25025"auto-start="true"/>
Themailservercomponentreceivesseveralpropertiessuchasportorauto-start.CitrusstartsainmemorySMTPserverthatclientscanconnectto.
InyourtestcaseyoucanthenreceivetheincomingmailmessagesontheserverinordertoperformthewellknownXMLvalidationmechanismswithinCitrus.Themessageheaderandthepayloadcontainallmailinformationsoyoucanverifythecontentwithexpectedtemplatesasusual:
<receiveendpoint="simpleMailServer"><message><payload><mail-messagexmlns="http://www.citrusframework.org/schema/mail/message"><from>[email protected]</from><to>[email protected]</to><cc></cc><bcc></bcc><subject>Thisisatestmailmessage</subject><body><contentType>text/plain;charset=utf-8</contentType><content>HelloCitrusmailserver!</content></body></mail-message></payload><header><elementname="citrus_mail_from"value="[email protected]"/><elementname="citrus_mail_to"value="[email protected]"/><elementname="citrus_mail_subject"value="Thisisatestmailmessage"/><elementname="citrus_mail_content_type"value="text/plain;charset=utf-8"/></header></message></receive>
CitrusReferenceGuide
343Mail
Thegeneralmailpropertiessuchasfrom,to,subjectareavailableaselementsinthemailpayloadandinthemessageheaderinformation.ThemessageheadernamesdostartwithacommonCitrusmailprefixcitrus_mail.Followingfromthatyoucanverifythesespecialmailmessageheadersinyourtestasshownabove.Citrusoffersfollowingmailheaders:
citrus_mail_fromcitrus_mail_tocitrus_mail_cccitrus_mail_bcccitrus_mail_subjectcitrus_mail_replyTocitrus_mail_date
InadditiontothatCitrusconvertstheincomingmaildatatoaspecialXMLmailrepresentationwhichispassedasmessagepayloadtothetest.Themailbodypartsarerepresentedasbodyandoptionalattachmentelements.AsthisisplainXMLyoucanverifythemailmessagecontentasusualusingCitrusvariables,functionsandvalidationmatchers.
RegardlessofhowthemailmessagehaspassedthevalidationtheCitrusSMTPmailserverwillautomaticallyrespondwithsuccesscodes(SMTP250OK)tothecallingclient.ThisisthebasicCitrusmailserverbehaviorwhereallclientconnectionsareacceptedanallmailmessagesarerespondedwithSMTP250OKresponsecodes.
Nowinmoreadvancedusagescenariosthetestermaywanttocontrolthemailcommunicationoutcome.UsercanforcesomeerrorscenarioswheremailclientsarenotacceptedormailcommunicationshouldfailwithsomeSMTPerrorstateforinstance.
Byusingamoreadvancedmailserversetupthetestergetsmorepowertosendingbackmailserverresponsecodestothemailclient.Justusetheadvancedmailadapterimplementationinyourmailservercomponentconfiguration:
<citrus-mail:serverid="advancedMailServer"auto-accept="false"split-multipart="true"port="25025"auto-start="true"/>
CitrusReferenceGuide
344Mail
Wehavedisabledtheauto-acceptmodeonthemailserver.Thismeansthatwehavetodosomeadditionalstepsinyourtestcasetoaccepttheincomingmailmessagefirst.Sowecandecideinourtestcasewhethertoacceptordeclinetheincomingmailmessageforamorepowerfultest.Youaccept/declineamailmessagewithaspecialXMLacceptrequest/responseexchangeinyourtestcase:
<receiveendpoint="advancedMailServer"><message><payload><accept-requestxmlns="http://www.citrusframework.org/schema/mail/message"><from>[email protected]</from><to>[email protected]</to></accept-request></payload></message></receive>
Sobeforereceivingtheactualmailmessagewereceivethissimpleaccept-requestinourtest.Theacceptrequestgivesusthemessagefromandtoresourcesofthemailmessage.Nowthetestdecidestoalsodeclineamailclientconnection.Youcansimulatethattheserverdoesnotacceptthemailclientconnectionbysendingbackanegativeacceptresponse.
<sendendpoint="advancedMailServer"><message><payload><accept-responsexmlns="http://www.citrusframework.org/schema/mail/message"><accept>true</accept></accept-response></payload></message></send>
Dependingontheacceptoutcomethemailclientwillreceiveanerrorresponsewithpropererrorcodes.Ifyouacceptthemailmessagewithapositiveacceptresponsethenextstepinyourtestreceivestheactualmailmessageaswehaveseenitbeforeinthischapter.
Nowbesidesnotacceptingamailmessageinthefirstplaceyoucanalssimulateanothererrorscenariowiththemailserver.InthisscenariothemailservershouldrespondwithsomesortofSMTPerrorcodeafteracceptingthemessage.Thisisdonewithaspecialmailresponsemessagelikethis:
CitrusReferenceGuide
345Mail
<receiveendpoint="advancedMailServer"><message><payload><mail-messagexmlns="http://www.citrusframework.org/schema/mail/message"><from>[email protected]</from><to>[email protected]</to><cc></cc><bcc></bcc><subject>Thisisatestmailmessage</subject><body><contentType>text/plain;charset=utf-8</contentType><content>HelloCitrusmailserver!</content></body></mail-message></payload></message></receive>
<sendendpoint="advancedMailServer"><message><payload><mail-responsexmlns="http://www.citrusframework.org/schema/mail/message"><code>443</code><message>Failed!</message></mail-response></payload></message></send>
Asyoucanseefromtheexampleabovewefirstaccepttheconnectionandreceivethemailcontentasusual.Nowthetestreturnsanegativemailresponsewithsomeerrorcodereasonset.TheCitrusSMTPcommunicationwillthenfailandthecallingmailclientreceivestherespectiveerror.
IfyouskipthenegativemailresponsetheserverwillautomaticallyresponsewithpositiveSMTPresponsecodestothecallingclient.
CitrusReferenceGuide
346Mail
ArquilliansupportArquillianisawellknownintegrationtestframeworkthatcomeswithagreatfeaturesetwhenitcomestoJavaEEtestinginsideofafullqualifiedapplicationserver.WithArquiliianyoucandeployyourJavaEEservicesinarealapplicationserverofyourchoiceandexecutethetestsinsidetheapplicationserverboundaries.ThismakesitveryeasytotestyourJavaEEservicesinscopewithproperJNDIresourceallocationandotherresourcesprovidedbytheapplicationserver.CitrusisabletoconnectwiththeArquilliantestcase.SpeakinginmoredetailyourArquilliantestisabletouseaCitrusextensioninordertousetheCitrusfeaturesetinsidetheArquillianboundaries.
ReadthenextsectioninordertofindoutmoreabouttheCitrusArquillianextension.
CitrusArquillianextension
ArquillianoffersafinemechanismforextensionsaddingfeaturestotheArquilliantestsetupandtestexecution.TheCitrusextensionrespectivelyaddsCitrusframeworkinstancecreationandCitrustestexecutiontotheArquillianworld.Firstofallletshavealookattheextensiondescriptorpropertiessettableviaarquillian.xml:
<extensionqualifier="citrus"><propertyname="citrusVersion">2.7.1</property><propertyname="autoPackage">true</property><propertyname="suiteName">citrus-arquillian-suite</property></extension>
TheCitrusextensionusesaspecificqualifiercitrusfordefiningpropertiesinsidetheArquilliandescriptor.Followingpropertiesaresettableincurrentversion:
citrusVersion:TheexplicitversionofCitrusthatshouldbeused.Besuretohavethesamelibraryversionavailableinyourproject(e.g.asMavendependency).Thispropertyisoptional.
Bydefaulttheextensionjustusesthelateststableversion.
autoPackage:Whentrue(defaultsetting)theextensionwillautomaticallyaddCitruslibrariesandalltransitivedependenciestothetestdeployment.ThisautomaticallyenablesyoutousetheCitrusAPIinsidetheArquilliantest
evenwhenthetestisexecutedinsidetheapplicationcontainer.
CitrusReferenceGuide
347Arquillian
suiteName:ThisoptionalsettingdefinesthenameofthetestsuitethatisusedfortheCitrustestrun.Whenusingbefore/aftersuitefunctionalityinCitrusthissettingmightbeofinterest.configurationClass:FullqualifiedJavaclassnameofcustomizedCitrusSpringbeanconfigurationtousewhenloadingtheCitrusSpringapplicationcontext.Asauseryoucandefineacustomconfigurationclassthatmust
beasubclassofcom.consol.citrus.config.CitrusSpringConfig.Whenspecifiedthecustomclassisloadedotherwisethedefaultcom.consol.citrus.config.CitrusSpringConfigisloadedtosetuptheSpringapplicationcontext.
NowthatwehaveaddedtheextensiondescriptorwithallpropertiesweneedtoaddtherespectiveCitrusArquillianextensionaslibrarytoourproject.ThisisdoneviaMaveninyourproject'sPOMfileasnormaldependency:
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-arquillian</artifactId><version>2.7.1</version><scope>test</scope></dependency>
NoweverythingissetuptouseCitruswithinArquillian.LetsuseCitrusfunctionalityinaArquilliantestcase.
Clientsidetesting
Arquillianseparatesclientandcontainersidetesting.Whenusingclientsidetestingthetestcaseisexecutedoutsideoftheapplicationcontainerdeployment.ThismeansthatyourtestcasehasnodirectaccesstocontainermanagedresourcessuchasJNDIresources.Ontheplussideitisnotnecessarytoincludeyourtestinthecontainerdeployment.Thetestcaseinteractswiththecontainerdeploymentasanormalclientwoulddo.Letshavealookatafirstexample:
@RunWith(Arquillian.class)@RunAsClientpublicclassEmployeeResourceTest
@CitrusFrameworkprivateCitruscitrusFramework;
CitrusReferenceGuide
348Arquillian
@ArquillianResourceprivateURLbaseUri;
privateStringserviceUri;
@DeploymentpublicstaticWebArchivecreateDeployment()returnShrinkWrap.create(WebArchive.class).addClasses(RegistryApplication.class,EmployeeResource.class,Employees.class,Employee.class,EmployeeRepository.class);
@BeforepublicvoidsetUp()throwsMalformedURLExceptionserviceUri=newURL(baseUri,"registry/employee").toExternalForm();
@Test@CitrusTestpublicvoidtestCreateEmployeeAndGet(@CitrusResourceTestDesignerdesigner)designer.send(serviceUri).message(newHttpMessage("name=Penny&age=20").method(HttpMethod.POST).contentType(MediaType.APPLICATION_FORM_URLENCODED));
designer.receive(serviceUri).message(newHttpMessage().statusCode(HttpStatus.NO_CONTENT));
designer.send(serviceUri).message(newHttpMessage().method(HttpMethod.GET).accept(MediaType.APPLICATION_XML));
designer.receive(serviceUri).message(newHttpMessage(""+""+"20"+"Penny"+""+"").statusCode(HttpStatus.OK));
citrusFramework.run(designer.build());
FirstofallweusethebasicArquillianJUnittestrunner@RunWith(Arquillian.class)incombinationwiththe@RunAsClientannotationtellingArquillianthatthisisaclientsidetestcase.AsthisisausualArquilliantestcasewehaveaccesstoArquillianresources
CitrusReferenceGuide
349Arquillian
thatautomaticallygetinjectedsuchasthebaseuriofthetestdeployment.ThetestdeploymentisawebdeploymentcreatedviaShrinkWrap.WeaddtheapplicationspecificclassesthatbuildourremoteRESTfulservicethatwewouldliketotest.
TheCitrusArquillianextensionisabletosetupaproperCitrustestenvironmentinthebackground.AsaresultthetestcasecanreferenceaCitrusframeworkinstancewiththe@CitrusFrameworkannotation.WewillusethisinstanceofCitruslateronwhenitcomestoexecutetheCitrustestinglogic.
NowecanfocusonwritingatestmethodwhichisagainnothingbutanormalJUnittestmethod.TheCitrusextensiontakescareoninjectingthe@CitrusResourceannotatedmethodparameter.WiththisCitrustestdesignerinstancewecanbuildaCitrustestlogicforsendingandreceivingmessagesviaHttpinordertocalltheremoteRESTfulemployeeserviceofourtestdeployment.TheHttpendpointuriisinjectedviaArquillianandweareabletocalltheremoteserviceasaclient.
TheCitrustestdesignerprovidesJavaDSLmethodsforbuildingthetestlogic.Pleasenotethatthedesignerwillaggregateallactionssuchassendorreceiveuntilthedesigneriscalledtobuildthetestcasewithbuild()methodinvocation.TheresultingtestcaseobjectcanbeexecutedbytheCitrusframeworkinstancewithrun()method.
WhentheCitrustestcaseisexecutedthemessagesaresentoverthewire.TherespectiveresponsemessageisreceivedwithwellknownCitrusreceivemessagelogic.Wecanvalidatetheresponsemessagesaccordinglyandmakesuretheclientcallwasdoneright.IncasesomethinggoeswrongwithinCitrustestexecutiontheframeworkwillraiseexceptionsaccordingly.AsaresulttheJUnittestmethodissuccessfulorfailedwitherrorscomingfromCitrustestexecution.
ThisishowCitrusandArquilliancaninteractinatestscenariowherethetestdeploymentismanagedbyArquillianandtheclientsideactionstakeplacewithinCitrus.ThisisagreatwaytocombinebothframeworkswithCitrusbeingabletocalldifferentserviceAPIendpointsinadditionwithvalidatingtheoutcome.Thiswasaclientsidetestcasewherethetestlogicwasexecutedoutsideoftheapplicationcontainer.Arquillianalsosupportscontainerremotetestcaseswherewehavedirectaccesstocontainermanagedresources.ThefollowingsectiondescribeshowthisworkswithCitrus.
Containersidetesting
InprevioussectionswehaveseenhowtocombineCitruswithArquillianinaclientsidetestcase.Thisisthewaytogoforalltestcasesthatdonotneedtohaveaccessoncontainermanagedresources.Letshavealookatasamplewherewewanttogain
CitrusReferenceGuide
350Arquillian
accesstoaJMSqueueandconnectionmanagedbytheapplicationcontainer.
@RunWith(Arquillian.class)publicclassEchoServiceTest
@CitrusFrameworkprivateCitruscitrusFramework;
@Resource(mappedName="jms/queue/test")privateQueueechoQueue;
@Resource(mappedName="/ConnectionFactory")privateConnectionFactoryconnectionFactory;
privateJmsSyncEndpointjmsSyncEndpoint;
@Deployment@OverProtocol("Servlet3.0")publicstaticWebArchivecreateDeployment()throwsMalformedURLExceptionreturnShrinkWrap.create(WebArchive.class).addClasses(EchoService.class);
@BeforepublicvoidsetUp()JmsSyncEndpointConfigurationendpointConfiguration=newJmsSyncEndpointConfiguration();endpointConfiguration.setConnectionFactory(newSingleConnectionFactory(connectionFactory));endpointConfiguration.setDestination(echoQueue);jmsSyncEndpoint=newJmsSyncEndpoint(endpointConfiguration);
@AfterpublicvoidcleanUp()closeConnections();
@Test@CitrusTestpublicvoidshouldBeAbleToSendMessage(@CitrusResourceTestDesignerdesigner)throwsExceptionStringmessageBody="ping";
designer.send(jmsSyncEndpoint).messageType(MessageType.PLAINTEXT).message(newJmsMessage(messageBody));
designer.receive(jmsSyncEndpoint).messageType(MessageType.PLAINTEXT).message(newJmsMessage(messageBody));
citrusFramework.run(designer.build());
CitrusReferenceGuide
351Arquillian
privatevoidcloseConnections()((SingleConnectionFactory)jmsSyncEndpoint.getEndpointConfiguration().getConnectionFactory()).destroy();
AsyoucanseethetestcaseaccessestwocontainermanagedresourcesviaJNDI.ThisisaJMSqueueandaJMSconnectionthatgetautomaticallyinjectedasresources.InabeforetestannotatedmethodwecanusetheseresourcestobuildupaproperCitrusJMSendpoint.InsidethetestmethodwecanusetheJMSendpointforsendingandreceivingJMSmessagesviaCitrus.Asusualresponsemessagesreceivedarevalidatedandcomparedtoanexpectedmessage.AsusualweusetheCitrusTestDesignermethodparameterthatisinjectedbytheframework.ThedesignerisabletobuildCitrustestlogicwithJavaDSLmethods.Oncethecompletetestisdesignedwecanbuildthetestcaseandrunthetestcasewiththeframeworkinstance.AfterthetestweshouldclosetheJMSconnectioninordertoavoidexceptionswhentheapplicationcontainerisshuttingdownafterthetest.
Thetestisnowpartofthetestdeploymentandisexecutedwithintheapplicationcontainerboundaries.AsusualwecanusetheCitrusextensiontoautomaticallyinjecttheCitrusframeworkinstanceaswellastheCitrustestbuilderinstanceforbuildingtheCitrustestlogic.
ThisishowtocombineCitrusandArquillianinordertobuildintegrationtestsonJavaEEservicesinarealapplicationcontainerenvironment.WithCitrusyouareabletosetupmorecomplextestscenarioswithsimulatedservicessuchasmailorftpservers.WecanbuildCitrusendpointswithcontainermanagedresources.
Testrunners
IntheprevioussectionswehaveusedtheCitrusTestDesignerinordertoconstructaCitrustestcasetoexecutewithintheArquillianboundaries.ThenatureofthetestdesigneristoaggregateallJavaDSLmethodcallsinordertobuildacompleteCitrustestcasebeforeexecutionisdoneviatheCitrusframework.ThisapproachcancausesomeunexpectedbehaviorwhenmixingtheCitrusJavaDSLmethodcallswithArquilliantestlogic.LetsdescribethisbyhavingalookatanexamplewherethmixtureoftestdesignerandpureJavatestlogiccausesunseenproblems.
@Test@CitrusTest
CitrusReferenceGuide
352Arquillian
publicvoidtestDesignRuntimeMixture(@CitrusResourceTestDesignerdesigner)throwsExceptiondesigner.send(serviceUri).message(newHttpMessage("name=Penny&age=20").method(HttpMethod.POST).contentType(MediaType.APPLICATION_FORM_URLENCODED));
designer.receive(serviceUri).message(newHttpMessage()).statusCode(HttpStatus.NO_CONTENT));
EmployeetestEmployee=employeeService.findEmployee("Penny");employeeService.addJob(testEmployee,"waitress");
designer.send(serviceUri).message(newHttpMessage().method(HttpMethod.GET).accept(MediaType.APPLICATION_XML));
designer.receive(serviceUri).message(newHttpMessage(""+""+"20"+"Penny"+""+"waitress"+""+""+"")).statusCode(HttpStatus.OK));
citrusFramework.run(designer.build());
AsyoucanseeinthisexamplewecreateanewEmployeenamedPennyviatheHttpRESTAPIonourservice.WedothiswithCitrusHttpsendandreceivemessagelogic.Oncethisisdonewewouldliketoaddajobdescriptiontotheemployee.WeuseaserviceinstanceofEmployeeServicewhichisaserviceofourtestdomainthatisinjectedtotheArquilliantestascontainerJEEresource.Firstofallwefindtheemployeeobjectandthenweaddsomejobdescriptionusingtheservice.NowasaresultwewouldliketoreceivetheemployeeasXMLrepresentationviaaRESTservicecallwithCitrusandweexpectthejobdescriptiontobepresent.
ThiscombinationofCitrusJavaDSLmethodsandservicecalllogicwillnotworkwithTestDesigner.ThisisbecausetheCitrustestlogicisnotexecutedimmediatelybutaggregatedtotheveryendwherethedesigneriscalledtobuildthetestcase.ThecombinationofCitrusdesigntimeandJavatestruntimeistricky.
CitrusReferenceGuide
353Arquillian
FortunatelywehavesolvedthisissuewithprovidingaseparateTestRunnercomponent.ThetestrunnerprovidesnearlythesameJavaDSLmethodsforconstructingCitrustestlogicasthetestdesigner.ThedifferencethoughisthatthetestlogicisexecutedimmediatelywhencallingtheJavaDSLmethods.SofollowingfromthatwecanmixCitrusJavaDSLcodewithtestruntimelogicasexpected.Seehowthislookslikewithourexample:
@Test@CitrusTestpublicvoidtestDesignRuntimeMixture(@CitrusResourceTestRunnerrunner)throwsExceptionrunner.send(builder->builder.endpoint(serviceUri).message(newHttpMessage("name=Penny&age=20").method(HttpMethod.POST).contentType(MediaType.APPLICATION_FORM_URLENCODED)));
runner.receive(builder->builder.endpoint(serviceUri).message(newHttpMessage().statusCode(HttpStatus.NO_CONTENT)));
EmployeetestEmployee=employeeService.findEmployee("Penny");employeeService.addJob(testEmployee,"waitress");
runner.send(builder->builder.endpoint(serviceUri).message(newHttpMessage().method(HttpMethod.GET).accept(MediaType.APPLICATION_XML)));
runner.receive(builder->builder.endpoint(serviceUri).message(newHttpMessage(""+""+"20"+"Penny"+""+"waitress"+""+""+"").statusCode(HttpStatus.OK)));
Thetestlogichasnotchangedsignificantly.WeusetheCitrusTestRunnerasmethodinjectedparameterinsteadoftheTestDesigner.Andthisisprettymuchthetrick.NowtheJavaDSLmethodsdoexecutetheCitrustestlogicimmediately.ThisiswhythesyntaxoftheCitrusJavaDSLmethodshavechangedalittlebit.Wenowusea
CitrusReferenceGuide
354Arquillian
anonymousinterfaceimplementationforconstructingthesend/receivetestactionlogic.AsaresultwecanusetheCitrusJavaDSLasnormalcodeandwecanmixtheruntimeJavalogicaseachstatementisexecutedimmediately.
WithJava8lambdaexpressionsourcodelooksevenmorestraightforwardandlessverboseaswecanskiptheanonymousinterfaceimplementations.WithJava8youcanwritethesametestlikethis:
@Test@CitrusTestpublicvoidtestDesignRuntimeMixture(@CitrusResourceTestRunnerrunner)throwsExceptionrunner.send(builder->builder.endpoint(serviceUri).message(newHttpMessage("name=Penny&age=20").method(HttpMethod.POST).contentType(MediaType.APPLICATION_FORM_URLENCODED));
runner.receive(builder->builder.endpoint(serviceUri).message(newHttpMessage().statusCode(HttpStatus.NO_CONTENT));
EmployeetestEmployee=employeeService.findEmployee("Penny");employeeService.addJob(testEmployee,"waitress");
runner.send(builder->builder.endpoint(serviceUri).message(newHttpMessage().method(HttpMethod.GET).accept(MediaType.APPLICATION_XML));
runner.receive(builder->builder.endpoint(serviceUri).message(newHttpMessage(""+""+"20"+"Penny"+""+"waitress"+""+""+"").statusCode(HttpStatus.OK));
CitrusReferenceGuide
355Arquillian
DockersupportCitrusprovidesconfigurationcomponentsandtestactionsforinteractionwithaDockerdaemon.TheCitrusdockerclientcomponentwillexecuteDockercommandsforcontainermanagementsuchasstart,stop,build,inspectandsoon.TheDockerclientbydefaultusestheDockerremoteRESTAPI.AsauseryoucanexecuteDockercommandsaspartofaCitrustestandvalidatepossiblecommandresults.
NoteTheDockertestcomponentsinCitrusarekeptinaseparateMavenmodule.IfnotalreadydonesoyouhavetoincludethemoduleasMavendependencytoyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-docker</artifactId><version>2.7.1</version></dependency>
Citrusprovidesa"citrus-docker"configurationnamespaceandschemadefinitionforDockerrelatedcomponentsandactions.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitrusDockerconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus-docker="http://www.citrusframework.org/schema/docker/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/docker/confighttp://www.citrusframework.org/schema/docker/config/citrus-docker-config.xsd">
[...]
</beans>
AfterthatyouareabletousecustomizedCitrusXMLelementsinordertodefinetheSpringbeans.
Dockerclient
CitrusReferenceGuide
356Docker
CitrusoperateswiththeDockerremoteRESTAPIinordertointeractwiththeDockerdaemon.TheDockerclientisdefinedasSpringbeancomponentintheconfigurationasfollows:
<citrus-docker:clientid="dockerClient"/>
TheDockerclientcomponentaboveisusingalldefaultconfigurationvalues.BydefaultCitrusissearchingthesystempropertiesaswellasenvironmentvariablesfordefaultDockersettingssuchas:
DOCKER_HOST="tcp://localhost:2376"DOCKER_CERT_PATH="~/.docker/machine/machines/default"DOCKER_TLS_VERIFY="1"DOCKER_MACHINE_NAME="default"
IncasethesesettingsarenotsettableinyourenvironmentyoucanalsouseexplicitsettingsintheDockerclientcomponent:
<citrus-docker:clientid="dockerClient"url="tcp://localhost:2376"version="1.20"username="user"password="s!cr!t"email="[email protected]"registry="https://index.docker.io/v1/"cert-path="/path/to/some/cert/directory"config-path="/path/to/some/config/directory"/>
NowCitrusisabletoaccesstheDockerremoteAPIforexecutingcommandssuchasstart,stop,build,inspectandsoon.
Dockercommands
WehaveseveralCitrustestactionseachrepresentingaDockercommand.TheseactionscanbepartofatestcasewhereyoucanmanageDockercontainersinsidethetest.AsaprerequisitewehavetoenabletheDockerspecifictestactionsinourXMLtestasfollows:
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:docker="http://www.citrusframework.org/schema/docker/testcase"xsi:schemaLocation="
CitrusReferenceGuide
357Docker
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/docker/testcasehttp://www.citrusframework.org/schema/docker/testcase/citrus-docker-testcase.xsd">
[...]
</beans>
Weaddedaspecialdockernamespacewithprefixdocker:sonowwecanstarttoaddDockertestactionstothetestcase:
XMLDSL
<testcasename="DockerCommandIT"><actions><docker:ping></docker:ping>
<docker:version><docker:expect><docker:result><![CDATA["Version":"1.8.3","ApiVersion":"1.21","GitCommit":"@ignore@","GoVersion":"go1.4.2","Os":"darwin","Arch":"amd64","KernelVersion":"@ignore@"]]></docker:result></docker:expect></docker:version></actions></testcase>
InthisverysimpleexamplewefirstpingtheDockerdaemontomakesurewehaveconnectivityupandrunning.AfterthatwegettheDockerversioninformation.ThesecondactionshowsanimportantconceptwhenexecutingDockercommandsinCitrus.Asatesterwemightbeinterestedinvalidatingthecommandresult.Sowencanspecifyanoptionaldocker:resultwhichisusuallyinJSONdataformat.AsusualwecanusetestvariableshereandignoresomevaluesexplicitlysuchastheGitCommitvalue.
BasedonthatwecanexecuteseveralDockercommandsinatestcase:
CitrusReferenceGuide
358Docker
XMLDSL
<testcasename="DockerCommandIT"><variables><variablename="imageId"value="busybox"></variable><variablename="containerName"value="citrus_box"></variable></variables>
<actions><docker:pullimage="$imageId"tag="latest"/>
<docker:createimage="$imageId"name="$containerName"cmd="top"><docker:expect><docker:result><![CDATA["Id":"@variable(containerId)@","Warnings":null]]></docker:result></docker:expect></docker:create>
<docker:startcontainer="$containerName"/></actions></testcase>
InthisexamplewepullaDockerimage,buildanewcontaineroutofthisimageandstartthecontainer.AsyoucanseeeachDockercommandactionoffersattributessuchascontainer,imageortag.ThesearecommandsettingsthatareavailableontheDockercommandspecification.ReadmoreabouttheDockercommandsandthespecificsettingsinofficialDockerAPIreferenceguide.
CitrussupportsthefollowingDockercommandswithrespectivetestactions:
docker:pulldocker:builddocker:createdocker:startdocker:stopdocker:waitdocker:pingdocker:versiondocker:inspect
CitrusReferenceGuide
359Docker
docker:removedocker:info
SomeoftheDockercommandscanbeexecutedbothoncontainerandimagetargetssuchasdocker:inspectordocker:remove.Thecommandactionthenoffersbothcontainerandimageattributessotheusercanchoosethetargetofthecommandoperationtobeacontaineroranimage.
UptonowwehaveonlyusedtheCitrusXMLDSL.OfcourseallDockercommandsarealsoavailableinJavaDSLasthenextexampleshows.
JavaDSL
@CitrusTestpublicvoiddockerTest()docker().version().validateCommandResult(newCommandResultCallback<Version>()@OverridepublicvoiddoWithCommandResult(Versionversion,TestContextcontext)Assert.assertEquals(version.getApiVersion(),"1.20"););
docker().ping();
docker().start("my_container");
TheJavaDSLDockercommandsprovideanoptionalCommandResultCallbackthatiscalledwiththeunmarshalledcommandresultobject.IntheexampleabovetheVersionmodelobjectispassedasargumenttothecallback.Sothetestercanaccessthecommandresultandvalidateitspropertieswithassertions.
BydefaultCitrustriestofindaDockerclientcomponentwithintheCitrusSpringapplicationcontext.IfnotpresentCitruswillinstantiateadefaultdockerclientwithalldefaultsettings.YoucanalsoexplicitlysetthedockerclientinstancewhenusingtheJavaDSLDockercommandactions:
JavaDSL
@AutowiredprivateDockerClientdockerClient;
@CitrusTestpublicvoiddockerTest()
CitrusReferenceGuide
360Docker
docker().client(dockerClient).version().validateCommandResult(newCommandResultCallback<Version>()@OverridepublicvoiddoWithCommandResult(Versionversion,TestContextcontext)Assert.assertEquals(version.getApiVersion(),"1.20"););
docker().client(dockerClient).ping();
docker().client(dockerClient).start("my_container");
CitrusReferenceGuide
361Docker
KubernetessupportKubernetesisoneofthehottestmanagementplatformsforcontainerizedapplicationsthesedays.Kubernetesletsyoudeploy,scaleandmanageyourcontainersontheplatformsoyougetfeatureslikeauto-scaling,self-healing,servicediscoveryandloadbalancing.CitrusprovidesinteractionwiththeKubernetesRESTAPIsoyoucanaccesstheKubernetesplatformanditsresourceswithinaCitrustestcase.
NoteTheKubernetestestcomponentsinCitrusarekeptinaseparateMavenmodule.IfnotalreadydonesoyouhavetoincludethemoduleasMavendependencytoyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-kubernetes</artifactId><version>2.7.1</version></dependency>
Citrusprovidesa"citrus-kubernetes"configurationnamespaceandschemadefinitionforKubernetesrelatedcomponentsandactions.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitrusKubernetesconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus-k8s="http://www.citrusframework.org/schema/kubernetes/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/kubernetes/confighttp://www.citrusframework.org/schema/kubernetes/config/citrus-kubernetes-config.xsd">
[...]
</beans>
AfterthatyouareabletousecustomizedCitrusXMLelementsinordertodefinetheSpringbeans.
CitrusReferenceGuide
362Kubernetes
Kubernetesclient
CitrusoperateswiththeKubernetesremoteRESTAPIinordertointeractwiththeKubernetesplatform.TheKubernetesclientisdefinedasSpringbeancomponentintheconfigurationasfollows:
<citrus-k8s:clientid="myK8sClient"/>
TheKubernetesclientisbasedontheFabric8JavaKubernetesclientimplementation.Followingfromthatthecomponentcanbeconfiguredinvariousways.BydefaulttheclientreadsthesystempropertiesaswellasenvironmentvariablesfordefaultKubernetessettingssuchas:
kubernetes.master/KUBERNETES_MASTERkubernetes.api.version/KUBERNETES_API_VERSIONkubernetes.trust.certificates/KUBERNETES_TRUST_CERTIFICATES
Ifyousetthesepropertiesinyourenvironmenttheclientcomponentwillautomaticallypickuptheconfigurationsettings.Alsowhenusingkubectlcommandlinelocallytheclientmayautomaticallyusethestoreduserauthenticationsettingsfromthere.ForacompletelistofsettingsandexplanationofthosepleaserefertotheFabric8clientdocumentation.
IncaseyouneedtosettheclientconfigurationexplicitlyonyourenvironmentyoucanalsouseexplicitsettingsontheKubernetesclientcomponent:
<citrus-k8s:clientid="myK8sClient"url="http://localhost:8843"version="v1"username="user"password="s!cr!t"namespace="user_namespace"message-converter="messageConverter"object-mapper="objectMapper"/>
NowCitrusisabletoaccesstheKubernetesremoteAPIforexecutingcommandssuchaslist-pods,watch-servicesandsoon.CitrusprovidesasetofactionsthatperformaKubernetescommandviaREST.TheresultsusuallygetvalidatedintheCitrustestasusual.
BasedonthatwecanexecuteseveralKubernetescommandsinatestcaseandvalidatetheJsonresults:
CitrusReferenceGuide
363Kubernetes
CitrussupportsthefollowingKubernetesAPIcommandswithrespectivetestactions:
k8s:infok8s:list-podsk8s:get-podk8s:delete-podk8s:list-servicesk8s:get-servicek8s:delete-servicek8s:list-namespacesk8s:list-eventsk8s:list-endpointsk8s:list-nodesk8s:list-replication-controllersk8s:watch-podsk8s:watch-servicesk8s:watch-namespacesk8s:watch-nodesk8s:watch-replication-controllers
Wewilldiscussthesecommandsindetaillateroninthischapter.FornowletshaveacloserlookonhowtousethecommandsinsideofaCitrustest.
KubernetescommandsinXML
WehaveseveralCitrustestactionseachrepresentingaKubernetescommand.TheseactionscanbepartofatestcasewhereyoucanmanageKubernetespodsinsidethetest.AsaprerequisitewehavetoenabletheKubernetesspecifictestactionsinourXMLtestasfollows:
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:k8s="http://www.citrusframework.org/schema/kubernetes/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/kubernetes/testcasehttp://www.citrusframework.org/schema/kubernetes/testcase/citrus-kubernetes-testcase.xsd"
[...]
</beans>
CitrusReferenceGuide
364Kubernetes
Weaddedaspecialkubernetesnamespacewithprefixk8s:sonowwecanstarttoaddKubernetestestactionstothetestcase:
XMLDSL
<testcasename="KubernetesCommandIT"><actions><k8s:infoclient="myK8sClient"><k8s:validate><k8s:result>"result":"clientVersion":"1.4.27","apiVersion":"v1","kind":"Info","masterUrl":"$masterUrl","namespace":"test"</k8s:result></k8s:validate></k8s:info>
<k8s:list-pods><k8s:validate><k8s:result>"result":"apiVersion":"v1","kind":"PodList","metadata":"@ignore@","items":[]</k8s:result><k8s:elementpath="$.result.items.size()"value="0"/></k8s:validate></k8s:list-pods></actions></testcase>
InthisverysimpleexamplewefirstpingtheKubernetesRESTAPItomakesurewehaveconnectivityupandrunning.TheinfocommandconnectstheRESTAPIandreturnsalistofstatusinformationoftheKubernetesclient.AfterthatwegetthelistofavailableKubernetespods.Asatesterwemightbeinterestedinvalidatingthecommandresults.Sowencanspecifyanoptionalk8s:resultwhichisusuallyinJsonformat.WiththatwecanapplythefullCitrusJsonvalidationpowertotheKubernetes
CitrusReferenceGuide
365Kubernetes
results.Asusualwecanusetestvariableshereandignoresomevaluesexplicitlysuchasthemetadatavalue.AlsoJsonPathexpressionvalidationandJsontestmessagevalidationfeaturesinCitruscomeinheretovalidatetheresults.
KubernetescommandsinJava
UptonowwehaveonlyusedtheCitrusXMLDSL.OfcourseallKubernetescommandsarealsoavailableinJavaDSLasthenextexampleshows.
JavaDSL
@CitrusTestpublicvoidkubernetesTest()kubernetes().info().validate(newCommandResultCallback<InfoResult>()@OverridepublicvoiddoWithCommandResult(InfoResultinfo,TestContextcontext)Assert.assertEquals(info.getApiVersion(),"v1"););
kubernetes().pods().list().withoutLabel("running").label("app","myApp");
TheJavaDSLKubernetescommandsprovideanoptionalCommandResultCallbackthatisautomaticallycalledwiththeunmarshalledcommandresultobject.IntheexampleabovetheInfoResultmodelobjectispassedasargumenttothecallback.Sothetestercanaccessthecommandresultandvalidateitspropertieswithassertions.
Java8Lambdaexpressionsaddsomesyntacticalsugartothecommandresultvalidation:
JavaDSL
@CitrusTestpublicvoidkubernetesTest()kubernetes().info().validate((info,context)->Assert.assertEquals(info.getApiVersion(),"v1"));
kubernetes().pods().list()
CitrusReferenceGuide
366Kubernetes
.withoutLabel("running").label("app","myApp");
BydefaultCitrustriestofindaKubernetesclientcomponentwithintheCitrusSpringapplicationcontext.IfnotpresentCitruswillinstantiateadefaultkubernetesclientwithalldefaultsettings.YoucanalsoexplicitlysetthekubernetesclientinstancewhenusingtheJavaDSLKubernetescommandactions:
JavaDSL
@AutowiredprivateKubernetesClientkubernetesClient;
@CitrusTestpublicvoidkubernetesTest()kubernetes().client(kubernetesClient).info().validate((info,context)->Assert.assertEquals(info.getApiVersion(),"v1"));
kubernetes().client(kubernetesClient).pods().list().withoutLabel("running").label("app","myApp");
Infocommand
TheinfocommandjustgetstheclientconnectionsettingsandprovidesthemasaJsonresulttotheaction.
XMLDSL
<k8s:infoclient="myK8sClient"><k8s:validate><k8s:result>"result":"clientVersion":"1.4.27","apiVersion":"v1","kind":"Info","masterUrl":"$masterUrl","namespace":"test"
CitrusReferenceGuide
367Kubernetes
</k8s:result></k8s:validate></k8s:info>
JavaDSL
@CitrusTestpublicvoidinfoTest()kubernetes().info().validate((info,context)->Assert.assertEquals(info.getApiVersion(),"v1"));
Listresources
WecanlistKubernetesresourcessuchaspods,services,endpointsandreplicationcontrollers.Thelistcanbefilteredbyseveralpropertiessuchas
labelnamespace
Thetestactionisabletodefinerespectivefilterstothelistsowegetonlypodsthematchthegivenattributes:
XMLDSL
<k8s:list-podslabel="app=todo"><k8s:validate><k8s:result>"result":"apiVersion":"$apiVersion","kind":"PodList","metadata":"@ignore@","items":"@ignore@"</k8s:result><k8s:elementpath="$.result.items.size()"value="1"/><k8s:elementpath="$..status.phase"value="Running"/></k8s:validate></k8s:list-pods>
JavaDSL
@CitrusTestpublicvoidlistPodsTest()
CitrusReferenceGuide
368Kubernetes
kubernetes().client(k8sClient).pods().list().label("app=todo").validate("$..status.phase","Running").validate((pods,context)->Assert.assertFalse(CollectionUtils.isEmpty(pods.getResult().getItems())););
Asyoucanseeweareabletogivethepodlabelthatissearchedforinlistofallpods.ThelistreturnedisvalidatedeitherbygivinganexpectedJsonmessageorbyaddingJsonPathexpressionswithexpectedvaluestocheck.
InJavaDSLwecanaddavalidationresultcallbackthatisprovidedwiththeunmarshalledresultobjectforvalidation.Besideslabelfilteringwecanalsospecifythenamespaceandthepodnametosearchfor.
Youcanalsodefinemultiplelabelsascommadelimitedlist:
<k8s:list-serviceslabel="stage!=test,provider=fabric8"namespace="default"/>
Asyoucanseewehavecombinedtolabelfiltersstage!=testandprovider=fabric8onpodsinnamespacedefault.Thefirstlabelfilterisnegatedsothelabelstageshouldnotbetesthere.
Listnodesandnamespaces
Nodesandnamespacesarespecialresourcesthatarenotfilteredbytheirnamespaceastheyaremoreglobalresources.Therestisprettysimilartolistingpodsorservices.Wecanaddfilteressuchasnameandlabel.
XMLDSL
<k8s:list-namespaceslabel="provider=citrus"><k8s:validate><k8s:elementpath="$.result.items.size()"value="1"/></k8s:validate></k8s:list-namespaces>
JavaDSL
CitrusReferenceGuide
369Kubernetes
@CitrusTestpublicvoidlistPodsTest()kubernetes().client(k8sClient).namespaces().list().label("provider=citrus").validate((pods,context)->Assert.assertFalse(CollectionUtils.isEmpty(pods.getResult().getItems())););
Getresources
WecangetaveryspecialKubernetesresourcesuchasapodorservicefordetailedvalidationofthatresource.WeneedtospecifyaresourcenameinordertoselecttheresourcefromlistofavailableresourcesinKubernetes.
XMLDSL
<k8s:get-podname="citrus_pod"><k8s:validate><k8s:result>"result":"apiVersion":"$apiVersion","kind":"Pod","metadata":"annotations":"@ignore@","creationTimestamp":"@ignore@","finalizers":[],"generateName":"@startsWith('hello-minikube-')@","labels":"pod-template-hash":"@ignore@","run":"hello-minikube","name":"$podName","namespace":"default","ownerReferences":"@ignore@","resourceVersion":"@ignore@","selfLink":"/api/$apiVersion/namespaces/default/pods/$podName","uid":"@ignore@","spec":"containers":["args":[],"command":[],"env":[],"image":"gcr.io/google_containers/echoserver:1.4",
CitrusReferenceGuide
370Kubernetes
"imagePullPolicy":"IfNotPresent","name":"hello-minikube","ports":["containerPort":8080,"protocol":"TCP"],"resources":,"terminationMessagePath":"/dev/termination-log","volumeMounts":"@ignore@"],"dnsPolicy":"ClusterFirst","imagePullSecrets":"@ignore@","nodeName":"minikube","restartPolicy":"Always","securityContext":"@ignore@","serviceAccount":"default","serviceAccountName":"default","terminationGracePeriodSeconds":30,"volumes":"@ignore@","status":"@ignore@"</k8s:result><k8s:elementpath="$..status.phase"value="Running"/></k8s:validate></k8s:get-pod>
JavaDSL
@CitrusTestpublicvoidgetPodsTest()kubernetes().client(k8sClient).pods().get("citrus_pod").validate("$..status.phase","Running").validate((pod,context)->Assert.assertEquals(pods.getResult().getStatus().getPhase(),"Running"););
AsyoucanseeweareablegetthecompletepodinformationfromKubernetes.TheresultisvalidatedwithJsonmessagevalidatorinCitrus.Thismeanswecanuse@ignore@aswellastestvariablesandJsonPathexpressions.
Createresources
CitrusReferenceGuide
371Kubernetes
WecancreatenewKubernetesresourceswithinaCitrustest.Thisisveryimportantincaseweneedtosetupnewpodsorservicesforthetestrun.Youcancreatenewresourcesbygivinga.ymlfileholdingallinformationhowtocreatethenewresource.SeethefollowingsampleYAMLforanewpodandservice:
kind:PodapiVersion:v1metadata:name:hello-jetty-$randomIdnamespace:defaultselfLink:/api/v1/namespaces/default/pods/hello-jetty-$randomIduid:citrus:randomUUID()labels:server:hello-jettyspec:containers:-name:hello-jettyimage:jetty:9.3imagePullPolicy:IfNotPresentports:-containerPort:8080protocol:TCPrestartPolicy:AlwaysterminationGracePeriodSeconds:30dnsPolicy:ClusterFirstserviceAccountName:defaultserviceAccount:defaultnodeName:minikube
ThisYAMLfilespecifiesanewresourceofkindPod.Wedefinethemetadataaswellasallcontainersthatarepartofthispod.Thecontainerisbuildfromjetty:9.3DockerimagethatshouldbepulledautomaticallyfromDockerHubregistry.Wealsoexposeport8080ascontainerPortsotheupcomingserviceconfigurationcanprovidethisporttoclientsasKubernetesservice.
kind:ServiceapiVersion:v1metadata:name:hello-jettynamespace:defaultselfLink:/api/v1/namespaces/default/services/hello-jettyuid:citrus:randomUUID()labels:service:hello-jettyspec:ports:-protocol:TCP
CitrusReferenceGuide
372Kubernetes
port:8080targetPort:8080nodePort:31citrus:randomNumber(3)selector:server:hello-jettytype:NodePortsessionAffinity:None
Theserviceresourcemapstheport8080andselectsallpodswithlabelserver=hello-jetty.Thismakesthejettycontaineravailabletoclients.TheservicetypeisNodePortwhichmeansthatclientsoutsideofKubernetesarealsoabletoaccesstheservicebyusingthedynamicportnodePort=31xxx.WecanuseCitrusfunctionssuchasrandomNumberintheYAMLfiles.
InthetestcasewecanusetheseYAMLfilestocreatetheresourcesinKubernetes:
XMLDSL
<k8s:create-podnamespace="default"><k8s:templatefile="classpath:templates/hello-jetty-pod.yml"/></k8s:create-pod>
<k8s:create-servicenamespace="default"><k8s:templatefile="classpath:templates/hello-jetty-service.yml"/></k8s:create-service>
JavaDSL
@CitrusTestpublicvoidcreatePodsTest()kubernetes().pods().create(newClassPathResource("templates/hello-jetty-pod.yml")).namespace("default");
kubernetes().services().create(newClassPathResource("templates/hello-jetty-service.yml")).namespace("default");
Creatingnewresourcesmaytakesometimetofinish.Kuberneteswillhavetopullimages,buildcontainersandstartupeverything.Thecreateactionisnotwaitingsynchronouslyforallthattohavehappened.Thereforewemightaddalist-podsactionthatwaitsforthenewresourcestoappear.
CitrusReferenceGuide
373Kubernetes
<repeat-onerror-until-truecondition="@assertThat('greaterThan(9)')@"auto-sleep="1000"><k8s:list-podslabel="server=hello-jetty"><k8s:validate><k8s:elementpath="$.result.items.size()"value="1"/><k8s:elementpath="$..status.phase"value="Running"/></k8s:validate></k8s:list-pods></repeat-onerror-until-true>
Withthisrepeatonerroractionwewaitforthenewserver=hello-jettylabeledpodtobeinstateRunning.
Deleteresources
WiththatcommandweareabletodeletearesourceinKubernetes.Uptonowdeletionofpodsandservicesissupported.Wehavetogiveanameoftheresourcethatwewanttodelete.
XMLDSL
<k8s:delete-podname="citrus_pod"><k8s:validate><k8s:elementpath="$.result.success"value="true"/></k8s:validate></k8s:delete-pod>
JavaDSL
@CitrusTestpublicvoiddeletePodsTest()kubernetes().pods().delete("citrus_pod").validate((result,context)->Assert.assertTrue(result.getResult().getSuccess()));
Watchresources
NoteThewatchoperationisstillinexperimentalstateandmayfacesevereadjustmentsandimprovementsinnearfuture.
CitrusReferenceGuide
374Kubernetes
WhenusingawatchcommandweaddasubscriptiontochangeeventsonaKubernetesresources.Sowecanwatchresourcessuchaspods,servicesforfuturechanges.Eachchangeonthatresourcetriggersanewwatcheventresultthatwecanexpectandvalidate.
XMLDSL
<k8s:watch-podslabel="provider=citrus"><k8s:validate><k8s:elementpath="$.action"value="DELETED"/></k8s:validate></k8s:watch-pods>
JavaDSL
@CitrusTestpublicvoidlistPodsTest()kubernetes().pods().watch().label("provider=citrus").validate((watchEvent,context)->Assert.assertFalse(watchEvent.hasError());Assert.assertEquals(((WatchEventResult)watchEvent).getAction(),Watcher.Action.DELETED););
NoteThewatchcommandmaybetriggeredseveraltimesformultiplechangesontherespectiveKubernetesresource.Thewatchactionwillalwayshandleonesingleeventresult.Thefirsteventtriggerisforwardedtotheactionvalidation.Allfurtherwatcheventsonthatsameresourceareignored.Thismeansthatyoumayneedmultiplewatchactionsinyourtestcaseincaseyouexpectmultiplewatcheventstobetriggered.
Kubernetesmessaging
WehaveseenhowtoaccesstheKubernetesremoteRESTAPIbyusingspecialCitrustestactionsinouttest.Asanalternativetothatwecanalsousemoregenericsend/receiveactionsinCitrusforaccessingtheKubernetesAPI.Wedemonstratethiswithasimpleexample:
XMLDSL
CitrusReferenceGuide
375Kubernetes
<testcasename="KubernetesSendReceiveIT"><actions><sendendpoint="k8sClient"><message><data>"command":"info"</data></message></send>
<receiveendpoint="k8sClient"><messagetype="json"><data>"command":"info","result":"clientVersion":"1.4.27","apiVersion":"v1","kind":"Info","masterUrl":"$masterUrl","namespace":"test"</data></message></receive>
<echo><message>Listallpods</message></echo>
<sendendpoint="k8sClient"><message><data>"command":"list-pods"</data></message></send>
<receiveendpoint="k8sClient"><messagetype="json"><data>"command":"list-pods","result":"apiVersion":"v1","kind":"PodList","metadata":"@ignore@","items":[]</data><validatepath="$.result.items.size()"value="0"/></message></receive>
CitrusReferenceGuide
376Kubernetes
</actions></testcase>
Asyoucanseewecanusethesend/receiveactionstocallKubernetesAPIcommandsandreceivetherespectiveresultsinJsonformat,too.ThisgivesusthewellknownJsonvalidationmechanisminCitrusinordertovalidatetheresultsfromKubernetes.ThiswayyoucanloadKubernetesresourcesverifyingitsstateandproperties.OfcourseJsonPathexpressionsalsocomeinhereinordertovalidateJsonelementsexplicitly.
CitrusReferenceGuide
377Kubernetes
SSHsupportInthespiritofotherCitrusmockservices,thereissupportforsimulatinganexternalSSHserveraswellasforconnectingtoSSHserversasaclientduringthetestexecution.CitrustranslatesSSHrequestsandresponsestosimpleXMLdocumentsforbettervalidationwiththecommonCitrusmechanisms.
ThismeansthattheCitrustestcasedoesnotdealwithpureSSHprotocolcommands.InsteadofthisweusethepowerfulXMLvalidationcapabilitiesinCitruswhendealingwiththesimpleXMLdocumentsthatrepresenttheSSHrequest/responsedata.
Letusclarifythiswithalittleexample.OncetherealSSHserverdaemonisfiredupwithinCitrusweacceptaSSHEXECrequestforinstance.TherequestistranslatedintoaXMLmessageofthefollowingformat:
<ssh-requestxmlns="http://www.citrusframework.org/schema/ssh/message"><command>cat-|sed-e's/Hello/HelloSSH/'</command><stdin>HelloWorld</stdin></ssh-request>
ThismessagecanbevalidatedwiththeusualCitrusmechanisminareceivetestaction.Ifyoudonotknowhowtodothis,pleasereadoneofthesectionsaboutXMLmessagevalidationinthisreferenceguidefirst.NowafterhavingreceivedthisrequestmessagetherespectiveSSHresponseshouldbeprovidedasappropriateanswer.ThisisdonewithamessagesendingactiononareplyhandlerasitisknownfromsynchronoushttpmessagecommunicationinCitrusforinstance.TheSSHXMLrepresentationofaresponsemessagelookslikethis:
<ssh-responsexmlns="http://www.citrusframework.org/schema/ssh/message"><stdout>HelloSSHWorld</stdout><stderr></stderr><exit>0</exit></ssh-response>
BesidessimulatingafullfeaturedSSHserver,CitrusalsoprovidesSSHclientfunctionality.Thisclientusesthesamerequestmessagepattern,whichistranslatedintoarealSSHcalltoanSSHserver.TheSSHresponsereceivedisalsotranslatedintoaXMLmessageasshownabovesowecanvalidateitwithknownvalidationmechanismsinCitrus.
CitrusReferenceGuide
378Ssh
SimilartotheotherCitrusmodules(http,soap),aCitrusSSHserverandclientisconfiguredinCitrusSpringapplicationcontext.ThereisadedicatedsshnamespaceavailableforallsshCitruscomponents.Thenamespacedeclarationgoesintothecontexttop-levelelementasusual:
<beans[...]xmlns:citrus-ssh="http://www.citrusframework.org/schema/ssh/config"[...]xsi:schemaLocation="[...]http://www.citrusframework.org/schema/ssh/confighttp://www.citrusframework.org/schema/ssh/config/citrus-ssh-config.xsd[...]">[...]</beans>
Both,SSHserverandclientalongwiththeirconfigurationoptionsaredescribedinthefollowingtwosections.
SSHClient
ACitrusSSHclientisusefulfortestingagainstarealSSHserver.SoCitrusisabletoinvokeSSHcommandsontheexternalserverandvalidatetheSSHresponseaccordingly.ThetestcasedoesnotdealwiththepureSSHprotocolwithinthiscommunication.TheCitrusSSHclientcomponentexpectsacustomizedXMLrepresentationandautomaticallytranslatestheserequestmessagesintoarealSSHcalltoaspecifichost.OncethesynchronousSSHresponsewasreceivedtheresultgetstranslatedbacktotheXMLresponsemessagerepresentation.OnthistranslatedresponsewecaneasilyapplythevalidationstepsbytheusualCitrusmeans.
TheSSHclientcomponentsreceiveitsconfigurationintheSpringapplicationcontextasusual.WecanusethespecialSSHmodulenamespaceforeasyconfiguration:
<citrus-ssh:clientid="sshClient"port="9072"user="roland"private-key-path="classpath:com/consol/citrus/ssh/test_user.priv"strict-host-checking="false"host="localhost"/>
TheSSHclientreceivesseveralattributes,theseare:
CitrusReferenceGuide
379Ssh
id:Ididentifyingthebeanandusedasreferencefromwithtestdescriptions.(e.g.id="sshClient")host:HosttoconnecttoforsendinganSSHExecrequest.Defaultis'localhost'(e.g.host="localhost")portPorttouse.Defaultis2222(e.g.port="9072")private-key-path:Pathtoaprivatekey,whichcanbeeitheraplainfilepathoranclassresourceifprefixedwith'classpath'(e.g.private-key-path="classpath:test_user.priv")private-key-password:Optionalpasswordfortheprivatekey(e.g.password="s!cr!t")user:UserusedforconnectingtotheSSHserver(e.g.user="roland")password:Passwordusedforpasswordbasedauthentication.Mightbecombinedwith"private-key-path"inwhichcasebothauthenticationmechanismaretried(e.g.password="ps!st)strict-host-checking:Whetherthehostkeyshouldbeverifiedbylookingitupina'known_hosts'file.Defaultisfalse(e.g.strict-host-checking="true")known-hosts-path:Pathtoaknownhostsfile.Ifprefixedwith'classpath:'thisfileislookedupasaresourceintheclasspath(e.g.known-hosts-path="/etc/ssh/known_hosts")command-timeout:TimeoutinmillisecondsforhowlongtowaitfortheSSHcommandtocomplete.Defaultis5minutes(e.g.command-timeout="300000")connection-timeout:Timeoutinmillisecondsforhowlongtoforaconnectiuontoconnect.Defaultis1minute(e.g.connection-timeout="60000")actor:Actorusedforswitchinggroupsofactions(e.g.actor="ssh-mock")
OncedefinesasclientcomponentintheSpringapplicationcontexttestcasescanreferencetheclientineverysendtestaction.
<sendendpoint="sshClient"><message><payload><ssh-requestxmlns="http://www.citrusframework.org/schema/ssh/message"><command>shutdown</command><stdin>input</stdin></ssh-request></payload></message></send>
<receiveendpoint="sshClient"><message><payload>
CitrusReferenceGuide
380Ssh
<ssh-responsexmlns="http://www.citrusframework.org/schema/ssh/message"><stdout>HelloCitrus</stdout><stderr/><exit>0</exit></ssh-response></payload></message></receive>
Asyoucanseeweuseusualsendandreceivetestactions.TheXMLSSHrepresentationhelpsustospecifytherequestandresponsedataforvalidation.ThiswayyoucancallSSHcommandsagainstanexternalSSHserverandvalidatetheresponsedata.
SSHServer
NowthatwehaveusedCitrusontheclientsidewecanalsouseCitrusSSHservermoduleinordertoprovideafullstackedSSHserverdaemon.WecanacceptSSHclientconnectionsandprovideproperresponsemessagesasananswer.
GiventheaboveSSHmodulenamespacedeclaration,addinganewSSHserverisquitesimple:
<citrus-ssh:serverid="sshServer"allowed-key-path="classpath:com/consol/citrus/ssh/test_user_pub.pem"user="roland"port="9072"auto-start="true"endpoint-adapter="sshEndpointAdapter"/>
endpoint-adapteristhehandlerwhichreceivestheSSHrequestasmessages(intherequestformatdescribedabove).Endpointadapterimplementationsarefullydescribedinhttp-serverAlladaptersdescribedtherearesupportedinSSHservermodule,too.
The<citrus-ssh:server>supportsthefollowingattributes:
SSHServerAttributes:
id:NameoftheSSHserverwhichidentifiesituniquewithintheCitrusSpringcontext(e.g.id="sshServer")host-key-path:PathtoPEMencodedkeypair(publicandprivatekey)whichisusedashostkey.Bydefault,astandard,pre-generate,fixedkeypairisused.Thepathcanbespecifiedeitherasanfilepath,or,ifprefixedwithclasspath:islooked
CitrusReferenceGuide
381Ssh
upfromwithintheclasspath.Thepaththeisrelativefromtothetop-levelpackage,sonoleadingslashshouldbeused(e.g.hist-key-path="/etc/citrus_ssh_server.pem)user:Userwhichisallowedtoconnect(e.g.user="roland")allowed-key-path:PathtoaSSHpublickeystoredinPEMformat.Thesearethekeys,whichareallowedtoconnecttotheSSHserverwhenpublickeyauthenticationisused.Itsevesthesamepurposeasauthorized_keysforstandardSSHinstallations.Thepathcanbespecifiedeitherasanfilepath,or,ifprefixedwithclasspath:islookedupfromwithintheclasspath.Thepaththeisrelativefromtothetop-levelpackage,sonoleadingslashshouldbeused(e.g.allowed-key-path="classpath:test_user_pub.pem)password:Passwordwhichshouldbeusedwhenpasswordauthenticationisused.Bothpublickeyauthenticationandpasswordbasedauthenticationcanbeusedtogetherinwhichcasebothmethodsaretriedinturn(e.g.password="s!cr!t")host:Hostaddress(e.g.localhost)port:Portonwhichtolisten.TheSSHserverwillbindonlocalhosttothisport(e.g.port="9072")auto-start:WhethertostartthisSSHserverautomatically.Defaultistrue.Ifsettofalse,atestactionisresponsibleforstarting/stoppingtheserver(e.g.auto-start="true")endpoint-adapter:BeanreferencetoaendpointadapterwhichprocessestheincomingSSHrequest.Themessageformatfortherequestandresponsearedescribedabove(e.g.endpoint-adapter="sshEndpointAdapter")
OncetheSSHservercomponentisaddedtotheSpringapplicationcontextwithaproperendpointadapterliketheMessageChannelforwardingadapterwecanreceiveincomingrequestsinatestcaseandprovidearesponemessagefortheclient.
<receiveendpoint="sshServer"><message><payload><ssh-requestxmlns="http://www.citrusframework.org/schema/ssh/message"><command>shutdown</command><stdin>input</stdin></ssh-request></payload></message></receive>
<sendendpoint="sshServer"><message><payload><ssh-responsexmlns="http://www.citrusframework.org/schema/ssh/message"><stdout>HelloCitrus</stdout>
CitrusReferenceGuide
382Ssh
<exit>0</exit></ssh-response></payload></message></send>
CitrusReferenceGuide
383Ssh
RMIsupportRMIstandsforRemoteMethodInvocationandisastandardwayofcallingJavamethodinterfaceswherecallerandcallee(clientandserver)arenotlocatedwithinthesameJVM.Sotheobjectpassedtothemethodasargumentaswellasthemethodreturnvaluearetransmittedoverthewire.
AsaclientCitrusisabletoconnecttosomeRMIregistrythatexposessomeremoteinterfaces.AsaserverCitrusimplementssuchaRMIregistryandhandlesincomingmethodcallswithprovidingtherespectivereturnvalue.
NoteTheRMIcomponentsinCitrusarekeptinaseparateMavenmodule.SoyoushouldcheckthatthemoduleisavailableasMavendependencyinyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-rmi</artifactId><version>2.7.1</version></dependency>
AsusualCitrusprovidesacustomizedrmiconfigurationschemathatisusedinSpringconfigurationfiles.Simplyincludethecitrus-rminamespaceintheconfigurationXMLfilesasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-rmi="http://www.citrusframework.org/schema/rmi/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/rmi/confighttp://www.citrusframework.org/schema/rmi/config/citrus-rmi-config.xsd">
[...]
</beans>
NowyouarereadytousethecustomizedHttpconfigurationelementswiththecitrus-rminamespaceprefix.
CitrusReferenceGuide
384Rmi
ReadthenextsectioninordertofindoutmoreabouttheRMImessagesupportinCitrus.
RMIclient
Ontheclientsidewewanttocalleremoteinterface.Weneedtospecifythemethodtocallaswellasallmethodarguments.Therespectivemethodreturnvalueisreceivablewithinthetestcaseforvalidation.CitrusprovidesaclientcomponentforRMIthatsendsoutserviceinvocationcalls.
<citrus-rmi:clientid="rmiClient1"host="localhost"port="1099"binding="newsService"/>
<citrus-rmi:clientid="rmiClient2"server-url="rmi://localhost:1099/newsService"/>
TheclientcomponentintheSpringapplicationcontextreceiveshostandportconfigurationofavalidRMIserviceregistry.Eitherbyspecifyingaproperserverurlorbygivinghost,portandbindingproperties.Theservicebindingisthenameoftheservicethatwewouldliketoaddressintheregistry.Nowwearereadytousethisclientreferencedbyitsidornameinatestcaseforamessagesendingaction.
XMLDSL
<sendendpoint="rmiClient"><message><payload><service-invocationxmlns="http://www.citrusframework.org/schema/rmi/message"><remote>com.consol.citrus.rmi.remote.NewsService</remote><method>getNews</method></service-invocation></payload></message></send>
JavaDSL
@CitrusTestpublicvoidrmiClientTest()send(rmiClient).message(RmiMessage.invocation(NewsService.class,"getNews"));
CitrusReferenceGuide
385Rmi
WeareusingtheusualCitrussendmessageactionreferencingthermiClientasendpoint.ThemessagepayloadisaspecialCitrusmessagethatdefinestheserviceinvocation.Wedefinetheremoteinterfaceaswellasthemethodtocall.CitrusRMIclientcomponentwillbeabletointerpretthismessagecontentandcalltheservicemethod.
Themethodreturnvalueisreceivableforvalidationusingtheverysameclientendpoint.
XMLDSL
<receiveendpoint="rmiClient"><message><payload><service-resultxmlns="http://www.citrusframework.org/schema/rmi/message"><objecttype="java.lang.String"value="ThisisnewsfromRMI!"/></service-result></payload></message></receive>
JavaDSL
@CitrusTestpublicvoidrmiClientTest()receive(rmiClient).message(RmiMessage.result("ThisisnewsfromRMI!"));
Inthesampleabovewereceivetheserviceresultandexpectajava.lang.Stringobjectreturnvalue.Thereturnvaluecontentisalsovalidatedwithintheserviceresultpayload.
Ofcoursewecanalsodealwithmethodarguments.
XMLDSL
<sendendpoint="rmiClient"><message><payload><service-invocationxmlns="http://www.citrusframework.org/schema/rmi/message"><remote>com.consol.citrus.rmi.remote.NewsService</remote><method>setNews</method><args><argvalue="Thisisbreakingnews!"/></args>
CitrusReferenceGuide
386Rmi
</service-invocation></payload></message></send>
@CitrusTestpublicvoidrmiServerTest()send(rmiClient).message(RmiMessage.invocation(NewsService.class,"setNews").argument("Thisisbreakingnews!"));
Thiscompletesthebasicremoteservicecall.Citrusinvokestheremoteinterfacemethodandvalidatesthemethodreturnvalue.Asatesteryoumightalsofaceerrorsandexceptionswhencallingtheremoteinterfacemethod.Youcancatchandasserttheseremoteexceptionsverifyingyourerrorscenario.
XMLDSL
<assertexception="java.rmi.RemoteException"><when><sendendpoint="rmiClient"><message><payload><service-invocationxmlns="http://www.citrusframework.org/schema/rmi/message"[...]</service-invocation></payload></message></send></when><assert/>
WeasserttheRemoteExceptiontobethrownwhilecallingtheremoteservicemethod.Thisishowyoucanhandlesomesortoferrorsituationwhilecallingremoteservices.InthenextsectionwewillhandleRMIcommunicationwhereCitrusprovidestheremoteinterfaces.
RMIserver
CitrusReferenceGuide
387Rmi
OntheserversideCitrusneedstoprovideremoteinterfaceswithmethodscallableforclients.ThismeansthatCitrusneedstosupportallyourremoteinterfaceswithmethodargumentsandreturnvalues.TheCitrusRMIserverisabletobindyourremoteinterfacestoaserviceregistry.AllincomingRMIclientmethodcallsareautomaticallyacceptedandthemethodargumentsareconvertedintoaCitrusXMLserviceinvocationrepresentation.TheRMImethodcallisthenpassedtotherunningtestforvalidation.
LetushavealookattheCitrusRMIservercomponentandhowyoucanaddittotheSpringapplicationcontext.
<citrus-rmi:serverid="rmiServer"host="localhost"port="1099"interface="com.consol.citrus.rmi.remote.NewsService"binding="newService"create-registry="true"auto-start="true"/>
TheRMIservercomponentusespropertiessuchashostandporttodefinetheserviceregistry.BydefaultCitruswillconnecttothisserviceregistryandbinditsremoteinterfacestoit.Withtheattributecreate-registryCitruscanalsocreatetheregistryforyou.
YouhavetogiveCitrusthefullyqualifiedremoteinterfacenamesoCitruscanbindittotheserviceregistryandhandleincomingmethodcallsproperly.Inyourtestcaseyoucanthenreceivetheincomingmethodcallsontheserverinordertoperformvalidationsteps.
XMLDSL
<receiveendpoint="rmiServer"><message><payload><service-invocationxmlns="http://www.citrusframework.org/schema/rmi/message"><remote>com.consol.citrus.rmi.remote.NewsService</remote><method>getNews</method></service-invocation></payload><header><elementname="citrus_rmi_interface"value="com.consol.citrus.rmi.remote.NewsService"<elementname="citrus_rmi_method"value="getNews"/></header></message></receive>
CitrusReferenceGuide
388Rmi
JavaDSL
@CitrusTestpublicvoidrmiServerTest()receive(rmiServer).message(RmiMessage.invocation(NewsService.class,"getNews"));
AsyoucanseeCitrusconvertstheincomingserviceinvocationtoaspecialXMLrepresentationwhichispassedasmessagepayloadtothetest.AsthisisplainXMLyoucanverifytheRMImessagecontentasusualusingCitrusvariables,functionsandvalidationmatchers.
Sincewehavereceivedthemethodcallweneedtoprovidesomereturnvaluefortheclient.AsusualwecanspecifythemethodreturnvaluewithsomeXMLrepresentation.
XMLDSL
<sendendpoint="rmiServer"><message><payload><service-resultxmlns="http://www.citrusframework.org/schema/rmi/message"><objecttype="java.lang.String"value="ThisisnewsfromRMI!"/></service-result></payload></message></send>
JavaDSL
@CitrusTestpublicvoidrmiServerTest()send(rmiServer).message(RmiMessage.result("ThisisnewsfromRMI!"));
Theserviceresultisdefinedasobjectwithatypeandvalue.TheCitrusRMIremoteinterfacemethodwillreturnthisvaluetothecallingclient.Thiswouldcompletethesuccessfulremoteserviceinvocation.Atthispointwealsohavetothinkofchoosingtoraisesomeremoteexceptionasserviceoutcome.
XMLDSL
CitrusReferenceGuide
389Rmi
<sendendpoint="rmiServer"><message><payload><service-resultxmlns="http://www.citrusframework.org/schema/rmi/message"><exception>Somethingwentwrong<exception/></service-result></payload></message></send>
JavaDSL
@CitrusTestpublicvoidrmiServerTest()send(rmiServer).message(RmiMessage.exception("Somethingwentwrong"));
IntheexampleaboveCitruswillnotreturnsomeobjectasserviceresultbutraiseajava.rmi.RemoteExceptionwithrespectiveerrormessageasspecifiedinthetestcase.Thecallingclientwillreceivetheexceptionaccordingly.
CitrusReferenceGuide
390Rmi
JMXsupportJMXisastandardJavaAPIformakingbeansaccessibletoothersintermsofmanagementandremoteconfiguration.JMXistheshorttermforJavaManagementExtensionsandisoftenusedinJEEapplicationserverstomanagebeanattributesandoperationsfromoutside(e.g.anotherJVM).AmanagedbeanserverhostsmultiplemanagedbeansforJMXaccess.RemoteconnectionstoJMXcanberealizedwithRMI(Remotemethodinvocation)capabilities.
CitrusisabletoconnecttoJMXmanagedbeansasclientandserver.AsaclientCitruscaninvokemanagedbeanoperationsandreadwritemanagedbeanattributes.AsaserverCitrusisabletoexposemanagedbeansasmbeanserver.ClientscanaccessthoseCitrusmanagedbeansandgetproperresponseobjectsasresult.DoingsoyoucanusetheJVMplatformmanagedbeanserverorsomeRMIregistryforprovidingremoteaccess.
NoteTheJMXcomponentsinCitrusarekeptinaseparateMavenmodule.SoyoushouldcheckthatthemoduleisavailableasMavendependencyinyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-jmx</artifactId><version>2.7.1</version></dependency>
AsusualCitrusprovidesacustomizedjmxconfigurationschemathatisusedinSpringconfigurationfiles.Simplyincludethecitrus-jmxnamespaceintheconfigurationXMLfilesasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus="http://www.citrusframework.org/schema/config"xmlns:citrus-jmx="http://www.citrusframework.org/schema/jmx/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/confighttp://www.citrusframework.org/schema/config/citrus-config.xsdhttp://www.citrusframework.org/schema/jmx/confighttp://www.citrusframework.org/schema/jmx/config/citrus-jmx-config.xsd">
[...]
CitrusReferenceGuide
391Jmx
</beans>
NowyouarereadytousethecustomizedHttpconfigurationelementswiththecitrus-jmxnamespaceprefix.
NextsectionsdescribetheJMXmessagesupportinCitrusinmoredetail.
JMXclient
Ontheclientsidewewanttocallsomemanagedbeanbyeitheraccessingmanagedattributeswithread/writeorbyinvokingamanagedbeanoperation.ForpropermbeanserverconnectivityweshouldspecifyaclientcomponentforJMXthatsendsoutmbeaninvocationcalls.
<citrus-jmx:clientid="jmxClient"server-url="platform"/>
Theclientcomponentspecifiesthetargetmanagedbeanserverthatwewanttoconnectto.InthisexampleweareusingtheJVMplatformmbeanserver.ThismeansweareabletoaccessallJVMmanagedbeanssuchasMemory,ThreadingandLogging.Inadditiontothatwecanaccessallcustommanagedbeansthatwereexposedtotheplatformmbeanserver.
InmostcasesyoumaywanttoaccessmanagedbeansonadifferentJVMorapplicationserver.Soweneedsomeremoteconnectiontotheforeignmbeanserver.
<citrus-jmx:clientid="jmxClient"server-url="service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"username="user"password="s!cr!t"auto-reconnect="true"delay-on-reconnect="5000"/>
InthisexampleaboveweconnecttoaremotembeanserverviaRMIusingthedefaultRMIregistrylocalhost:1099andtheservicenamejmxrmi.Citrusisabletohandledifferentremotetransportprotocols.Justdefinethoseintheserver-url.
Nowthatwehavesetuptheclientcomponentwecanuseitinatestcasetoaccessamanagedbean.
XMLDSL
CitrusReferenceGuide
392Jmx
<sendendpoint="jmxClient"><message><payload><mbean-invocationxmlns="http://www.citrusframework.org/schema/jmx/message"><mbean>java.lang:type=Memory</mbean><attributename="Verbose"/></mbean-invocation></payload></message></send>
JavaDSL
@CitrusTestpublicvoidjmxClientTest()send(jmxClient).message(JmxMessage.invocation("java.lang:type=Memory").attribute("Verbose"));
Asyoucanseewejustusedanormalsendactionreferencingthejmxclientcomponentthatwehavejustadded.ThemessagepayloadisaXMLrepresentationofthemanagedbeanaccess.ThisisaspecialCitrusXMLrepresentation.CitruswillconvertthisXMLpayloadtotheactuelmanagedbeanaccess.Intheexampleabovewetrytoaccessamanagedbeanwithobjectnamejava.lang:type=Memory.TheobjectnameisdefinedinJMXspecificationandconsistsofakeyjava.lang:typeandavalueMemory.Soweidentifythemanagedbeanontheserverbyitstype.
NowthatwehaveaccesstothemanagedbeanwecanreaditsmanagedattributessuchasVerbose.ThisisabooleantypeattributesothembeaninvocationresultwillbearespectiveBooleanobject.Wecanvalidatethemanagedbeanattributeaccessinareceiveaction.
XMLDSL
<receiveendpoint="jmxClient"><message><payload><mbean-resultxmlns="http://www.citrusframework.org/schema/jmx/message"><objecttype="java.lang.Boolean"value="false"/></mbean-result></payload></message></receive>
CitrusReferenceGuide
393Jmx
JavaDSL
@CitrusTestpublicvoidjmxClientTest()receive(jmxClient).message(JmxMessage.result(false));
Inthesampleabovewereceivethembeanresultandexpectajava.lang.Booleanobjectreturnvalue.Thereturnvaluecontentisalsovalidatedwithinthembeanresultpayload.
Somemanagedbeanattributesmightalsobesettableforus.Sowencandefinetheattributeaccessaswriteoperationbyspecifyingavalueinthesendactionpayload.
XMLDSL
<sendendpoint="jmxClient"><message><payload><mbean-invocationxmlns="http://www.citrusframework.org/schema/jmx/message"><mbean>java.lang:type=Memory</mbean><attributename="Verbose"value="true"type="java.lang.Boolean"/></mbean-invocation></payload></message></send>
JavaDSL
@CitrusTestpublicvoidjmxClientTest()send(jmxClient).message(JmxMessage.invocation("java.lang:type=Memory").attribute("Verbose",true));
NowwehavewriteaccesstothemanagedattributeVerbose.Wedospecifythevalueanditstypejava.lang.Boolean.Thisishowwecansetattributevaluesonmanagedbeans.
Lastnotleastweareabletoaccessmanagedbeanoperations.
XMLDSL
CitrusReferenceGuide
394Jmx
<sendendpoint="jmxClient"><message><payload><mbean-invocationxmlns="http://www.citrusframework.org/schema/jmx/message"><mbean>com.consol.citrus.jmx.mbean:type=HelloBean</mbean><operationname="sayHello">>parameter>>paramtype="java.lang.String"value="HelloJMX!"/>>/parameter>>/operation></mbean-invocation></payload></message></send>
JavaDSL
@CitrusTestpublicvoidjmxClientTest()send(jmxClient).message(JmxMessage.invocation("com.consol.citrus.jmx.mbean:type=HelloBean").operation("sayHello").parameter("HelloJMX!"));
IntheexampleaboveweaccessacustommanagedbeanandinvokeitsoperationsayHello.Wearealsousingoperationparametersfortheinvocation.Thisshouldcallthemanagedbeanoperationandreturnitsresultifanyasusual.
ThiscompletesthebasicJMXmanagedbeanaccessasclient.NowwealsowanttodiscusstheserversidewereCitrusisabletoprovidemanagedbeansforothers
JMXserver
Theserversideisalwaysalittlebitmoretrickybecauseweneedtosimulatecustommanagedbeanaccessasaserver.FirstofallCitrusprovidesaservercomponentthatspecifiestheconnectionpropertiesforclientssuchastransportprotocols,portsandmbeanobjectnames.LetscreateanewserverthatacceptsincomingrequestsviaRMIonaremoteregistrylocalhost:1099.
<citrus-jmx:serverid="jmxServer"server-url="service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"<citrus-jmx:mbeans><citrus-jmx:mbeantype="com.consol.citrus.jmx.mbean.HelloBean"/><citrus-jmx:mbeantype="com.consol.citrus.jmx.mbean.NewsBean"objectDomain="com.consol.citrus.news"objectName="name=News"/>
CitrusReferenceGuide
395Jmx
</citrus-jmx:mbeans></citrus-jmx:server>
Asusualwedefineaserver-urlthatcontrolstheJMXconnectoraccesstothembeanserver.InthisexampleaboveweopenaJMXRMIconnectorforclientsusingtheregistrylocalhost:1099andtheservicenamejmxrmiBydefaultCitruswillnotattempttocreatethisregistryautomaticallysotheregistryhastobepresentbeforetheserverstartup.Withtheoptionalserverpropertycreate-registrysettotrueyoucanautocreatetheregistrywhentheserverstartsup.ThesepropertiesdoonlyapplywhenusingaremoteJMXconnectorserver.
Besidesusingthewholeserver-urlaspropertywecanalsoconstructtheconnectionbyhost,port,protocolandbindingproperties.
<citrus-jmx:serverid="jmxServer"host="localhost"port="1099"protocol="rmi"binding="jmxrmi"<citrus-jmx:mbeans><citrus-jmx:mbeantype="com.consol.citrus.jmx.mbean.HelloBean"/><citrus-jmx:mbeantype="com.consol.citrus.jmx.mbean.NewsBean"objectDomain="com.consol.citrus.news"objectName="name=News"/></citrus-jmx:mbeans></citrus-jmx:server>
Onlastthingtomentionisthatwecouldhavealsousedplatformasserver-urlinordertousetheJVMplatformmbeanserverinstead.
NowthatweclarifiedtheconnectivityweneedtotalkabouthowtodefinethemanagedbeansthatareavailableonourJMXmbeanserver.Thisisdoneasnestedmbeanconfigurationelements.HerethemanagedbeandefinitionsdescribethemanagedbeanwithitsobjectDomain,objectName,operationsandattributes.Themostconvenientwayofdefiningsuchmanagedbeandefinitionsistogiveabeantypewhichisthefullyqualifiedclassnameofthemanagedbean.CitruswillusethepackagenameandclassnameforproperobjectDomainandobjectNameconstruction.
Letshaveacloserlookattheirstmbeandefinitionintheexampleabove.Sothefirstmanagedbeanisdefinedbyitsclassnamecom.consol.citrus.jmx.mbean.HelloBeanandthereforeisaccessibleusingtheobjectNamecom.consol.citrus.jmx.mbean:type=HelloBean.InadditiontothatCitruswillreadthe
CitrusReferenceGuide
396Jmx
classinformationsuchasavailablemethods,gettersandsettersforconstructingaproperMBeanInfo.InthesecondmanagedbeandefinitioninourexamplewehaveusedadditionalcustomobjectDomainandobjectNamevalues.SotheNewsBeanwillbeaccessiblewithcom.consol.citrus.news:name=Newsonthemanagedbeanserver.
Thisishowwecandefinethebindingsofmanagedbeansandwhatclientsneedtosearchforwhenfindingandaccessingthemanagedbeansontheserver.WhenclientstrytofindthemanagedbeanstheyhavetouseproperobjectNamesaccordingly.ObjectNamesthatarenotdefinedontheserverwillberejectedwithmanagedbeannotfounderror.
Rightnowwehavetousethequalifiedclassnameofthemanagedbeaninthedefinition.Whathappensifwedonothaveaccesstothatmbeanclassorifthereisnotmanagedbeaninterfaceavailableatall?Citrusprovidesagenericmanagedbeanthatisabletohandleanymanagedbeaninteraction.Thegenericbeanimplementationneedstoknowthemanagedoperationsandattributesthough.Soletsdefineanewgenericmanagedbeanonourserver:
<citrus-jmx:serverid="jmxServer"server-url="service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"<citrus-jmx:mbeans><citrus-jmx:mbeanname="fooBean"objectDomain="foo.object.domain"objectName="type=FooBean"><citrus-jmx:operations><citrus-jmx:operationname="fooOperation"><citrus-jmx:parameter><citrus-jmx:paramtype="java.lang.String"/><citrus-jmx:paramtype="java.lang.Integer"/></citrus-jmx:parameter></citrus-jmx:operation><citrus-jmx:operationname="barOperation"/></citrus-jmx:operations><citrus-jmx:attributes><citrus-jmx:attributename="fooAttribute"type="java.lang.String"/><citrus-jmx:attributename="barAttribute"type="java.lang.Boolean"/></citrus-jmx:attributes></citrus-jmx:mbean></citrus-jmx:mbeans></citrus-jmx:server>
Thegenericbeandefinitionneedstodefinealloperationsandattributesthatareavailableforaccess.UptonowwearerestrictedtousingJavabasetypeswhendefiningoperationparameterandattributereturntypes.Thereisactuallynowaytodefinemore
CitrusReferenceGuide
397Jmx
complexreturntypes.NeverthelessCitrusisnowabletoexposethemanagedbeanforclientaccesswithouthavingtoknowtheactualmanagedbeanimplementation.
Nowwecanusetheservercomponentinatestcasetoreceivesomeincomingmanagedbeanaccess.
XMLDSL
<receiveendpoint="jmxServer"><message><payload><mbean-invocationxmlns="http://www.citrusframework.org/schema/jmx/message"><mbean>com.consol.citrus.jmx.mbean:type=HelloBean</mbean><operationname="sayHello">>parameter>>paramtype="java.lang.String"value="HelloJMX!"/>>/parameter></operation></mbean-invocation></payload></message></receive>
JavaDSL
@CitrusTestpublicvoidjmxServerTest()receive(jmxServer).message(JmxMessage.invocation("com.consol.citrus.jmx.mbean:type=HelloBean").operation("sayHello").parameter("HelloJMX!"));
Inthisveryfirstexampleweexpectamanagedbeanaccesstothebeancom.consol.citrus.jmx.mbean:type=HelloBean.WefurtherexpecttheoperationsayHellotobecalledwithrespectiveparametervalues.Nowwehavetodefinetheoperationresultthatwillbereturnedtothecallingclientasoperationresult.
XMLDSL
<sendendpoint="jmxServer"><message><payload><mbean-resultxmlns="http://www.citrusframework.org/schema/jmx/message"><objecttype="java.lang.String"value="HellofromJMX!"/></mbean-result>
CitrusReferenceGuide
398Jmx
</payload></message></send>
JavaDSL
@CitrusTestpublicvoidjmxServerTest()send(jmxServer).message(JmxMessage.result("HellofromJMX!"));
TheoperationreturnsaStringHellofromJMX!.Thisishowwecanexpectoperationcallsonmanagedbeans.Nowwealreadyhaveseenthatmanagedbeansalsoexposeattributes.Thenextexampleishandlingincomingattributereadaccess.
XMLDSL
<receiveendpoint="jmxServer"><message><payload><mbean-invocationxmlns="http://www.citrusframework.org/schema/jmx/message"><mbean>com.consol.citrus.news:name=News</mbean>>attributename="newsCount"/></mbean-invocation></payload></message></receive>
<sendendpoint="jmxServer"><message><payload><mbean-resultxmlns="http://www.citrusframework.org/schema/jmx/message"><objecttype="java.lang.Integer"value="100"/></mbean-result></payload></message></send>
JavaDSL
@CitrusTestpublicvoidjmxServerTest()receive(jmxServer).message(JmxMessage.invocation("com.consol.citrus.news:name=News").attribute("newsCount");
CitrusReferenceGuide
399Jmx
send(jmxServer).message(JmxMessage.result(100));
ThereceiveactionexpectsreadaccesstotheNewsBeanattributenewsCountandreturnsaresultobjectoftypejava.lang.Integer.Thiswaywecanexpectallattributeaccesstoourmanagedbeans.Writeoperationswillhaveaattributevaluespecified.
ThiscompletestheJMXservercapabilitieswithmanagedbeanaccessonoperationsandattributes.
CitrusReferenceGuide
400Jmx
CucumberBDDsupportBehaviordrivendevelopment(BDD)isbecomingmoreandmorepopularthesedays.Theideaofdefininganddescribingthesoftwarebehaviorasbasisforalltestsinpriortotranslatingthosefeaturedescriptionsintoexecutabletestsisaveryinterestingapproachbecauseitincludesthetechnicalexpertsaswellasthedomainexperts.WithBDDthedomainexpertscaneasilyreadandverifythetestsandthetechnicalexpertsgetadetaileddescriptionofwhatshouldhappeninthetest.
ThetestscenariodescriptionsfollowtheGherkinsyntaxwitha"Given-When-Then"structuremostofthetime.TheGherkinlanguageisbusinessreadableandwellknowninBDD.
TherearelotsofframeworksintheJavacommunitythatsupportBDDconcepts.CitrushasdedicatedsupportfortheCucumberframeworkbecauseCucumberiswellsuitedforextensionsandplugins.SowiththeCitrusandCucumberintegrationyoucanwriteGherkinsyntaxscenarioandfeaturestoriesinordertoexecutetheCitrusintegrationtestcapabilities.Asusualwehavealookatafirstexample.FirstletsseetheCitruscucumberdependencyandXMLschemadefinitions.
NoteTheCucumbercomponentsinCitrusarekeptinaseparateMavenmodule.IfnotalreadydonesoyouhavetoincludethemoduleasMavendependencytoyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-cucumber</artifactId><version>2.7.1</version></dependency>
CitrusprovidesaseparateconfigurationnamespaceandschemadefinitionforCucumberrelatedstepdefinitions.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitrusCucumberconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<spring:beansxmlns:spring="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.citrusframework.org/schema/cucumber/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/cucumber/testcase
CitrusReferenceGuide
401Cucumber
http://www.citrusframework.org/schema/cucumber/testcase/citrus-cucumber-testcase.xsd">
[...]
</spring:beans>
CucumberworkswithbothJUnitandTestNGasunittestingframework.YoucanchoosewhichframeworktousewithCucumber.SofollowingfromthatweneedaMavendependencyfortheunittestingframeworksupport:
<dependency><groupId>info.cukes</groupId><artifactId>cucumber-junit</artifactId><version>$cucumber.version</version></dependency>
InordertoenableCitrusCucumbersupportweneedtospecifyaspecialobjectfactoryintheenvironment.Themostcomfortablewaytospecifyacustomobjectfactoryistoaddthispropertytothecucumber.propertiesinclasspath.
cucumber.api.java.ObjectFactory=cucumber.runtime.java.CitrusObjectFactory
Thisspecialobjectfactorytakescareoncreatingallstepdefinitioninstances.Theobjectfactoryisabletoinject@CitrusResourceannotatedfieldsinstepclasses.Wewillseethislateronintheexamples.TheusageofthisspecialobjectfactoryismandatoryinordertocombineCitrusandCucumbercapabilities.
TheCitrusObjectFactorywillautomaticallyinitializetheCitrusworldforus.Thisincludesthedefaultcitrus-context.xmlCitrusSpringconfigurationthatisautomaticallyloadedwithintheobjectfactory.SoyoucandefineanduseCitruscomponentsasusualwithinyourtest.
AfterthesepreparationstepsyouareabletocombineCitrusandCucumberinyourproject.
Cucumberintegration
CucumberisabletoruntestswithJUnit.ThebasictestcaseisanemptytestwhichusestherespectiveJUnitrunnerimplementationfromcucumber.
CitrusReferenceGuide
402Cucumber
@RunWith(Cucumber.class)@CucumberOptions(plugin="com.consol.citrus.cucumber.CitrusReporter")publicclassMyFeatureIT
ThetestcaseaboveusestheCucumberJUnittestrunner.InadditiontothatwegivesomeoptionstotheCucumberexecution.WedefineaspecialCitrusreporterimplementation.ThisclassisresponsibleforprintingtheCitrustestsummary.ThisreporterextendsthedefaultCucumberreporterimplementationsothedefaultCucumberreportsummariesarealsoprintedtotheconsole.
ThatcompletestheJUnitclassconfiguration.NowweareabletoaddfeaturestoriesandstepdefinitionstothepackageofourtestMyFeatureIT.CucumberandCitruswillautomaticallypickupstepdefinitionsandgluecodeinthattestpackage.Soletswriteafeaturestoryecho.featurerightnexttotheMyFeatureITtestclass.
Feature:Echoservice
Scenario:SayhelloGivenMynameisCitrusWhenIsayhellototheserviceThentheserviceshouldreturn:"Hello,mynameisCitrus!"
Scenario:SaygoodbyeGivenMynameisCitrusWhenIsaygoodbyetotheserviceThentheserviceshouldreturn:"GoodbyefromCitrus!"
AsyoucanseethisstorydefinestwoscenarioswiththeGherkinGiven-When-Thensyntax.NowweneedtoaddstepdefinitionsthatgluethestorydescriptiontoCitrustestactions.LetsdothisinanewclassEchoSteps.
publicclassEchoSteps
@CitrusResourceprotectedTestDesignerdesigner;
@Given("^Mynameis(.*)$")publicvoidmy_name_is(Stringname)designer.variable("username",name);
@When("^Isayhello.*$")
CitrusReferenceGuide
403Cucumber
publicvoidsay_hello()designer.send("echoEndpoint").messageType(MessageType.PLAINTEXT).payload("Hello,mynameis$username!");
@When("^Isaygoodbye.*$")publicvoidsay_goodbye()designer.send("echoEndpoint").messageType(MessageType.PLAINTEXT).payload("Goodbyefrom$username!");
@Then("^theserviceshouldreturn:\"([^\"]*)\"$")publicvoidverify_return(finalStringbody)designer.receive("echoEndpoint").messageType(MessageType.PLAINTEXT).payload("Youjustsaid:"+body);
IfwehaveacloserlookatthestepdefinitionclassweseethatitisanormalPOJOthatusesa@CitrusResourceannotatedTestDesigner.ThetestdesignerisautomaticallyinjectedbyCitrusCucumberextension.Thisisdonebecausewehaveincludedthecitrus-cucumberdependencytoourprojectbefore.Nowwecanwrite@Given,@Whenor@Thenannotatedmethodsthatmatchthescenariodescriptionsinourstory.Cucumberwillautomaticallyfindmatchingmethodsandexecutethem.ThemethodsaddtestactionstothetestdesignerasweareusedtoitinnormalJavaDSLtests.Attheendthetestdesignerisautomaticallyexecutedwiththetestlogic.
ImportantOfcourseyoucandothedependencyinjectionwith@CitrusResourceannotationsonTestRunnerinstances,too.WhichvariationshouldsomeoneuseTestDesignerorTestRunner?Infactthereisasignificantdifferencewhenlookingatthetwoapproaches.ThedesignerwillusetheGherkinmethodstobuildthewholeCitrustestcasefirstbeforeanyactionisexecuted.TherunnerwillexecuteeachtestactionthathasbeenbuiltwithaGherkinstepimmediately.ThismeansthatadesignerapproachwillalwayscompleteallBDDstepdefinitionsbeforetakingaction.ThisdirectlyaffectstheCucumberstepreports.AllstepsareusuallymarkedassuccessfulwhenusingadesignerapproachastheCitrustestisexecutedaftertheCucumberstepshavebeenexecuted.Therunnerapproachincontrastwillfailthestepwhenthecorrespondingtestactionfails.TheCucumbertestreportswilldefinitelylookdifferent
CitrusReferenceGuide
404Cucumber
dependingonwhatapproachyouarechoosing.Allotherfunctionsstaythesameinbothapproaches.Ifyouneedtolearnmoreaboutdesignerandrunnerapproachespleasesee
IfweruntheCucumbertesttheCitrustestcaseautomaticallyperformsitsactions.ThatisafirstcombinationofCitrusandCucumberBDD.Thestorydescriptionsaretranslatedtotestactionsandweareabletorunintegrationtestswithbehaviordrivendevelopment.Great!InanextstepwewilluseXMLstepdefinitionsratherthancodingthestepsinJavaDSL.
CucumberXMLsteps
SofarwehavewrittengluecodeinJavainordertotranslateGherkinsyntaxdescriptionstotestactions.NowwewanttodothesamewithjustXMLconfiguration.TheJUnitCucumberclassshouldnotchange.WestillusetheCucumberrunnerimplementationwithsomeoptionsspecifictoCitrus:
@RunWith(Cucumber.class)@CucumberOptions(plugin="com.consol.citrus.cucumber.CitrusReporter")publicclassMyFeatureIT
Thescenariodescriptionisalsonotchanged:
Feature:Echoservice
Scenario:SayhelloGivenMynameisCitrusWhenIsayhellototheserviceThentheserviceshouldreturn:"Hello,mynameisCitrus!"
Scenario:SaygoodbyeGivenMynameisCitrusWhenIsaygoodbyetotheserviceThentheserviceshouldreturn:"GoodbyefromCitrus!"
Inthefeaturepackagemy.company.featuresweaddanewXMLfileEchoSteps.xmlthatholdsthenewXMLstepdefinitions:
<?xmlversion="1.0"encoding="UTF-8"?><spring:beansxmlns:citrus="http://www.citrusframework.org/schema/testcase"
CitrusReferenceGuide
405Cucumber
xmlns:spring="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.citrusframework.org/schema/cucumber/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/cucumber/testcasehttp://www.citrusframework.org/schema/cucumber/testcase/citrus-cucumber-testcase.xsd"
<stepgiven="^Mynameis(.*)$"parameter-names="username"><citrus:create-variables><citrus:variablename="username"value="$username"/></citrus:create-variables></step>
<stepwhen="^Isayhello.*$"><citrus:sendendpoint="echoEndpoint"><citrus:messagetype="plaintext"><citrus:data>Hello,mynameis$username!</citrus:data></citrus:message></citrus:send></step>
<stepwhen="^Isaygoodbye.*$"><citrus:sendendpoint="echoEndpoint"><citrus:messagetype="plaintext"><citrus:data>Goodbyefrom$username!</citrus:data></citrus:message></citrus:send></step>
<stepthen="^theserviceshouldreturn:"([^"]*)"$"parameter-names="body"><citrus:receiveendpoint="echoEndpoint"><citrus:messagetype="plaintext"><citrus:data>Youjustsaid:$body</citrus:data></citrus:message></citrus:receive></step>
</spring:beans>
TheabovestepsdefinitioniswritteninpureXML.CitruswillautomaticallyreadthestepdefinitionandaddthosetotheCucumberruntime.Followingfromthatthestepdefinitionsareexecutedwhenmatchingtothefeaturestory.TheXMLstepfilesfollowanamingconvention.Citruswilllookforallfileslocatedinthefeaturepackagewithnamepattern**/.Steps.xml**andloadthosedefinitionswhenCucumberstartsup.
CitrusReferenceGuide
406Cucumber
TheXMLstepsareabletoreceiveparametersfromtheGherkinregexpmatcher.Theparametersarepassedtothestepastestvariable.Theparameternamesgetdeclaredintheoptionalattributeparameter-names.Inthestepdefinitionactionsyoucanusetheparameternamesastestvariables.
NoteThetestvariablesarevisibleinallupcomingsteps,too.Thisisbecausethetestvariablesareglobalbydefault.Ifyouneedtosetlocalstateforastepdefinitionyoucanuseanotherattributeglobal-contextandsetittofalseinthestepdefinition.Thiswayalltestvariablesandparametersareonlyvisibleinthestepdefinition.Otherstepswillnotseethetestvariables.
NoteAnothernotablethingistheXMLescapingofreservedcharactersinthepatterndefinition.Youcanseethatinthelaststepwherethethenattributeisescapingquotationcharacters.
then="^theserviceshouldreturn:"([^"]*)"$"
WehavetodothisbecauseotherwisethequotationcharacterswillinterferewiththeXMLsyntaxintheattribute.
ThiscompletesthedescriptionofhowtoaddXMLstepdefinitionstothecucumberBDDtests.Inanextsectionwewillusepredefinedstepsforsendingandreceivingmessages.
CucumberSpringsupport
CucumberprovidessupportforSpringdependencyinjectioninstepdefinitionclasses.TheCucumberSpringcapabilitiesareincludedinaseparatemodule.Sowefirstofallwehavetoaddthisdependencytoourproject:
<dependency><groupId>info.cukes</groupId><artifactId>cucumber-spring</artifactId><version>$cucumber.version</version></dependency>
TheCitrusCucumberextensionhastohandlethingsdifferentwhenCucumberSpringsupportisenabled.ThereforeweuseanotherobjectfactoryimplementationthatalsosupportCucumberSpringfeatures.Changetheobjectfactorypropertyincucumber.propertiestothefollowing:
CitrusReferenceGuide
407Cucumber
cucumber.api.java.ObjectFactory=cucumber.runtime.java.spring.CitrusSpringObjectFactory
Nowwearereadytoadd@AutowiredSpringbeandependenyinjectiontostepdefinitionclasses:
@ContextConfiguration(classes=CitrusSpringConfig.class)publicclassEchoSteps@AutowiredprivateEndpointechoEndpoint;
@CitrusResourceprotectedTestDesignerdesigner;
@Given("^Mynameis(.*)$")publicvoidmy_name_is(Stringname)designer.variable("username",name);
@When("^Isayhello.*$")publicvoidsay_hello()designer.send(echoEndpoint).messageType(MessageType.PLAINTEXT).payload("Hello,mynameis$username!");
@When("^Isaygoodbye.*$")publicvoidsay_goodbye()designer.send(echoEndpoint).messageType(MessageType.PLAINTEXT).payload("Goodbyefrom$username!");
@Then("^theserviceshouldreturn:\"([^\"]*)\"$")publicvoidverify_return(finalStringbody)designer.receive(echoEndpoint).messageType(MessageType.PLAINTEXT).payload("Youjustsaid:"+body);
AsyoucanseeweusedSpringautowiringmechanismfortheechoEndpointfieldinthestepdefinition.Alsobesuretodefinethe@ContextConfigurationannotationonthestepdefinition.TheCucumberSpringsupportloadstheSpringapplicationcontextandtakescareondependencyinjection.WeusetheCitrusCitrusSpringConfigJavaconfigurationbecausethisisthemainentranceforCitrustestcases.Youcanadd
CitrusReferenceGuide
408Cucumber
custombeansandfurtherSpringrelatedconfigurationtothisSpringapplicationcontext.IfyouwanttoaddmorebeansforautowiringdosointheCitrusSpringconfiguration.Usuallythisisthedefaultcitrus-context.xmlwhichisautomaticallyloaded.
OfcourseyoucanalsouseacustomJavaSpringconfigurationclasshere.ButbesuretoalwaysimporttheCitrusSpringJavaconfigurationclasses,too.OtherwiseyouwillnotbeabletoexecutetheCitrusintegrationtestcapabilities.
Asusualweareabletouse@CitrusResourceannotatedTestDesignerfieldsforbuildingtheCitrusintegrationtestlogic.WiththisextensionyoucanusethefullSpringtestingpowerinyourtestsinparticulardependencyinjectionandalsotransactionmanagementfordatapersistancetests.
Citrusstepdefinitions
Citrusprovidessomeoutoftheboxpredefinedstepsfortypicalintegrationtestscenarios.Thesestepsarereadytouseinscenarioorfeaturestories.Youcanbasicallydefinesendandreceiveoperations.AsthesestepsarepredefinedinCitrusyoujustneedtowritefeaturestories.Thestepdefinitionswithgluetotestactionsarehandledautomatically.
Ifyouwanttoenablepredefinedstepssupportinyourtestyouneedtoincludethegluecodepackageinyourtestclasslikethis:
@RunWith(Cucumber.class)@CucumberOptions(glue="com.consol.citrus.cucumber.step.designer.core",plugin="com.consol.citrus.cucumber.CitrusReporter")publicclassMyFeatureIT
Insteadofwritingthegluecodeonourowninstepdefinitionclassesweincludethegluepackagecom.consol.citrus.cucumber.step.designer.core.ThisautomaticallyloadsallCitrusgluestepdefinitionsinthispackage.OnceyouhavedonethisyoucanusepredefinedstepsthataddCitrustestlogicwithouthavingtowriteanygluecodeinJavastepdefinitions.
OfcourseyoucanalsochoosetoincludetheTestRunnerstepdefinitionsbychoosingthegluepackagecom.consol.citrus.cucumber.step.runner.core.
@RunWith(Cucumber.class)
CitrusReferenceGuide
409Cucumber
@CucumberOptions(glue="com.consol.citrus.cucumber.step.runner.core",plugin="com.consol.citrus.cucumber.CitrusReporter")publicclassMyFeatureIT
Followingbasicstepdefinitionsareincludedinthispackage:
Givenvariable[name]is"[value]"Givenvariables|[name1]|[value1]||[name2]|[value2]|
When<[endpoint-name]>sends"[message-payload]"Then<[endpoint-name]>shouldreceive(message-type)"[message-payload]"
When<[endpoint-name]>sends"""[message-payload]"""Then<[endpoint-name]>shouldreceive(message-type)"""[message-payload]"""
When<[endpoint-name]>receives(message-type)"[message-payload]"Then<[endpoint-name]>shouldsend"[message-payload]"
When<[endpoint-name]>receives(message-type)"""[message-payload]"""Then<[endpoint-name]>shouldsend"""[message-payload]"""
Onceagainitshouldbesaidthatthestepdefinitionsincludedinthispackageareloadedautomaticallyasgluecode.SoyoucanstarttowritefeaturestoriesinGherkinsyntaxthattriggerthepredefinedsteps.
Thereareseveraldefaultstepdefinitionsfordifferentaspectsofintegrationtesting.PleaseseethefollowingpackagesthatdefinedefaultstepsinCitrus:
Testdesignerpackages
com.consol.citrus.cucumber.step.designer.core
CitrusReferenceGuide
410Cucumber
com.consol.citrus.cucumber.step.designer.httpcom.consol.citrus.cucumber.step.designer.dockercom.consol.citrus.cucumber.step.designer.selenium
Testrunnerpackages
com.consol.citrus.cucumber.step.runner.corecom.consol.citrus.cucumber.step.runner.httpcom.consol.citrus.cucumber.step.runner.dockercom.consol.citrus.cucumber.step.runner.selenium
InthefollowingsectionswehaveacloserlookatallpredefinedCitrusstepsandhowtheywork.
Variablesteps
AsyoualreadyknowCitrusisabletoworkwithtestvariablesthatholdimportantinformationduringatestsuchasidentifiersanddynamicvalues.ThepredefinedstepdefinitionsinCitrusareabletocreatenewtestvariables.
GivenvariablemessageTextis"Hello"
Thesyntaxofthispredefinedstepisprettyselfdescribing.Thestepinstructionfollowsthepattern:
Givenvariable[name]is"[value]"
Ifyoukeepthissyntaxinyourfeaturestorythepredefinedstepisactivatedforcreatinganewvariable.WealwaysusetheGivensteptocreatenewvariables.
Scenario:CreateVariablesGivenvariablemessageTextis"Hello"AndvariableoperationHeaderis"sayHello"
SowecanusetheAndkeywordtocreatemorethanonevariable.Evenmorecomfortableistheusageofdatatables:
Givenvariables|hello|Isayhello||goodbye|Isaygoodbye|
CitrusReferenceGuide
411Cucumber
Thisdatatablewillcreatethetestvariableforeachrow.ThisishowyoucaneasilycreatenewvariablesinyourCitrustest.Asusualthevariablesarereferencedinmessagepayloadsandheadersasplaceholdersfordynamicallyaddingcontent.
AddingvariablesisusuallydonewithinaScenarioblockinyourfeaturestory.ThismeansthatthetestvariableisusedinthisscenariowhichisexactlyoneCitrustestcase.CucumberBDDalsodefinesaBackgroundblockattheverybeginningofyourFeature.Wecanalsoplacevariablesinhere.ThismeansthatCucumberwillexecutethesestepsforallupcomingscenarios.Thetestvariableissotospeakglobalforthisfeaturestory.
Feature:Variables
Background:GivenvariablemessageTextis"Hello"
Scenario:DosomethingScenario:Dosomethingelse
ThatcompletesthevariablestepdefinitionsinCitrus.
Messagingsteps
IntheprevioussectionwehavelearnedhowtouseafirstpredefinedCitrusstep.NowwewanttocovermessagingstepsforsendingandreceivingmessagesinCitrus.Asusualwithpredefinedstepsyoudonotneedtowriteanygluecodeforthestepstotakeaction.ThestepsarealreadyincludedinCitrusjustusetheminyourfeaturestories.
Feature:Messagingfeatures
Background:GivenvariablemessageTextis"Hello"
Scenario:SendandreceiveplaintextWhen<echoEndpoint>sends"$messageText"Then<echoEndpoint>shouldreceiveplaintext"Youjustsaid:$messageText"
Ofcourseweneedtofollowthepredefinedsyntaxwhenwritingfeaturestoriesinordertotriggerapredefinedstep.Let'shaveacloserlookatthispredefinedsyntaxbyfurtherdescribingtheaboveexample.
CitrusReferenceGuide
412Cucumber
FirstofallwedefineanewtestvariablewithGivenvariablemessageTextis"Hello".ThistellsCitrustocreateanewtestvariablenamedmessageTextwithrespectivevalue.Wecandothesameforsendingandreceivingmessageslikedoneinourtestscenario:
When<[endpoint-name]>sends"[message-payload]"
Thestepdefinitionrequirestheendpointcomponentnameandamessagepayload.ThepredefinedstepwillautomaticallyconfigureasendtestactionintheCitrustestasresult.
Then<[endpoint-name]>shouldreceive(message-type)"[message-payload]"
Thepredefinedreceivestepalsorequirestheendpoint-nameandmessage-payload.Asoptionalparameteryoucandefinethemessage-type.ThisisrequiredwhensendingmessagepayloadsotherthanXML.
ThiswayyoucanwriteCitrustestswithjustwritingfeaturestoriesinGherkinsyntax.Uptonowwehaveusedprettysimplemessagepayloadsinonsingleline.Ofcoursewecanalsousemultilinepayloadsinthestories:
Feature:Messagingfeatures
Background:GivenvariablemessageTextis"Hello"
Scenario:SendandreceiveWhen<echoEndpoint>sends"""<message><text>$messageText</text></message>"""Then<echoEndpoint>shouldreceive"""<message><text>$messageText</text></message>"""
AsyoucanseeweareabletousethesendandreceivestepswithmultilineXMLmessagepayloaddata.
Namedmessages
CitrusReferenceGuide
413Cucumber
IntheprevioussectionwehavelearnedhowtouseCitruspredefinedstepdefinitionsforsendandreceiveoperations.Themessagepayloadhasbeenaddeddirectlytothestoriessofar.Butwhatiswithmessageheaderinformation?Wewanttospecifyacompletemessagewithpayloadandheader.Youcandothisbydefininganamedmessage.
Asusualwedemonstratethisinafirstexample:
Feature:Namedmessagefeature
Background:GivenmessageechoRequestAnd<echoRequest>payloadis"HimynameisCitrus!"And<echoRequest>headeroperationis"sayHello"
GivenmessageechoResponseAnd<echoResponse>payloadis"Hi,Citrushowareyoudoingtoday?"And<echoResponse>headeroperationis"sayHello"
Scenario:SendandreceiveWhen<echoEndpoint>sendsmessage<echoRequest>Then<echoEndpoint>shouldreceivemessage<echoResponse>
IntheBackgroundsectionweintroducenamedmessagesechoRequestandechoResponse.Thismakesuseofthenewpredefinedstepforaddingnamedmessage:
Givenmessage[message-name]
Oncethemessageisintroducedwithitsnamewecanusethemessageinfurtherconfigurationsteps.Youcanaddpayloadinformationandyoucanaddmultipleheaderstothemessage.Thenamedmessagethenisreferencedinsendandreceivestepsasfollows:
When<[endpoint-name]>sendsmessage<[message-name]>Then<[endpoint-name]>shouldreceivemessage<[message-name]>
ThestepsreferenceamessagebyitsnameechoRequestandechoResponse.
Asyoucanseethenamedmessagesareusedtodefinecompletemessageswithpayloadandheaderinformation.Ofcoursethenamedmessagescanbereferencedinmanyscenariosandsteps.Alsowithusageoftestvariablesinpayloadandheaderyou
CitrusReferenceGuide
414Cucumber
candynmaicallyadjustthosemessagesineachstep.
Messagecreatorsteps
Intheprevioussectionwehavelearnedhowtousenamedmessagesaspredefinedstep.Thenamedmessagehasbeendefineddirectlyinthestoriessofar.ThemessagecreatorconceptmovesthistasktosomeJavaPOJO.Thiswayyouareabletoconstructmorecomplicatedmessagesforreuseinseveralscenariosandfeaturestories.
Asusualwedemonstratethisinafirstexample:
Feature:Messagecreatorfeatures
Background:Givenmessagecreatorcom.consol.citrus.EchoMessageCreatorAndvariablemessageTextis"Hello"Andvariableoperationis"sayHello"
Scenario:SendandreceiveWhen<echoEndpoint>sendsmessage<echoRequest>Then<echoEndpoint>shouldreceivemessage<echoResponse>
IntheBackgroundsectionweintroduceamessagecreatorEchoMessageCreatorinpackagecom.consol.citrus.Thismakesuseofthenewpredefinedstepforaddingmessagecreatorstothetest:
Givenmessagecreator[message-creator-name]
ThemessagecreatornamemustbethefullyqualifiedJavaclassnamewithpackageinformation.Oncethisisdonewecanusenamedmessagesinthesendandreceiveoperations:
When<[endpoint-name]>sendsmessage<[message-name]>Then<[endpoint-name]>shouldreceivemessage<[message-name]>
ThestepsreferenceamessagebyitsnameechoRequestandechoResponse.NowletshavealookatthemessagecreatorEchoMessageCreatorimplementationinordertoseehowthiscorrelatestoarealmessage.
publicclassEchoMessageCreator@MessageCreator("echoRequest")publicMessagecreateEchoRequest()
CitrusReferenceGuide
415Cucumber
returnnewDefaultMessage(""+"$messageText"+"").setHeader("operation","$operation");
@MessageCreator("echoResponse")publicMessagecreateEchoResponse()returnnewDefaultMessage(""+"$messageText"+"").setHeader("operation","$operation");
AsyoucanseethemessagecreatorisaPOJOJavaclassthatdefinesoneormoremethodsthatareannotatedwith@MessageCreatorannotation.Theannotationrequiresamessagename.ThisishowCitruswillcorrelatemessagenamesinfeaturestoriestomessagecreatormethods.Themessagereturnedistheusedforthesendandreceiveoperationsinthetest.Themessagecreatorisreusableaccrossmultiplefeaturestoriesandscenarios.Inadditiontothatthecreatorisabletoconstructmessagesinamorepowerfulway.Forinstancethemessagepayloadcouldbeloadedfromfilesystemresources.
Echosteps
AnotherpredefinedstepdefinitioninCitrusisusedtoaddaechotestaction.Youcanusethefollowingstepinyourfeaturescenarios:
Feature:Echofeatures
Scenario:EchomessagesGivenvariablefoois"bar"Thenecho"Variablefoo=$foo"Thenecho"Todayiscitrus:currentDate()"
Thestepdefinitionrequiresfollowingpattern:
Thenecho"[message]"
Sleepsteps
Youcanaddsleeptestactionstothefeaturescenarios:
CitrusReferenceGuide
416Cucumber
Feature:Sleepfeatures
Scenario:SleepdefaulttimeThensleep
Scenario:SleepmillisecondstimeThensleep200ms
Thestepdefinitionrequiresoneofthefollowingpatterns:
ThensleepThensleep[time]ms
ThisaddsanewsleeptestactiontotheCitrustest.
Httpsteps
TheHttpstepsarespeciallydesignedforHttpclient-servercommunication.YoucanusethesestepsbyaddingfollowingpackagesasglueoptionsinyourCucumbertest:
com.consol.citrus.cucumber.step.(designer|runner).http
ThispackagecontainsHttpspecificstepsthatenableyoutosendandreceivemessagesviaHttpREST:
Feature:VotingHttpRESTAPI
Background:GivenURL:http://localhost:8080/rest/servicesGivenvariables|id|citrus:randomUUID()||title|DoyoulikeMondays?||options|["name":"yes","votes":0,"name":"no","votes":0]||report|true|
Scenario:ClearvotinglistWhensendDELETE/votingThenreceivestatus200OK
Scenario:GetemptyvotinglistGivenAccept:application/jsonWhensendGET/votingThenResponse:[]Andreceivestatus200OK
Scenario:Createvoting
CitrusReferenceGuide
417Cucumber
GivenRequest:""""id":"$id","title":"$title","options":$options,"report":$report"""AndContent-Type:application/jsonWhensendPOST/votingThenreceivestatus200OK
Scenario:GetvotinglistWhensendGET/votingThenvalidate$.size()is1Thenvalidate$..titleis$titleThenvalidate$..reportis$reportAndreceivestatus200OK
ThefeaturescenariosusedefaultHttpstepstosendrequestswithdifferentmethods(GET,POST,PUT,DELETE)andreceivestatusresponses(Http200OK).Pleaseexplorethedefaultstepdefinitionsintherespectivepackagetogetadetailedunderstandingonhowtousethoseinyourfeaturespecification.
Dockersteps
Dockerstepsaccesscontainersandbuildimages.BydefaultthestepstrytofindavalidaDockerClientcomponentintheSpringapplicationcontextconfiguration.Youcanusethestepsinfeaturespecificationstomanagecontainerstates.
Feature:VotingDockerinfrastructure
Scenario:CheckcontainerdeploymentstateGivendocker-client"dockerClient"Thencontainer"voting-app"shouldberunningAndcontainer"message-broker"shouldberunning
Weareabletocheckthecontainerstaterunning.AllweneedistheDockercontainernameorid.WhatelsecanwedowithinthedefaultDockersteps?Wecanbuildnewimages:
Feature:Buildimages
Scenario:Buildvotingimage
CitrusReferenceGuide
418Cucumber
Givendocker-client"dockerClient"Whenbuildimage"voting:1.0.0"fromfile"scr/main/docker/Dockerfile"Thencreatecontainer"voting-app"from"voting:1.0.0"Andcontainer"voting-app"shouldberunning
ThisishowwecanuseDockercommandsinCucumberfeaturespecificationswithCitrusdefaultstepdefinitions.AlldefaultstepdefinitionsforDockerarelocatedinpackage
com.consol.citrus.cucumber.step.(designer|runner).docker
Seleniumsteps
SeleniumisawidelyusedUIautomationframeworkwherebrowseruserinteractionsaresimulated.WecanusedefaultSeleniumstepsinthefeaturespecificationsinordertoaccessSeleniumcommandsinourtests.
Feature:Votinguserinterface
Background:GivenuserstartsbrowserAndusernavigatesto"http://localhost:8080"
Scenario:WelcomepageThenpageshoulddisplaylinkwithlink-text="Runapplication"
Scenario:StartapplicationWhenuserclickslinkwithlink-text="Runapplication"Andsleep500msThenpageshoulddisplayheadingwithtag-name="h1"having|text|Votinglist|
Andpageshoulddisplaylinkwithlink-text="Novotingfound"Andpageshoulddisplayformwithid="new-voting"having|tag-name|form||attribute|method="post"|
Scenario:AddvotingGivenusernavigatesto"http://localhost:8080/voting"Whenusersetstext"Doyoulikeburgers?"toinputwithid="title"Anduserclicksbuttonwithid="submitNew"Andsleep500msThenpageshoulddisplayelementwithlink-text="Doyoulikeburgers?"
CitrusReferenceGuide
419Cucumber
WiththepredefinedCucumberstepsforSeleniumweareabletointeractwiththebrowser.Forinstancewecanclickbuttons,verifypageobjectsandnavigatetodifferentpages.
AlltheseSeleniumstepsarelocatedinpackage:
com.consol.citrus.cucumber.step.(designer|runner).selenium
TheSeleniumbrowserisautomaticallypickedfromtheSpringbeanapplicationcontextconfigurationinCitrus.HereyoucandecidewhichSeleniumWebDrivertouseduringthetests.Alsoyoucaninstantiatewebpageinstancesandcallpageactionsandvalidationsteps:
publicclassVotingListPageimplementsWebPage,PageValidator<VotingListPage>
@FindBy(tagName="h1")privateWebElementheading;
@FindBy(id="new-voting")privateWebElementnewVotingForm;
/***Submitsnewvoting.*@paramtitle*@paramoptions*/publicvoidsubmit(Stringtitle,Stringoptions)newVotingForm.findElement(By.id("title")).sendKeys(title);if(StringUtils.hasText(options))newVotingForm.findElement(By.id("options")).sendKeys(options.replaceAll(":","\n"
newVotingForm.submit();
@Overridepublicvoidvalidate(VotingListPagewebPage,SeleniumBrowserbrowser,TestContextcontext)Assert.assertEquals("Votinglist",heading.getText());
Thispageobjectdefineselementsandactionsonthatpagethatarecallableinourfeaturespecification.
Feature:Votingpages
CitrusReferenceGuide
420Cucumber
Background:Givenpage"welcomePage"com.consol.citrus.demo.voting.selenium.pages.WelcomePageGivenpage"votingListPage"com.consol.citrus.demo.voting.selenium.pages.VotingListPage
Scenario:WelcomepageWhenuserstartsbrowserAndusernavigatesto"http://localhost:8080"ThenpagewelcomePageshouldvalidate
Scenario:StartapplicationWhenusernavigatesto"http://localhost:8080"AndpagewelcomePageperformsstartAppAndsleep500msThenpagevotingListPageshouldvalidate
Scenario:AddvotingGivenusernavigatesto"http://localhost:8080/voting"WhenpagevotingListPageperformssubmitwitharguments|Doyoulikepizza?|Andsleep500msThenpageshoulddisplayelementwithlink-text="Doyoulikepizza?"AndpagevotingListPageshouldvalidate
Scenario:AddvotingwithoptionsGivenusernavigatesto"http://localhost:8080/voting"WhenpagevotingListPageperformssubmitwitharguments|Whatisyourfavoritecolor?||red:green:blue|Andsleep500msThenpageshoulddisplayelementwithlink-text="Whatisyourfavoritecolor?"AndpagevotingListPageshouldvalidate
Thepageobjectsgetinstantiatedanddependencyinjectionmakessurethatwebelementsandotherresourcesarepassedtothepageobject.Thenactionmethodcanperformaswellasvalidationtaskscanvalidatethepagestate.
CitrusReferenceGuide
421Cucumber
ZookeepersupportCitrusprovidesconfigurationcomponentsandtestactionsforinteractingwithZookeeper.TheCitrusZookeeperclientcomponentexecutescommandslikecreate-node,checknode-exists,delete-node,getnode-dataorsetnode-data.AsauseryoucanexecuteZookeepercommandsaspartofaCitrustestandvalidatepossiblecommandresults.
NoteTheZookeepertestcomponentsinCitrusarekeptinaseparateMavenmodule.IfnotalreadydonesoyouhavetoincludethemoduleasMavendependencytoyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-zookeeper</artifactId><version>2.7.1</version></dependency>
Citrusprovidesa"citrus-zookeeper"configurationnamespaceandschemadefinitionforZookeeperrelatedcomponentsandactions.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitruszookeeperconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus-zookeeper="http://www.citrusframework.org/schema/zookeeper/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/zookeeper/confighttp://www.citrusframework.org/schema/zookeeper/config/citrus-zookeeper-config.xsd">
[...]
</beans>
AfterthatyouareabletousecustomizedCitrusXMLelementsinordertodefinetheSpringbeans.
CitrusReferenceGuide
422Zookeeper
Zookeeperclient
BeforeyoucaninteractwithaZookeeperserveryouhavetoconfiguretheZookeeperclient.Asampleconfigurationisprovidedbelowdescribingtheconfigurationoptionsavailable:
<citrus-zookeeper:clientid="zookeeperClient"url="http://localhost:21118"timeout="2000"/>
ThisisatypicalclientconfigurationforconnectingtoaZookeeperserver.Nowyouareabletoexecuteseveralcommands.ThesecommandswillbesenttotheZookeeperserverforexecution.
Zookeepercommands
SeebelowallavailableZookeepercommandsthataCitrusclientisabletoexecute.
info:Retrievesthecurrentstateoftheclientconnectioncreate:CreatesaznodeinaspecifiedpathoftheZooKeepernamespacedelete:DeletesaznodefromaspecifiedpathoftheZooKeepernamespaceexists:Checksifaznodeexistsinthepathchildren:Getsalistofchildrenofaznodeget:Getsthedataassociatedwithaznodeset:Sets/writesdataintothedatafieldofaznode
BeforeweseesomeofthesecommandsinactionwehavetoaddanewtestnamespacetoourtestcasewhenusingtheXMLDSL.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:zookeeper="http://www.citrusframework.org/schema/zookeeper/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/zookeeper/testcasehttp://www.citrusframework.org/schema/zookeeper/testcase/citrus-zookeeper-testcase.xsd"
[...]
</beans>
CitrusReferenceGuide
423Zookeeper
WeaddedtheZookeepernamespacewithprefixzookeeper:sonowwecanstarttoaddspecialtestactionstothetestcase:
XMLDSL
<zookeeper:createzookeeper-client="zookeeperClient"path="/$randomString"acl="OPEN_ACL_UNSAFE"<zookeeper:data>foo</zookeeper:data><zookeeper:expect><zookeeper:result><![CDATA["responseData":"path":"/$randomString"]]></zookeeper:result></zookeeper:expect></zookeeper:create>
<zookeeper:getzookeeper-client="zookeeperClient"path="/$randomString"><zookeeper:expect><zookeeper:result><![CDATA["responseData":"data":"foo"]]></zookeeper:result></zookeeper:expect></zookeeper:getData>
<zookeeper:setzookeeper-client="zookeeperClient"path="/$randomString"><zookeeper:data>bar</zookeeper:data></zookeeper:setData>
WhenusingtheJavaDSLwecandirectlyconfigurethecommandswithafluentAPI.
JavaDSLdesignerandrunner
@CitrusTestpublicvoidtestZookeeper()variable("randomString","citrus:randomString(10)");
zookeeper().create("/$randomString","foo")
CitrusReferenceGuide
424Zookeeper
.acl("OPEN_ACL_UNSAFE").mode("PERSISTENT").validateCommandResult(newCommandResultCallback<ZooResponse>()@OverridepublicvoiddoWithCommandResult(ZooResponseresult,TestContextcontext)Assert.assertEquals(result.getResponseData().get("path"),context.replaceDynamicContentInString("/$randomString")););
zookeeper().get("/$randomString").validateCommandResult(newCommandResultCallback<ZooResponse>()@OverridepublicvoiddoWithCommandResult(ZooResponseresult,TestContextcontext)Assert.assertEquals(result.getResponseData().get("version"),0););
zookeeper().set("/$randomString","bar");
TheexamplesabovecreateanewznodeinZookeeperusingarandomStringaspath.WecangetandsetthedatawithexpectingandvalidatingtheresultoftheZookeeperserver.ThisisbasicallytheideaofintegratingZookepperoperationstoaCitrustest.ThisopensthegatetomanageZookeeperrelatedentitieswithinaCitrustest.WecanmanipulateandvalidatetheznodesontheZookeeperinstance.
Zookeeperkeepsitsnodesinahierarchicalstorage.Thismeansaznodecanhavechildrenandwecanaddandremovethose.InCitrusyoucangetallchildrenofaznodeandmanagethosewithinthetest:
XMLDSL
<zookeeper:createzookeeper-client="zookeeperClient"path="/$randomString/child1"acl="OPEN_ACL_UNSAFE"<zookeeper:data></zookeeper:data><zookeeper:expect><zookeeper:result><![CDATA["responseData":"path":"/$randomString/child1"]]></zookeeper:result></zookeeper:expect>
CitrusReferenceGuide
425Zookeeper
</zookeeper:create>
<zookeeper:createzookeeper-client="zookeeperClient"path="/$randomString/child2"acl="OPEN_ACL_UNSAFE"<zookeeper:data></zookeeper:data><zookeeper:expect><zookeeper:result><![CDATA["responseData":"path":"/$randomString/child2"]]></zookeeper:result></zookeeper:expect></zookeeper:create>
<zookeeper:childrenzookeeper-client="zookeeperClient"path="/$randomString"><zookeeper:expect><zookeeper:result><![CDATA["responseData":"children":["child1","child2"]]]></zookeeper:result></zookeeper:expect></zookeeper:children>
JavaDSLdesignerandrunner
zookeeper().create("/$randomString/child1","").acl("OPEN_ACL_UNSAFE").mode("PERSISTENT").validateCommandResult(newCommandResultCallback<ZooResponse>()@OverridepublicvoiddoWithCommandResult(ZooResponseresult,TestContextcontext)Assert.assertEquals(result.getResponseData().get("path"),context.replaceDynamicContentInString("/$randomString/child1")););
zookeeper().create("/$randomString/child2","").acl("OPEN_ACL_UNSAFE").mode("PERSISTENT").validateCommandResult(newCommandResultCallback<ZooResponse>()
CitrusReferenceGuide
426Zookeeper
@OverridepublicvoiddoWithCommandResult(ZooResponseresult,TestContextcontext)Assert.assertEquals(result.getResponseData().get("path"),context.replaceDynamicContentInString("/$randomString/child2")););
zookeeper().children("/$randomString").validateCommandResult(newCommandResultCallback<ZooResponse>()@OverridepublicvoiddoWithCommandResult(ZooResponseresult,TestContextcontext)Assert.assertEquals(result.getResponseData().get("children").toString(),"[child1,child2]"););
CitrusReferenceGuide
427Zookeeper
SpringRestdocssupportSpringRestdocsprojecthelpstoeasilygenerateAPIdocumentationforRESTfulservices.WhilemessagesareexchangedtheRestdocslibrarygeneratesrequest/responsesnippetsandAPIdocumentation.YoucanaddtheSpringRestdocsdocumentationtotheCitrusclientcomponentsforHttpandSOAPendpoints.
NoteTheSpringRestdocssupportcomponentsinCitrusarekeptinaseparateMavenmodule.IfnotalreadydonesoyouhavetoincludethemoduleasMavendependencytoyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-restdocs</artifactId><version>2.7.1</version></dependency>
ForeasyconfigurationCitrushascreatedaseparatenamespaceandschemadefinitionforSpringRestdocsrelateddocumentation.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitrusRestdocsconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<spring:beansxmlns:spring="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.citrusframework.org/schema/cucumber/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/restdocs/confighttp://www.citrusframework.org/schema/restdocs/config/citrus-restdocs-config.xsd">
[...]
</spring:beans>
AfterthatyouareabletousecustomizedCitrusXMLelementsinordertodefinetheSpringbeans.
SpringRestdocsusingHttp
CitrusReferenceGuide
428Restdocs
FirstofallweconcentrateonaddingtheSpringRestdocsfeaturetoHttpclientcommunication.ThenextsampleconfigurationusesthenewSpringRestdocscomponentsinCitrus:
<citrus-restdocs:documentationid="restDocumentation"output-directory="test-output/generated-snippets"identifier="rest-docs/method-name"/>
Theabovecomponentaddsanewdocumentationconfiguration.Behindthescenesthecomponentcreatesanewrestdocsconfigurerandaclientinterceptor.Wecanreferencethenewrestdocscomponentincitrus-httpclientcomponentslikethis:
<citrus-http:clientid="httpClient"request-url="http://localhost:8080/test"request-method="POST"interceptors="restDocumentation"/>
TheSpringRestdocsdocumentationcomponentactsasaclientinterceptor.EverytimetheclientcomponentisusedtosendandreceiveamessagetherestdocsinterceptorwillautomaticallycreateitsAPIdocumentation.Theconfigurationidentifierattributedescribestheoutputformatrest-docs/method-namewhichresultsinafolderlayoutlikethis:
test-output|-rest-docs|-test-a|-curl-request.adoc|-http-request.adoc|-http-response.adoc|-test-b|-curl-request.adoc|-http-request.adoc|-http-response.adoc|-test-c|-curl-request.adoc|-http-request.adoc|-http-response.adoc
TheexampleaboveistheresultofthreetestcaseseachofthemperformingaclientHttprequest/responsecommunication.Eachtestmessageexchangeisdocumentedwithseparatefiles:
CitrusReferenceGuide
429Restdocs
curl-request.adoc
[source,bash]----$curl'http://localhost:8080/test'-i-XPOST-H'Accept:application/xml'-H'CustomHeaderId:123456789'-H'Content-Type:application/xml;charset=UTF-8'-H'Accept-Charset:utf-8'-d'>testRequestMessage>>text>HelloHttpServer>/text>>/testRequestMessage>'----
Thecurlfilerepresentstheclientrequestascurlcommandandcanbeseenasasampletoreproducetherequest.
http-request.adoc
[source,http,options="nowrap"]----POST/testHTTP/1.1Accept:application/xmlCustomHeaderId:123456789Content-Type:application/xml;charset=UTF-8Content-Length:118Accept-Charset:utf-8Host:localhost
>testRequestMessage>>text>HelloHttpServer>/text>>/testRequestMessage>----
Thehttp-request.adocfilerepresentsthesentmessagedatafortheclientrequest.Therespectivehttp-response.adocrepresentstheresponsethatwassenttotheclient.
http-response.adoc
[source,http,options="nowrap"]----HTTP/1.1200OKDate:Tue,07Jun201612:10:46GMTContent-Type:application/xml;charset=UTF-8Accept-Charset:utf-8Content-Length:122Server:Jetty(9.2.15.v20160210)
>testResponseMessage>>text>HelloCitrus!>/text>>/testResponseMessage>
CitrusReferenceGuide
430Restdocs
----
Nicework!WehaveautomaticallycreatedsnippetsfortheRESTfulAPIbyjustaddingtheinterceptortotheCitrusclientcomponent.SpringRestdocscomponentscanbecombinedmanually.Seethenextconfigurationthatusesthisapproach.
<citrus-restdocs:configurerid="restDocConfigurer"output-directory="test-output/generated-snippets"<citrus-restdocs:client-interceptorid="restDocClientInterceptor"identifier="rest-docs/method-name"
<util:listid="restDocInterceptors"><refbean="restDocConfigurer"/><refbean="restDocClientInterceptor"/></util:list>
<citrus-http:clientid="httpClient"request-url="http://localhost:8080/test"request-method="POST"interceptors="restDocInterceptors"/>
Whatexactlyisthedifferencetothecitrus-restdocs:documentationthatwehaveusedbefore?Ingeneralthereisnodifference.Bothconfigurationsareidenticalinitsoutcome.Whyshouldsomeoneusethesecondapproachthen?Itismoreverboseasweneedtoalsodefinealistofinterceptors.Theansweriseasy.Ifyouwanttocombinetherestdocsinterceptorswithotherclientinterceptorsinalistthenyoushouldusethemanualcombinationapproach.Wecanaddbasicauthenticationinterceptorsforinstancetothelistofinterceptorsthen.Themorecomfortablecitrus-restdocs:documentationcomponentonlysupportsexclusiverestdocsinterceptors.
SpringRestdocsusingSOAP
YoucanusetheSpringRestdocsfeaturesalsoforSOAPclientsinCitrus.ThisisacontroversyideaasSOAPendpointsaredifferenttoRESTfulconcepts.ButattheendSOAPHttpcommunicationisHttpcommunicationwithrequestandresponsemessages.Whyshouldwemissoutthefantasticdocumentationfeatureherejustbecauseofideologyreasons.
TheconceptofaddingtheSpringRestdocsdocumentationasinterceptortotheclientisstillthesame.
<citrus-restdocs:documentationid="soapDocumentation"
CitrusReferenceGuide
431Restdocs
type="soap"output-directory="test-output/generated-snippets"identifier="soap-docs/method-name"/>
Wehaveaddedatypesettingwithvaluesoap.Andthatisbasicallyallweneedtodo.NowCitrusknowsthatwewouldliketoadddocumentationforaSOAPclient:
<citrus-ws:clientid="soapClient"request-url="http://localhost:8080/test"interceptors="soapDocumentation"/>
FollowingfromthatthesoapClientisenabledtogenerateSpringRestdocsdocumentationforeachrequest/response.ThegeneratedsnippetsthendorepresenttheSOAPrequestandresponsemessages.
http-request.adoc
[source,http,options="nowrap"]----POST/testHTTP/1.1SOAPAction:"test"Accept:application/xmlCustomHeaderId:123456789Content-Type:application/xml;charset=UTF-8Content-Length:529Accept-Charset:utf-8Host:localhost
>SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">>SOAP-ENV:Header>>Operationxmlns="http://citrusframework.org/test">sayHello>/Operation>>/SOAP-ENV:Header>>SOAP-ENV:Body>>testRequestMessage>>text>HelloHttpServer>/text>>/testRequestMessage>>/SOAP-ENV:Body>>/SOAP-ENV:Envelope>----
http-response.adoc
[source,http,options="nowrap"]----HTTP/1.1200OK
CitrusReferenceGuide
432Restdocs
Date:Tue,07Jun201612:10:46GMTContent-Type:application/xml;charset=UTF-8Accept-Charset:utf-8Content-Length:612Server:Jetty(9.2.15.v20160210)
>SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">>SOAP-ENV:Header>>Operationxmlns="http://citrusframework.org/test">sayHello>/Operation>>/SOAP-ENV:Header>>SOAP-ENV:Body>>testResponseMessage>>text>HelloCitrus!>/text>>/testResponseMessage>>/SOAP-ENV:Body>>/SOAP-ENV:Envelope>----
Thefilenamesarestillusinghttp-requestandhttp-responsebutthecontentisclearlytheSOAPrequest/responsemessagedata.
SpringRestdocsinJavaDSL
HowcanweuseSpringRestdocsinJavaDSL?OfcoursewehavespecialsupportinCitrusJavaDSLfortheSpringRestdocsconfiguration,too.
JavaDSL
publicclassRestDocConfigurationITextendsTestNGCitrusTestDesigner
@AutowiredprivateTestListenerstestListeners;
privateHttpClienthttpClient;
@BeforeClasspublicvoidsetup()CitrusRestDocConfigurerrestDocConfigurer=CitrusRestDocsSupport.restDocsConfigurer(newManualRestDocumentation("target/generated-snippets"));RestDocClientInterceptorrestDocInterceptor=CitrusRestDocsSupport.restDocsInterceptor("rest-docs/method-name");
httpClient=CitrusEndpoints.http().client().requestUrl("http://localhost:8073/test").requestMethod(HttpMethod.POST).contentType("text/xml").interceptors(Arrays.asList(restDocConfigurer,restDocInterceptor)).build();
CitrusReferenceGuide
433Restdocs
testListeners.addTestListener(restDocConfigurer);
@Test@CitrusTestpublicvoidtestRestDocs()http().client(httpClient).send().post().payload("<testRequestMessage>"+"<text>HelloHttpServer</text>"+"</testRequestMessage>");
http().client(httpClient).receive().response(HttpStatus.OK).payload("<testResponseMessage>"+"<text>HelloTestFramework</text>"+"</testResponseMessage>");
ThemechanismisquitesimilartotheXMLconfiguration.WeaddtheRestdocsconfigurerandinterceptortothelistofinterceptorsfortheHttpclient.Ifwedothisallclientcommunicationisautomaticallydocumented.TheCitrusJavaDSLprovidessomeconvenientconfigurationmethodsinclassCitrusRestDocsSupportforcreatingtheconfigurerandinterceptorobjects.
NoteTheconfigurermustbeaddedtothelistoftestlisteners.Thisisamandatorystepinordertoenabletheconfigurerfordocumentationpreparationsbeforeeachtest.Otherwisewewouldnotbeabletogenerateproperdocumentation.IfyouareusingtheXMLconfigurationthisisdoneautomaticallyforyou.
CitrusReferenceGuide
434Restdocs
SeleniumsupportSeleniumisaverypopulartoolfortestinguserinterfaceswithbrowserautomation.CitrusisabletointegratewiththeSeleniumJavaAPIinordertoexecuteSeleniumcommands.
NoteTheSeleniumtestcomponentsinCitrusarekeptinaseparateMavenmodule.IfnotalreadydonesoyouhavetoincludethemoduleasMavendependencytoyourproject
<dependency><groupId>com.consol.citrus</groupId><artifactId>citrus-selenium</artifactId><version>2.7.1</version></dependency>
Citrusprovidesa"citrus-selenium"configurationnamespaceandschemadefinitionforSeleniumrelatedcomponentsandactions.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitrusSeleniumconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus-selenium="http://www.citrusframework.org/schema/selenium/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/selenium/confighttp://www.citrusframework.org/schema/selenium/config/citrus-selenium-config.xsd">
[...]
</beans>
AfterthatyouareabletousecustomizedCitrusXMLelementsinordertodefinetheSpringbeans.
Seleniumbrowser
CitrusReferenceGuide
435Selenium
Seleniumusesbrowserautomationinordertosimulatetheuserinteractwithwebapplications.YoucanconfiguretheSeleniumbrowserandwebdriverasSpringbean.
<citrus-selenium:browserid="seleniumBrowser"type="firefox"start-page="http://citrusframework.org"/>
TheSeleniumbrowsercomponentsupportsdifferentbrowsertypesforthecommonlyusedbrowsersoutinthewild.
htmlunitfirefoxsafarichromegooglechromeinternetexploreredgecustom
Htmlunitisthedefaultbrowsertypeandrepresentsaheadlessbrowserthatexecutedwithoutdisplayingthegraphicaluserinterface.IncaseyouneedatotallydifferentbrowseroryouneedtocustomizetheSeleniumwebdriveryoucanusethebrowserType="custom"incombinationwithawebdriverreference:
<citrus-selenium:browserid="mySeleniumBrowser"type="custom"web-driver="operaWebDriver"/>
<beanid="operaWebDriver"class="org.openqa.selenium.opera.OperaDriver"/>
NowCitrusisusingthecustomizedSeleniumwebdriverimplementation.
NoteWhenusingFirefoxasbrowseryoumayalsowanttosettheoptionalpropertiesfirefox-profileandversion.
<citrus-selenium:browserid="mySeleniumBrowser"type="firefox"firefox-profile="firefoxProfile"version="FIREFOX_38"start-page="http://citrusframework.org"/>
<beanid="firefoxProfile"class="org.openqa.selenium.firefox.FirefoxProfile"/>
CitrusReferenceGuide
436Selenium
NowCitrusisabletoexecuteSeleniumoperationsasauser.
Seleniumactions
WehaveseveralCitrustestactionseachrepresentingaSeleniumcommand.TheseactionscanbepartofaCitrustestcase.AsaprerequisitewehavetoenabletheSeleniumspecifictestactionsinourXMLtestasfollows:
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:selenium="http://www.citrusframework.org/schema/selenium/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/selenium/testcasehttp://www.citrusframework.org/schema/selenium/testcase/citrus-selenium-testcase.xsd"
[...]
</beans>
Weaddedaspecialseleniumnamespacewithprefixselenium:sonowwecanstarttoaddSeleniumtestactionstothetestcase:
XMLDSL
<testcasename="SeleniumCommandIT"><actions><selenium:startbrowser="webBrowser"/>
<selenium:navigatepage="http://localhost:8080"/>
<selenium:find><selenium:elementtag-name="h1"text="Welcome!"><selenium:styles><selenium:stylename="font-size"value="40px"/></selenium:styles></selenium:element></selenium:find>
<selenium:click><selenium:elementid="ok-button"/></selenium:click></actions></testcase>
CitrusReferenceGuide
437Selenium
InthisverysimpleexamplewefirststarttheSeleniumbrowserinstance.AfterthatwecancontinuetouseSeleniumcommandswithoutbrowserattributeexplicitlyset.Citrusknowswhichbrowserinstanceiscurrentlyactiveandwillautomaticallyusethisopenedbrowserinstance.Nextinthisexamplewefindsomeelementonthedisplayedpagebyitstag-nameandtext.Wealsovalidatetheelementstylefont-sizetomeettheexpectedvalue40pxinthisstep.
Inadditiontothattheexampleperformsaclickoperationontheelementwiththeidok-button.Seleniumsupportselementfindoperationsondifferentproperties:
idfindselementbasedontheidattributenamefindselementbasedonthenameattributetag-namefindselementbasedonthetagnameclass-namefindselementbasedonthecssclassnamelink-textfindslinkelementbasedonthelink-textxpathfindselementbasedonXPathevaluationintheDOM
BasedonthatwecanexecuteseveralSeleniumcommandsinatestcaseandvalidatetheresultssuchaswebelements.
CitrussupportsthefollowingSeleniumcommandswithrespectivetestactions:
selenium:startStartthebrowserinstanceselenium:findFindselementoncurrentpageandvalidateselementpropertiesselenium:clickPerformsclickoperationonelementselenium:hoverPerformshoveroperationonelementselenium:navigateNavigatestonewpageurl(includinghistoryback,forwardandrefresh)selenium:set-inputFindsinputelementandsetsvalueselenium:check-inputFindscheckboxelementandsets/unsetsvalueselenium:dropdown-selectFindsdropdownelementandselectssingleormultiplevalue/sselenium:pageInstantiatepageobjectwithdependencyinjectionandexecutepageactionwithverificationselenium:openOpennewwindowselenium:closeClosewindowbygivennameselenium:switchSwitchfocustowindowwithgivennameselenium:wait-untilWaitforelementtobehiddenorvisibleselenium:alertAccesscurrentalertdialog(withactionaccessordismiss)selenium:screenshotMakesscreenshotofcurrentpage
CitrusReferenceGuide
438Selenium
selenium:store-fileStorefiletotemporarybrowserdirectoryselenium:get-stored-fileGetsstoredfilefromtemporarybrowserdirectoryselenium:javascriptExecuteJavascriptcodeinbrowserselenium:clear-cacheClearbrowsercacheandallcookiesselenium:stopStopsthebrowserinstance
UptonowwehaveonlyusedtheCitrusXMLDSL.OfcourseallSeleniumcommandsarealsoavailableinJavaDSLasthenextexampleshows.
JavaDSL
@AutowiredprivateSeleniumBrowserseleniumBrowser;
@CitrusTestpublicvoidseleniumTest()selenium().start(seleniumBrowser);
selenium().navigate("http://localhost:8080");
selenium().find().element(By.id("header"));.tagName("h1").enabled(true).displayed(true).text("Welcome!").style("font-size","40px");
selenium().click().element(By.linkText("ClickMe!"));
NowletshaveacloserlookatthedifferentSeleniumtestactionssupportedinCitrus.
Start/stopbrowser
Youcanstartandstopthebrowserinstancewithatestaction.Thisinstantiatesanewbrowserwindowandprepareseverythingforinteractingwiththewebinterface.
XMLDSL
<selenium:startbrowser="seleniumBrowser"/>
<!--Dosomethinginbrowser-->
<selenium:stopbrowser="seleniumBrowser"/>
CitrusReferenceGuide
439Selenium
JavaDSL
selenium().start(seleniumBrowser);
//dosomethinginbrowser
selenium().stop(seleniumBrowser);
AfterstartingabrowserinstanceCitruswillautomaticallyusethisverysamebrowserinstanceinallfurtherSeleniumactions.Thismechanismisbasedonatestvariable(selenium_browser)thatisautomaticallyset.Allothertestactionsareabletoloadthecurrentbrowserinstancebyreadingthistestvariablebeforeexecution.IncaseyouneedtoexplicitlyuseadifferentbrowserinstancethantheactiveinstanceyoucanaddthebrowserattributetoallSeleniumtestactions.
Note
Itisagoodideatostartandstopthebrowserinstancebeforeeachtestcase.Thismakessurethattestsarealsoexecutableinsinglerunanditalwayssetsupanewbrowserinstancesotestswillnotinfluenceeachother.
Find
Thefindelementtestactionsearchesforanelementonthecurrentpage.Theelementisspecifiedbyoneofthefollowingsettings:
idfindselementbasedontheidattributenamefindselementbasedonthenameattributetag-namefindselementbasedonthetagnameclass-namefindselementbasedonthecssclassnamelink-textfindslinkelementbasedonthelink-textxpathfindselementbasedonXPathevaluationintheDOM
Thefindelementactionwillautomaticallyfailincasethereisnosuchelementonthecurrentpage.Incasetheelementisfoundyoucanaddadditionalattributesandpropertiesforfurtherelementvalidation:
XMLDSL
<selenium:find><selenium:elementtag-name="h1"text="Welcome!"><selenium:styles><selenium:stylename="font-size"value="40px"/>
CitrusReferenceGuide
440Selenium
</selenium:styles></selenium:element></selenium:find>
<selenium:find><selenium:elementid="ok-button"text="Ok"enabled="true"displayed="true"><selenium:attributes><selenium:attributename="type"value="submit"/></selenium:attributes></selenium:element></selenium:find>
JavaDSL
selenium().find().element(By.tagName("h1")).text("Welcome!").style("font-size","40px");
selenium().find().element(By.id("ok-button")).tagName("button").enabled(true).displayed(true).text("Ok").style("color","red").attribute("type","submit");
Theexampleabovefindstheh1elementbyitstagnameandvalidatesthetextandcssstyleattributes.Secondlytheok-buttonisvalidatedwithexpectedenabled,displayed,text,styleandattributevalues.Theelementsmustbepresentonthecurrentpageandallexpectedelementpropertieshavetomatch.Otherwisethetestactionandthetestcaseisfailingwithvalidationerrors.
Click
Theactionperformsaclickoperationontheelement.
XMLDSL
<selenium:click><selenium:elementlink-text="ClickMe!"/></selenium:click>
JavaDSL
CitrusReferenceGuide
441Selenium
selenium().click().element(By.linkText("ClickMe!"));
Hover
Theactionperformsahoveroperationontheelement.
XMLDSL
<selenium:hover><selenium:elementlink-text="FindMe!"/></selenium:hover>
JavaDSL
selenium().hover().element(By.linkText("FindMe!"));
Forminputactions
Thefollowingactionsareusedtoaccessforminputelementssuchastextfields,checkboxesanddropdownlists.
XMLDSL
<selenium:set-inputvalue="Citrus"><selenium:elementname="username"/></selenium:set-input>
<selenium:check-inputchecked="true"><selenium:elementxpath="//input[@type='checkbox']"/></selenium:check-input>
<selenium:dropdown-selectoption="happy"><selenium:elementid="user-mood"/></selenium:dropdown-select>
JavaDSL
selenium().setInput("Citrus").element(By.name("username"));selenium().checkInput(true).element(By.xpath("//input[@type='checkbox']"));
selenium().select("happy").element(By.id("user-mood"));
CitrusReferenceGuide
442Selenium
Theactionsaboveselectdropdownoptionsandsetuserinputontextfieldsandcheckboxes.Asusualtheformelementsareselectedbysomepropertiessuchasids,namesorxpathexpressions.
Pageactions
PageobjectsareawellknownpatternwhenusingSelenium.Thepageobjectsdefineelementsthatthepageisworkingwith.Inadditiontothatthepageobjectsdefineactionsthatcanbeexecutedfromoutside.Thisobjectorientedapproachforaccessingpagesandtheirelementsisaverygoodidea.Letshavealookatasamplepageobject.
publicclassUserFormPageimplementsWebPage
@FindBy(id="userForm")privateWebElementform;
@FindBy(id="username")privateWebElementuserName;
/***Setstheusername.*/publicvoidsetUserName(Stringvalue,TestContextcontext)userName.clear();userName.sendKeys(value);
/***Submitstheform.*@paramcontext*/publicvoidsubmit(TestContextcontext)form.submit();
AsyoucanseethepageobjectisaJavaPOJOthatimplementstheWebPageinterface.ThepagedefinesWebElementmembers.TheseareautomaticallyinjectedbyCitrusandSeleniumbasedontheFindByannotation.NowthetestcaseisabletoloadthatpageobjectandexecutesomeactionmethodsonthepagesuchassetUserNameorsubmit.
XMLDSL
<selenium:pagetype="com.consol.citrus.selenium.pages.UserFormPage"
CitrusReferenceGuide
443Selenium
action="setUserName"><selenium:arguments><selenium:argument>Citrus</selenium:argument></selenium:arguments></selenium:page>
<selenium:pagetype="com.consol.citrus.selenium.pages.UserFormPage"action="submit"/>
JavaDSL
selenium().page(UserFormPage.class).argument("Citrus").execute("setUserName");
selenium().page(UserFormPage.class).execute("submit");
ThepageobjectclassisautomaticallyloadedandinstantiatedwithdependencyinjectionforallFindByannotatedwebelements.Afterthattheactionmethodisexecuted.TheactionmethodscanalsohavemethodparametersasseeninsetUserName.Thevalueparameterisautomaticallysetwhencallingthemethod.
MethodscanalsousetheoptionalparameterTestContext.Withthiscontextyoucanaccessthecurrenttestcontextwithalltestvariablesforinstance.Thismethodparametershouldalwaysbethelastparameter.
Pagevalidation
Wecanalsousepageobjectforvalidationpurpose.Thepageobjectisloadedandinstantiatedasdescribedinprevioussection.Thenthepagevalidatoriscalled.Thevalidatorperformsassertionsandvalidationoperationswiththepageobject.Letsseeasamplepagevalidator:
publicclassUserFormValidatorimplementsPageValidator<UserFormPage>
@Overridepublicvoidvalidate(UserFormPagewebPage,SeleniumBrowserbrowser,TestContextcontext)Assert.isTrue(webPage.getUserName()!=null);Assert.isTrue(StringUtils.hasText(webPage.getUserName().getAttribute("value")));
CitrusReferenceGuide
444Selenium
Thepagevalidatoriscalledwiththewebpageinstance,thebrowserandthetestcontext.Thevalidatorshouldassertpageobjectsandwebelementsforvalidationpurpose.Inatestcasewecancallthevalidatortovalidatethepage.
XMLDSL
<beanid="userFormValidator"class="com.consol.citrus.selenium.pages.UserFormValidator"/>
<selenium:pagetype="com.consol.citrus.selenium.pages.UserFormPage"action="validate"validator="userFormValidator"/>
JavaDSL
@AutowiredprivateUserFormValidatoruserFormValidator;
selenium().page(UserFormPage.class).execute("validate").validator(userFormValidator);
Insteadofusingaseparatevalidatorclassyoucanalsoputthevalidationmethodtothepageobjectitself.Thenpageobjectandvalidationisdonewithinthesameclass:
publicclassUserFormPageimplementsWebPage,PageValidator<UserFormPage>
@FindBy(id="userForm")privateWebElementform;
@FindBy(id="username")privateWebElementuserName;
/***Setstheusername.*/publicvoidsetUserName(Stringvalue,TestContextcontext)userName.clear();userName.sendKeys(value);
/***Submitstheform.*@paramcontext*/publicvoidsubmit(TestContextcontext)form.submit();
@Override
CitrusReferenceGuide
445Selenium
publicvoidvalidate(UserFormPagewebPage,SeleniumBrowserbrowser,TestContextcontext)Assert.isTrue(userName!=null);Assert.isTrue(StringUtils.hasText(userName.getAttribute("value")));Assert.isTrue(form!=null);
XMLDSL
<selenium:pagetype="com.consol.citrus.selenium.pages.UserFormPage"action="validate"/>
JavaDSL
selenium().page(UserFormPage.class).execute("validate");
Wait
Sometimesitisrequiredtowaitforanelementtoappearordisappearonthecurrentpage.Thewaitactionwillwaitagiventimefortheelementstatustobevisibleorhidden.
XMLDSL
<selenium:waituntil="hidden"><selenium:elementid="info-dialog"/></selenium:wait>
JavaDSL
selenium().waitUntil().hidden().element(By.id("info-dialog"));
Theexamplewaitsfortheelementinfo-dialogtodisappear.Thetimetowaitis5000millisecondsbydefault.Youcansetthetimeoutontheaction.DuetoSeleniumlimitationstheminimumwaittimeis1000milliseconds.
Navigate
TheactionnavigatestoanewpageeitherbyusinganewrelativepathoracompletenewHttpURL.
CitrusReferenceGuide
446Selenium
XMLDSL
<selenium:navigatepage="http://localhost:8080"/>
<selenium:navigatepage="help"/>
JavaDSL
selenium().navigate("http://localhost:8080");
selenium().navigate("help");
ThesampleabovedescribesanewpagewithnewHttpURL.Thebrowserwillnavigatetothisnewpage.AllfurtherSeleniumactionsareperformedonthisnewpage.ThesecondnavigationactionopenstherelativepagehelpsothenewpageURLishttp://localhost:8080/help.
Navigationisalwaysdoneontheactivebrowserwindow.Youcanmanagetheopenedwindowsasdescribedinnextsection.
Windowactions
Seleniumisabletomanagemultiplewindows.Soyoucanopen,closeandswichactivewindowsinaCitrustest.
XMLDSL
<selenium:open-windowname="my_window"/><selenium:switch-windowname="my_window"/><selenium:close-windowname="my_window"/>
JavaDSL
selenium().open().window("my_window");selenium().focus().window("my_window");selenium().close().window("my_window");
WhenanewwindowisopenedSeleniumcreatesawindowhandleforus.Thiswindowhandleissavedastestvariableusingagivenwindowname.Soafteropeningthewindowyoucanaccessthewindowbyitsnameinfurtheractions.Allupcoming
CitrusReferenceGuide
447Selenium
Seleniumactionswilltakeplaceinthisnewactivewindow.Ofcoursethetestactionswillfailassoonasthewindowwiththatgivennameismissing.Citrususesdefaultwindownamesthatareautomaticallyusedastestvariables:
selenium_active_windowtheactivewindowhandleselenium_last_windowthelastwindowhandlewhenswitchedtootherwindow
Alert
Weareabletoaccessthealertdialogonthecurrentpage.Citruswillvalidatethedisplayeddialogtextandacceptordismissofthedialog.
XMLDSL
<selenium:alertaccept="true"><selenium:alert-text>Hello!</selenium:alert-text></selenium:alert>
JavaDSL
selenium().alert().text("Hello!").accept();
Thealertdialogtextisvalidatedwhenexpectedtextisgivenonthetestaction.Theusercandecidetoacceptordismissthedialog.Afterthatthedialogshouldbeclosed.Incasethetestactionfailstofindanopenalertdialogthetestactionraisesruntimeerrorsandthetestwillfail.
Makescreenshot
Youcanexecutethisactionincaseyouwanttotakeascreenshotofthecurrentpage.Thisactiononlyworkswithbrowsersthatactuallydisplaytheuserinterface.TheactionwillnothaveanyeffectwhenexecutedwithHtmlunitwebdriverinheadlessmode.
XMLDSL
<selenium:screenshot/>
<selenium:screenshotoutput-dir="target"/>
JavaDSL
CitrusReferenceGuide
448Selenium
selenium().screenhsot();
selenium().screenhsot("target");
Thetestactionhasanoptionalparameteroutput-dirwhichrepresentstheoutputdirectorywherethescreenshotissavedto.
Temporarystorage(Firefox)
ImportantThisactiononlyworkswithFirefoxwebdriver!Otherbrowsersarenotworkingwiththetemporarydownloadstorage.
Thebrowserusesatemporarystoragefordownloadedfiles.Wecanaccessthistemporarystorageduringatestcase.
XMLDSL
<selenium:store-filefile-path="classpath:download/file.txt"/><selenium:get-stored-filefile-name="file.txt"/>
JavaDSL
selenium().store("classpath:download/file.txt");selenium().getStored("file.txt");
Asyoucanseethetestcaseisabletostorenewfilestothetemporarybrowserstorage.Wehavetogivethefilepathasclasspathorfilesystempath.Whenreadingthetemporaryfilestorageweneedtospecifythefilenamethatwewanttoaccessinthetemporarystorage.Thetemporarystorageisnotcapableofsubdirectoriesallfilesarestoreddirectlytothestorageinonesingledirectory.
Incasethestoredfileisnotfoundbythatnamethetestactionfailswithrespectiveerrors.OntheotherhandwhenthefileisfoundintemporarystorageCitruswillautomaticallycreateanewtestvariableselenium_download_filewhichcontainsthefilenameasvalue.
Clearbrowsercache
Whenclearingthebrowsercacheallcookiesandtemporaryfileswillbedeleted.
XMLDSL
CitrusReferenceGuide
449Selenium
<selenium:clear-cache/>
JavaDSL
selenium().clearCache();
CitrusReferenceGuide
450Selenium
DynamicendpointcomponentsEndpointsrepresentthecentralcomponentsinCitrustosendorreceiveamessageonsomedestination.UsuallyendpointsgetdefinedinthebasicCitrusSpringapplicationcontextconfigurationasSpringbeancomponents.Insomecasesthismightbeoverengineeringasthetesterjustwantstosendorreceiveamessage.Inparticularthisisdonewhendoingsanitychecksinserverendpointswhiledebuggingacertainscenario.
WithendpointcomponentsyouareabletocreatetheCitrusendpointforsendingandreceivingamessageattestruntime.ThereisnoadditionalconfigurationorSpringbeancomponentneeded.YoujustusetheendpointuriinaspecialnamingconventionandCitruswillcreatetheendpointforyou.Letusseeafirstexampleofthisscenario:
<testcasename="DynamicEndpointTest"><actions><sendendpoint="jms:Hello.Queue?timeout=10000"><message><payload>[...]</payload></message></send>
<receiveendpoint="jms:Hello.Response.Queue?timeout=5000"><message><payload>[...]</payload></message></receive></actions></testcase>
Asyoucanseetheendpointurijustgoesintothetestcaseactioninsubstitutiontotheusualendpointreferencename.InsteadofreferencingabeanidthatpointstothepreviouslyconfiguredCitrusendpointweusetheendpointuridirectly.Theendpointurishouldgiveallinformationtocreatetheendpointatruntime.Intheexampleaboveweuseakeywordjms:whichtellsCitrusthatweneedtocreateaJMSmessageendpoint.SecondlywegivetheJMSdestinationnameHello.Queuewhichisamandatorypartof
CitrusReferenceGuide
451Endpointcomponent
theendpointuriwhenusingtheJMScomponent.Theoptionaltimeoutparametercompletedtheuri.CitrusisabletocreatetheJMSendpointatruntimesendingthemessagetothedefineddestinationviaJMS.
OfcoursethismechanismisnotlimitedtoJMSendpoints.WecanusealldefaultCitrusmessagetransportsintheendpointuri.Justpicktherightkeywordthatdefinesthemessagetransporttouse.Hereisalistofsupportedkeywords:
jms:CreatesaJMSendpointforsendingandreceivingmessagetoaqueueortopicchannel:CreatesachannelendpointforsendingandreceivingmessagesusinganinmemorySpringIntegrationmessagechannelhttp:CreatesaHTTPclientforsendingarequesttosomeserverURLsynchronouslywaitingfortheresponsemessagews:CreatesaWebSocketclientforsendingmessagestoorreceivingmessagesfromaWebSocketserversoap:CreatesaSOAPWebServiceclientthatsendaproperSOAPmessagetotheserverURLandwaitsforthesynchronousresponsetoarrivessh:Createsanewsshclientforpublishingacommandtotheservermail:orsmtp:CreatesanewmailclientforsendingamailmimemessagetoaSMTPservercamel:CreatesanewApacheCamelendpointforsendingandreceivingCamelexchangesbothtoandfromCamelroutes.vertx:oreventbus:CreatesanewVert.xinstancesendingandreceivingmessageswiththenetworkeventbusrmi:CreatesanewRMIclientinstancesendingandreceivingmessagesformethodinvocationonremoteinterfacesjmx:CreatesanewJMXclientinstancesendingandreceivingmessagestoandfromamanagedbeanserver.
Dependingonthemessagetransportwehavetoaddmandatoryparameterstotheendpointuri.IntheJMSexamplewehadtospecifythedestinationname.Themandatoryparametersarealwayspartoftheendpointuri.Optionalparameterscanbeaddedaskeyvaluepairstotheendpointuri.Theavailableparametersdependontheendpointkeywordthatyouhavechosen.Seetheseexampleendpointuriexpressions:
jms:queuename?connectionFactory=specialConnectionFactory&timeout=10000jms:topic:topicname?connectionFactory=topicConnectionFactoryjms:sync:queuename?connectionFactory=specialConnectionFactory&pollingInterval=100&replyDestination=myReplyDestination
channel:channelName
CitrusReferenceGuide
452Endpointcomponent
channel:sync:channelNamechannel:channelName?timeout=10000&channelResolver=myChannelResolver
http:localhost:8088/testhttp://localhost:8088/testhttp:localhost:8088?requestMethod=GET&timeout=10000&errorHandlingStrategy=throwsException&requestFactory=myRequestFactoryhttp://localhost:8088/test?requestMethod=DELETE&customParam=foo
websocket:localhost:8088/testwebsocket://localhost:8088/testws:localhost:8088/testws://localhost:8088/test
soap:localhost:8088/testsoap:localhost:8088?timeout=10000&errorHandlingStrategy=propagateError&messageFactory=myMessageFactory
mail:localhost:25000smtp://localhost:25000smtp://localhost?timeout=10000&username=foo&password=1234&mailMessageMapper=myMapper
ssh:localhost:2200ssh://localhost:2200?timeout=10000&strictHostChecking=true&user=foo&password=12345678
rmi://localhost:1099/someServicermi:localhost/someService&timeout=10000
jmx:rmi:///jndi/rmi://localhost:1099/someServicejmx:platform&timeout=10000
camel:direct:addresscamel:seda:addresscamel:jms:queue:someQueue?connectionFactory=myConnectionFactorycamel:activemq:queue:someQueue?concurrentConsumers=5&destination.consumer.prefetchSize=50camel:controlbus:route?routeId=myRoute&action=status
vertx:addressNamevertx:addressName?port=10105&timeout=10000&pubSubDomain=truevertx:addressName?vertxInstanceFactory=vertxFactory
Theoptionalparametersgetdirectlysetasendpointconfiguration.YoucanuseprimitivevaluesaswellasSpringbeanidreferences.CitruswillautomaticallydetectthetargetparametertypeandresolvethevaluetoaSpringbeanintheapplicationcontextifnecessary.IfyouusesomeunknownparameterCitruswillraiseanexceptionatruntimeastheendpointcouldnotbecreatedproperly.
CitrusReferenceGuide
453Endpointcomponent
Insynchronouscommunicationwehavetoreuseendpointcomponentsinordertoreceivesynchronousmessagesonreplydestinations.Thisisaproblemwhenusingdynamicendpointsastheendpointsgetcreatedatruntime.Citrususesacachingofendpointsthatgetcreatedatruntime.Followingfromthatwehavetousetheexactsameendpointuriinyourtestcaseinordertogetthecachedendpointinstance.Withthislittletricksynchronouscommunicationwillworkjustasitisdonewithstaticendpointcomponents.Havealookatthissampletest:
<testcasename="DynamicEndpointTest"><actions><sendendpoint="jms:sync:Hello.Sync.Queue"><message><payload>[...]</payload></message></send>
<receiveendpoint="jms:sync:Hello.Sync.Queue"><message><payload>[...]</payload></message></receive></actions></testcase>
Asyoucanseeweusedtheexactdynamicendpointuriinbothsendandreceiveactions.Citrusisthenabletoreusethesamedynamicendpointandthesynchronousreplywillbereceivedasexpected.Howeverthereuseofexactlythesameendpointurimightgetannoyingaswealsohavetocopyendpointuriparametersandsoon.
<testcasename="DynamicEndpointTest"><actions><sendendpoint="http://localhost:8080/HelloService?user=1234567"><message><payload>[...]</payload></message></send>
<receiveendpoint="http://localhost:8080/HelloService?user=1234567"><message><payload>
CitrusReferenceGuide
454Endpointcomponent
[...]</payload></message></receive></actions></testcase>
Wehavetousetheexactsameendpointuriwhenreceivingthesynchronousserviceresponse.Thisisnotverystraightforward.ThisiswhyCitrusalsosupportsdynamicendpointnames.WithaspecialendpointuriparametercalledendpointNameyoucannamethedynamicendpoint.Inacorrespondingreceiveactionyoujustusetheendpointnameasreferencewhichmakeslifemoreeasy:
<testcasename="DynamicEndpointTest"><actions><sendendpoint="http://localhost:8080/HelloService?endpointName=myHttpClient"><message><payload>[...]</payload></message></send>
<receiveendpoint="http://localhost?endpointName=myHttpClient"><message><payload>[...]</payload></message></receive></actions></testcase>
Sowecanreferencethedynamicendpointwiththegivenname.TheinternalendpointNameuriparameterisautomaticallyremovedbeforesendingoutmessages.OnceagainthedynamicendpointurimechanismprovidesafastwaytowritetestcasesinCitruswithlessconfiguration.ButyoushouldconsidertousethestaticendpointcomponentsdefinedinthebasicSpringbeanapplicationcontextforendpointsthatareheavilyreusedinmultipletestcases.
CitrusReferenceGuide
455Endpointcomponent
EndpointadapterEndpointadapterhelptocustomizethebehaviorofaCitrusserversuchasHTTPorSOAPwebservers.AstheserversgetstartedwiththeCitruscontexttheyarereadytoreceiveincomingclientrequests.Nowtherearedifferentwaystoprocesstheseincomingrequestsandtoprovideaproperresponsemessage.Bydefaulttheserverwillforwardtheincomingrequesttoainmemorymessagechannelwhereatestcanreceivethemessageandprovideasynchronousresponse.Thismessagechannelhandlingisdoneautomaticallybehindthescenessothetesterdoesnotcareaboutthesethings.Thetesterjustusestheserverdirectlyasendpointreferenceinthetestcase.Thisisthedefaultbehaviour.InadditiontothatyoucandefinecustomendpointadaptersontheCitrusserverinordertochangethisdefaultbehavior.
Yousetthecustomendpointadapterdirectlyontheserverconfigurationasfollows:
<citrus-http:serverid="helloHttpServer"port="8080"auto-start="true"endpoint-adapter="emptyResponseEndpointAdapter"resource-base="src/it/resources"/>
<citrus:empty-response-adapterid="emptyResponseEndpointAdapter"/>
Nowletushaveacloserlookattheprovidedendpointadapterimplementations.
Emptyresponseendpointadapter
Thisisthesimplestendpointadapteryoucanthinkof.ItsimplyprovidesanemptysuccessresponseusingtheHTTPresponsecode200.TheadapterdoesnotneedanyconfigurationsorpropertiesasitsimplyrespondswithanemptyHTTPresponse.
<citrus:empty-response-adapterid="emptyResponseEndpointAdapter"/>
Staticresponseendpointadapter
Thenextmorecomplexendpointadapterwillalwaysreturnastaticresponsemessage.
<citrus:static-response-adapterid="endpointAdapter"><citrus:payload>
CitrusReferenceGuide
456Endpointadapter
<![CDATA[<HelloResponsexmlns="http://www.consol.de/schemas/samples/sayHello.xsd"><MessageId>123456789</MessageId><CorrelationId>Cx1x123456789</CorrelationId><Text>HelloUser</Text></HelloResponse>]]></citrus:payload><citrus:header><citrus:elementname="http://www.consol.de/schemas/samplesh1:Operation"value="sayHello"/><citrus:elementname="http://www.consol.de/schemas/samplesh1:MessageId"value="123456789"/></citrus:header></citrus:static-response-adapter>
Theendpointadapterisconfiguredwithastaticmessagepayloadandstaticresponseheadervalues.Theresponsetotheclientisthereforealwaysthesame.YoucanadddynamicvaluesbyusingCitrusfunctionssuchasrandomStringorrandomNumber.Alsoweareabletousevaluesoftheactualrequestmessagethathastriggeredtheresponseadapter.Therequestisavailableviathelocalmessagestore.IncombinationwithXpathorJsonPathfunctionswecanmapvaluesfromtheactualrequest.
<citrus:static-response-adapterid="endpointAdapter"><citrus:payload><![CDATA[<HelloResponsexmlns="http://www.consol.de/schemas/samples/sayHello.xsd"><MessageId>citrus:randomNumber(10)</MessageId><CorrelationId>citrus:xpath(citrus:message(request.payload()),'/hello:HelloRequest/hello:CorrelationId')</CorrelationId><Text>HelloUser</Text></HelloResponse>]]></citrus:payload><citrus:header><citrus:elementname="http://www.consol.de/schemas/samplesh1:Operation"value="sayHello"/><citrus:elementname="http://www.consol.de/schemas/samplesh1:MessageId"value="citrus:randomNumber(10)"/></citrus:header></citrus:static-response-adapter>
CitrusReferenceGuide
457Endpointadapter
TheexampleabovemapstheCorrelationIdoftheHelloRequestmessagetotheresponsewithXpathfunction.Thelocalmessagestoreautomaticallyhasthemessagenamedrequeststoredsowecanaccessthepayloadwiththismessagename.
NoteXMLisnamespacespecificsoweneedtousethenamespaceprefixhellointheXpathexpression.ThenamespaceprefixshouldevaluatetoaglobalnamespaceentryintheglobalCitrusxpath-namespace.
Requestdispatchingendpointadapter
Theideabehindtherequestdispatchingendpointadapteristhattheincomingrequestsaredispatchedtoseveralotherendpointadapters.Thedecisionwhichendpointadaptershouldhandletheactualrequestisdonedependingonsomeadaptermapping.Themappingisdonebasedonthepayloadorheaderdataoftheincomingrequest.Amappingstrategyevaluatesamappingkeyusingtheincomingrequest.YoucanthinkofanXPathexpressionthatevaluatestothemappingkeyforinstance.Theendpointadapterthatmapstothemappingkeyisthencalledtohandletherequest.
Sotherequestdispatchingendpointadapterisabletodynamicallycallseveralotherendpointadaptersbasedontheincomingrequestmessageatruntime.Thisisverypowerful.ThenextexampleusestherequestdispatchingendpointadapterwithaXPathmappingkeyextractor.
<citrus:dispatching-endpoint-adapterid="dispatchingEndpointAdapter"mapping-key-extractor="mappingKeyExtractor"mapping-strategy="mappingStrategy"/>
<beanid="mappingStrategy"class="com.consol.citrus.endpoint.adapter.mapping.SimpleMappingStrategy"><propertyname="adapterMappings"><map><entrykey="sayHello"ref="helloEndpointAdapter"/></map></property></bean>
<beanid="mappingKeyExtractor"class="com.consol.citrus.endpoint.adapter.mapping.XPathPayloadMappingKeyExtractor"><propertyname="xpathExpression"value="//TestMessage/Operation/*"/></bean>
<citrus:static-response-adapterid="helloEndpointAdapter"><citrus:payload><![CDATA[<HelloResponse
CitrusReferenceGuide
458Endpointadapter
xmlns="http://www.consol.de/schemas/samples/sayHello.xsd"><MessageId>123456789</MessageId><Text>HelloUser</Text></HelloResponse>]]></citrus:payload></citrus:static-response-adapter>
TheXPathmappingkeyextractorexpressiondecidesforeachrequestwhichmappingkeytouseinordertofindaproperendpointadapterthroughthemappingstrategy.Theendpointadaptersavailableintheapplicationcontextaremappedviatheirbeanid.Forinstanceanincomingrequestwithamatchingelement//TestMessage/Operation/sayHellowouldbehandledbytheendpointadapterbeanthatisregisteredinthemappingstrategyas"sayHello"key.TheavailableendpointadaptersareconfiguredinthesameSpringapplicationcontext.
Citrusprovidesseveraldefaultmappingkeyextractorimplementations.
HeaderMappingKeyExtractor:Readsaspecialheaderentryandusesitsvalueasmappingkey
SoapActionMappingKeyExtractor:Usesthesoapactionheaderentryasmappingkey
XPathPayloadMappingKeyExtractor:EvaluatesaXPathexpressionontherequestpayloadandusestheresultasmappingkey
Inadditiontothatweneedamappingstrategy.Citrusprovidesfollowingdefaultimplementations.
SimpleMappingStrategy:Simplekeyvaluemapwithendpointadapterreferences
BeanNameMappingStrategy:LoadstheendpointadapterSpringbeanwiththegivenidmatchingthemappingkey
ContextLoadingMappingStrategy:SameasBeanNameMappingStrategybutloadsaseparateapplicationcontextdefinedbyexternalfileresource
Channelendpointadapter
ThechannelconnectingendpointadapteristhedefaultadapterusedinallCitrusservercomponents.Indeedthisadapteralsoprovidesthemostflexibility.Thisadapterforwardsincomingrequeststoachanneldestination.Theadapteriswaitingforaproperresponse
CitrusReferenceGuide
459Endpointadapter
onareplydestinationsynchronously.Withthechannelendpointcomponentsyoucanreadtherequestsonthechannelandprovideaproperresponseonthereplydestination.
<citrus:channel-endpoint-adapterid="channelEndpointAdapter"channel-name="inbound.channel"timeout="2500"/>
JMSendpointadapter
AnotherpowerfulendpointadapteristheJMSconnectingadapterimplementation.ThisadapterforwardsincomingrequeststoaJMSdestinationandwaitsforaproperresponseonareplydestination.AJMSendpointcanaccesstherequestsinternallyandprovideaproperresponseonthereplydestination.Sothisadapterisveryflexibletoprovideproperresponsemessages.
Thisspecialadaptercomeswiththecitrus-jmsmodule.SoyouhavetoaddthemoduleandthespecialXMLnamespaceforthismoduletoyourconfigurationfiles.TheMavenmoduleforcitrus-jmsgoestotheMavenPOMfileasnormalprojectdependency.Thecitrus-jmsnamespacegoestotheSpringbeanXMLconfigurationfileasfollows:
NoteCitrusprovidesa"citrus-jms"configurationnamespaceandschemadefinitionforJMSrelatedcomponentsandfeatures.IncludethisnamespaceintoyourSpringconfigurationinordertousetheCitrusJMSconfigurationelements.ThenamespaceURIandschemalocationareaddedtotheSpringconfigurationXMLfileasfollows.
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus-jms="http://www.citrusframework.org/schema/jms/config"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/jms/confighttp://www.citrusframework.org/schema/jms/config/citrus-jms-config.xsd">
[...]
</beans>
AfterthatyouareabletousetheadapterimplementationintheSpringbeanconfiguration.
CitrusReferenceGuide
460Endpointadapter
<citrus-jms:endpoint-adapterid="jmsEndpointAdapter"destination-name="JMS.Queue.Requests.In"reply-destination-name="JMS.Queue.Response.Out"connection-factory="jmsConnectionFactory"timeout="2500"/>
<beanid="jmsConnectionFactory"class="org.apache.activemq.ActiveMQConnectionFactory"><propertyname="brokerURL"value="tcp://localhost:61616"/></bean>
CitrusReferenceGuide
461Endpointadapter
FunctionsThetestframeworkwillofferseveralfunctionsthatareusefulthroughoutthetestexecution.Thefunctionswillalwaysreturnastringvaluethatisreadyforuseasvariablevalueordirectlyinsideatextmessage.
Asetoffunctionsisusuallycombinedtoafunctionlibrary.Thelibraryhasaprefixthatwillidentifythefunctionsinsidethetestcase.Thedefaulttestframeworkfunctionlibraryusesadefaultprefix(citrus).Youcanwriteyourownfunctionlibraryusingyourownprefixinordertoextendthetestframeworkfunctionalitywheneveryouwant.
ThelibraryisbuiltintheSpringconfigurationandcontainsasetoffunctionsthatareofpublicuse.
<citrus:function-libraryid="testLibrary"prefix="foo:"><citrus:functionname="randomNumber">class="com.consol.citrus.functions.RandomNumberFunction"/><citrus:functionname="randomString">class="com.consol.citrus.functions.RandomStringFunction"/><citrus:functionname="customFunction">ref="customFunctionBean"/>...</citrus:function-library>
AsyoucanseethelibrarydefinesonetomanyfunctionseitherreferencedasnormalSpringbeanorbyitsimplementingJavaclassname.Citrusconstructsthelibraryandyouareabletousethefunctionsinyourtestcasewiththeleadinglibraryprefixjustlikethis:
foo:randomNumber()foo:randomString()foo:customFunction()
TipYoucanaddcustomfunctionimplementationsandcustomfunctionlibraries.Justuseacustomprefixforyourlibrary.ThedefaultCitrusfunctionlibraryusesthecitrus:prefix.Inthenextchaptersthedefaultfunctionsofferedbytheframeworkwillbedescribedindetail.
concat()
CitrusReferenceGuide
462Functions
Thefunctionwillcombineseveralstringtokenstoasinglestringvalue.Thismeansthatyoucancombineastatictextvaluewithavariablevalueforinstance.Afirstexampleshouldclarifytheusage:
<testcasename="concatFunctionTest"><variables><variablename="date"value="citrus:currentDate(yyyy-MM-dd)"/><variablename="text"value="HelloTestFramework!"/></variables><actions><echo><message>citrus:concat('Todayis:',$date,'right!?')</message></echo><echo><message>citrus:concat('Textis:',$text)</message></echo></actions></testcase>
Pleasedonotforgettomarkstatictextwithsinglequotesigns.Thereisnolimitationforstringtokenstobecombined.
citrus:concat('Text1','Text2','Text3',$text,'Text5',…,'TextN')
Thefunctioncanbeusedwherevervariablescanbeused.ForinstancewhenvalidatingXMLelementsinthereceiveaction.
<message><validatepath="//element/element"value="citrus:concat('Cx1x',$generatedId)"/></message>
substring()
Thefunctionwillhavethreeparameters.
1. Stringtoworkon2. Startingindex3. Endindex(optional)
Letushavealookatasimpleexampleforthisfunction:
CitrusReferenceGuide
463Functions
<echo><message>citrus:substring('HelloTestFramework',6)</message></echo><echo><message>citrus:substring('HelloTestFramework',0,5)</message></echo>
Functionoutput:
TestFrameworkHello
stringLength()
Thefunctionwillcalculatethenumberofcharactersinastringrepresentationandreturnthenumber.
<echo><message>citrus:stringLength('HelloTestFramework')</message></echo>
Functionoutput:
20
translate()
Thisfunctionwillreplaceregularexpressionmatchingvaluesinsideastringrepresentationwithaspecifiedreplacementstring.
<echo><message>citrus:translate('H.lloTestFr.mework','\.','a')</message></echo>
Notethatthesecondparameterwillbearegularexpression.Thethirdparameterwillbeasimplereplacementstringvalue.
CitrusReferenceGuide
464Functions
Functionoutput:
HelloTestFramework
substringBefore()
Thefunctionwillsearchforthefirstoccurrenceofaspecifiedstringandwillreturnthesubstringbeforethatoccurrence.Letushaveacloserlookinasimpleexample:
<echo><message>citrus:substringBefore('Test/Framework','/')</message></echo>
Inthespecificexamplethefunctionwillsearchforthe‘/’characterandreturnthestringbeforethatindex.
Functionoutput:
Test
substringAfter()
Thefunctionwillsearchforthefirstoccurrenceofaspecifiedstringandwillreturnthesubstringafterthatoccurrence.Letusclarifythiswithasimpleexample:
<echo><message>citrus:substringAfter('Test/Framework','/')</message></echo>
SimilartothesubstringBeforefunctionthe‘/’characterisfoundinthestring.Butnowtheremainingstringisreturnedbythefunctionmeaningthesubstringafterthischaracterindex.
Functionoutput:
Framework
round()
CitrusReferenceGuide
465Functions
Thisisasimplemathematicfunctionthatwillrounddecimalnumbersrepresentationstotheirnearestnondecimalnumber.
<echo><message>citrus:round('3.14')</message></echo>
Functionoutput:
3
floor()
Thisfunctionwillrounddowndecimalnumbervalues.
<echo><message>citrus:floor('3.14')</message></echo>
Functionoutput:
3.0
ceiling()
Similartofloorfunction,butnowthefunctionwillroundupthedecimalnumbervalues.
<echo><message>citrus:ceiling('3.14')</message></echo>
Functionoutput:
4.0
randomNumber()
Therandomnumberfunctionwillprovideyoutheopportunitytogeneraterandomnumberstringscontainingpositivenumberletters.ThereisasingularBooleanparameterforthatfunctiondescribingwhetherthegeneratednumbershouldhaveexactlytheamountofdigits.Defaultvalueforthispaddingflagwillbetrue.
CitrusReferenceGuide
466Functions
Nextexamplewillshowthefunctionusage:
<variables><variablename="rndNumber1"value="citrus:randomNumber(10)"/><variablename="rndNumber2"value="citrus:randomNumber(10,true)"/><variablename="rndNumber2"value="citrus:randomNumber(10,false)"/><variablename="rndNumber3"value="citrus:randomNumber(3,false)"/></variables>
Functionoutput:
89546387655003485980638765065
randomString()
Thisfunctionwillgeneratearandomstringrepresentationwithadefinedlength.Asecondparameterforthisfunctionwilldefinethecaseofthegeneratedletters(UPPERCASE,LOWERCASE,MIXED).Thelastparameterallowsalsodigitcharactersinthegeneratedstring.Bydefaultdigitcharatersarenotallowed.
<variables><variablename="rndString0"value="$citrus:randomString(10)"/><variablename="rndString1"value="citrus:randomString(10)"/><variablename="rndString2"value="citrus:randomString(10,UPPERCASE)"/><variablename="rndString3"value="citrus:randomString(10,LOWERCASE)"/><variablename="rndString4"value="citrus:randomString(10,MIXED)"/><variablename="rndString4"value="citrus:randomString(10,MIXED,true)"/></variables>
Functionoutput:
HrGHOdfAerAgSSwedetGJSDFUTTRKUdtkhirtsuzVt567JkA32
randomEnumValue()
CitrusReferenceGuide
467Functions
Thisfunctionreturnsoneofitssuppliedarguments.Furthermoreyoucanspecifyacustomfunctionwithaconfiguredlistofvalues(theenumeration).Thefunctionwillrandomlyreturnanentrywhencalledwithoutarguments.Thispromotescodereuseandfacilitatesrefactoring.
InthenextsamplethefunctionisusedtosetahttpStatusCodevariabletooneofthegivenHTTPstatuscodes(200,401,500)
<variablename="httpStatusCode"value="citrus:randomEnumValue('200','401','500')"/>
Asmentionedbeforeyoucandefineacustomfunctionforyourveryspecificneedsinordertoeasilymanagealistofpredefinedvalueslikethis:
<citrus:function-libraryid="myCustomFunctionLibrary"prefix="custom:"><citrus-functionname="randomHttpStatusCode"ref="randomHttpStatusCodeFunction"/></citrus:function-library>
<beanid="randomHttpStatusCodeFunction"class="com.consol.citrus.functions.core.RandomEnumValueFunction"<propertyname="values"><list><value>200</value><value>500</value><value>401</value></list></property></bean>
Wehaveaddedacustomfunctionlibrarywithacustomfunctiondefinition.Thecustomfunction"randomHttpStatusCode"randomlychoosesanHTTPstatuscodeeachtimeitiscalled.Insidethetestyoucanusethefunctionlikethis:
<variablename="httpStatusCode"value="custom:randomHttpStatusCode()"/>
currentDate()
Thisfunctionwilldefinitelyhelpyouwhenaccessingthecurrentdate.Someexampleswillshowtheusageindetail:
<echo><message>citrus:currentDate()</message></echo><echo><message>citrus:currentDate('yyyy-MM-dd')</message></echo><echo><message>citrus:currentDate('yyyy-MM-ddHH:mm:ss')</message></echo>
CitrusReferenceGuide
468Functions
<echo><message>citrus:currentDate('yyyy-MM-dd'T'hh:mm:ss')</message></echo><echo><message>citrus:currentDate('yyyy-MM-ddHH:mm:ss','+1y')</message></echo><echo><message>citrus:currentDate('yyyy-MM-ddHH:mm:ss','+1M')</message></echo><echo><message>citrus:currentDate('yyyy-MM-ddHH:mm:ss','+1d')</message></echo><echo><message>citrus:currentDate('yyyy-MM-ddHH:mm:ss','+1h')</message></echo><echo><message>citrus:currentDate('yyyy-MM-ddHH:mm:ss','+1m')</message></echo><echo><message>citrus:currentDate('yyyy-MM-ddHH:mm:ss','+1s')</message></echo><echo><message>citrus:currentDate('yyyy-MM-ddHH:mm:ss','-1y')</message></echo>
NotethatthecurrentDatefunctionprovidestwoparameters.Firstparameterdescribesthedateformatstring.Thesecondwilldefineadateoffsetstringcontainingyear,month,days,hours,minutesorsecondsthatwillbeaddedorsubtractedtoorfromtheactualdatevalue.
Functionoutput:
01.09.20092009-09-012009-09-0112:00:002009-09-01T12:00:00
upperCase()
Thisfunctionconvertsanystringtouppercaseletters.
<echo><message>citrus:upperCase('HelloTestFramework')</message></echo>
Functionoutput:
HELLOTESTFRAMEWORK
lowerCase()
Thisfunctionconvertsanystringtolowercaseletters.
<echo><message>citrus:lowerCase('HelloTestFramework')</message></echo>
Functionoutput:
CitrusReferenceGuide
469Functions
hellotestframework
average()
Thefunctionwillsumupallspecifiednumbervaluesanddividetheresultthroughthenumberofvalues.
<variablename="avg"value="citrus:average('3','4','5')"/>
avg=4.0
minimum()
Thisfunctionreturnstheminimumvalueinasetofnumbervalues.
<variablename="min"value="citrus:minimum('3','4','5')"/>
min=3.0
maximum()
Thisfunctionreturnsthemaximumvalueinasetofnumbervalues.
<variablename="max"value="citrus:maximum('3','4','5')"/>
max=5.0
sum()
Thefunctionwillsumupallnumbervalues.Thenumbervaluescanalsobenegative.
<variablename="sum"value="citrus:sum('3','4','5')"/>
sum=12.0
absolute()
Thefunctionwillreturntheabsolutenumbervalue.
CitrusReferenceGuide
470Functions
<variablename="abs"value="citrus:absolute('-3')"/>
abs=3.0
mapValue()
Thisfunctionimplementationmapsstringkeystostringvalues.Thisisveryhelpfulwhentheusedkeyisrandomlychosenatruntimeandthecorrespondingvalueisnotdefinedduringthedesigntime.
ThefollowingfunctionlibrarydefinesacustomfunctionformappingHTTPstatuscodestothecorrespondingmessages:
<citrus:function-libraryid="myCustomFunctionLibrary"prefix="custom:"><citrus-functionname="getHttpStatusMessage"ref="getHttpStatusMessageFunction"/></citrus:function-library>
<beanid="getHttpStatusMessageFunction"class="com.consol.citrus.functions.core.MapValueFunction"<propertyname="values"><map><entrykey="200"value="OK"/><entrykey="401"value="Unauthorized"/><entrykey="500"value="InternalServerError"/></map></property></bean>
InthisexamplethefunctionsetsthevariablehttpStatusMessagetothe'InternalServerError'stringdynamicallyatruntime.ThetestonlyknowstheHTTPstatuscodeanddoesnotcareaboutspellingandmessagelocales.
<variablename="httpStatusCodeMessage"value="custom:getHttpStatusMessage('500')"/>
randomUUID()
ThefunctionwillgeneratearandomJavaUUID.
<variablename="uuid"value="citrus:randomUUID()"/>
uuid=98fbd7b0-832e-4b85-b9d2-e0113ee88356
CitrusReferenceGuide
471Functions
encodeBase64()
Thefunctionwillencodeastringtobinarydatausingbase64hexadecimalencoding.
<variablename="encoded"value="citrus:encodeBase64('HalloTestframework')"/>
encoded=VGVzdCBGcmFtZXdvcms=
decodeBase64()
Thefunctionwilldecodebinarydatatoacharactersequenceusingbase64hexadecimaldecoding.
<variablename="decoded"value="citrus:decodeBase64('VGVzdCBGcmFtZXdvcms=')"/>
decoded=HalloTestframework
escapeXml()
IfyouwanttodealwithescapedXMLinyourtestcaseyoumaywanttousethisfunction.ItautomaticallyescapesallXMLspecialcharacters.
<echo><message><![CDATA[citrus:escapeXml('<Message>HalloTestFramework</Message>')]]></message></echo>
<Message>HalloTestFramework</Message>
cdataSection()
UsuallyweuseCDATAsectionstodefinemessagepayloaddatainsideatestcase.WemightrunintoproblemswhenthepayloaditselfcontainsCDATAsectionsasnestedCDATAsectionsareprohibitedbyXMLnature.Inthiscasethenextfunctionshipsveryusefull.
CitrusReferenceGuide
472Functions
<variablename="cdata"value="citrus:cdataSection('payload')"/>
cdata=<![CDATA[payload]]>
digestAuthHeader()
Digestauthenticationisacommonlyusedsecurityalgorithm,especiallyinHttpcommunicationandSOAPWebServices.CitrusoffersafunctiontogenerateadigestauthenticationprincipleusedintheHttpheadersectionofamessage.
<variablename="digest"value="citrus:digestAuthHeader('username','password','authRealm','acegi','POST','http://127.0.0.1:8080','citrus','md5')"/>
Apossibledigestauthenticationheadervaluelookslikethis:
<Digestusername=foo,realm=arealm,nonce=MTMzNT,uri=http://127.0.0.1:8080,response=51f98c,opaque=b29a30,algorithm=md5>
YoucanusethesedigestheadersinmessagessentbyCitruslikethis:
<header><elementname="citrus_http_Authorization"value="vflig:digestAuthHeader('$username','$password','$authRealm','$nonceKey','POST','$uri','$opaque','$algorithm')"/></header>
ThiswillsetaHttpAuthorizationheaderwiththerespectivedigestintherequestmessage.Soyourtestisreadyforclientdigestauthentication.
localHostAddress()
Testcasesmayusethelocalhostaddressforsomereason(e.g.usedasauthenticationprinciple).Asthetestsmayrunondifferentmachinesatthesametimewecannotusestatichostaddresses.TheprovidedfunctionlocalHostAddress()readsthelocalhostnamedynamicallyatruntime.
<variablename="address"value="citrus:localHostAddress()"/>
CitrusReferenceGuide
473Functions
ApossiblevalueiseitherthehostnameasusedinDNSentryoranIPaddressvalue:
address=<192.168.2.100>
changeDate()
Thisfunctionworkswithdatevaluesandmanipulatesthoseatruntimebyaddingorremovingadatevalueoffset.Youcanmanipulateseveraldatefieldssuchas:year,month,day,hour,minuteorsecond.
Letusclarifythiswithasimpleexampleforthisfunction:
<echo><message>citrus:changeDate('01.01.2000','+1y+1M+1d')</message></echo><echo><message>citrus:changeDate(citrus:currentDate(),'-1M')</message></echo>
Functionoutput:
02.02.200113.04.2013
Asyoucanseethechangedatefunctionworksonstaticdatevaluesordynamicvariablevaluesorfunctionslikecitrus:currentDate().Bydefaultthechangedatefunctionrequiresadateformatsuchasthecurrentdatefunction('dd.MM.yyyy').Youcanalsodefineacustomdateformat:
<echo><message>citrus:changeDate('2000-01-10','-1M-1d','yyyy-MM-dd')</message></echo>
Functionoutput:
1999-12-09
Withthisyouareabletomanipulatealldatevaluesofstaticordynamicnatureattestruntime.
readFile()
CitrusReferenceGuide
474Functions
ThereadFilefunctionreadsafileresourcefromgivenfilepathandloadsthecompletefilecontentasfunctionresult.Thefilepathcanbeasystemfilepathaswellasaclasspathfileresource.Thefilepathcanhavetestvariablesaspartofthepathorfilename.Inadditiontothatthefilecontentcanalsohavetestvariablevaluesandotherfunctions.
Let'sseethisfunctioninaction:
<echo><message>citrus:readFile('classpath:some/path/to/file.txt')</message></echo><echo><message>citrus:readFile($filePath)</message></echo>
Thefunctionreadsthefilecontentandplacesthecontentatthepositionwherethefunctionhasbeencalled.ThismeansthatyoucanalsousethisfunctionaspartofStringsandmessagepayloadsforinstance.Thisisaverypowerfulwaytoextractlargemessagepartstoseparatefileresources.JustaddthereadFilefunctionsomewheretothemessagecontentandCitruswillloadtheextrafilecontentandplaceitrightintothemessagepayloadforyou.
message()
WhenmessagesareexchangedinCitrusthecontentisautomaticallysavedtoaninmemorystorageforfurtheraccessinthetestcase.Thatmeansthatfunctionsandtestactionscanaccessthemessagesthathavebeensentorreceivedwithinthetestcase.Themessagefunctionloadsamessagecontentfromthatmessagestore.Themessageisidentifiedbyitsname.Receiveandsendactionsusuallydefinethemessagename.Nowwecanloadthemessagepayloadwiththatname.
Let'sseethisfunctioninaction:
<echo><message>citrus:message(myRequest.payload())</message></echo>
ThefunctionaboveloadsthemessagenamedmyRequestfromthelocalmemorystore.Thisrequiresasendorreceiveactiontohavehandledthemessagebeforeinthesametestcase.
CitrusReferenceGuide
475Functions
XMLDSL
<sendendpoint="someEndpoint"><messagename="myRequest"><payload>Somepayload</payload></message></send>
JavaDSL
send("someEndpoint").name("myRequest").payload("Somepayload");
Thenameofthemessageisimportant.Otherwisethemessagecannotbefoundinthelocalmessagestore.Note:amessagecaneitherbereceivedorsentwithanameinordertobestoredinthelocalmessagestore.Themessagefunctionisthenabletoaccessthemessagebyitsname.Inthefirstexamplethepayload()hasbeenloaded.Ofcoursewecanalsoaccessheaderinformation.
<echo><message>citrus:message(myRequest.header('Operation'))</message></echo>
ThesampleaboveloadstheheaderOperationofthemessage.
InJavaDSLthemessagestoreisalsoaccessibleovertheTestContext.
xpath()
ThexpathfunctionevaluatesaXpathexpressionsonsomeXMLsourceandreturnstheexpressionresultasString.
<echo><message><![CDATA[citrus:xpath('<message><id>1000</id></text>Sometextcontent</text></message>','/message/id')]]></echo>
TheXMLsourceisgivenasfirstfunctionparameterandcanbeloadedindifferentways.IntheexampleaboveastaticXMLsourcehasbeenused.WecouldloadtheXMLcontentfromexternalfileorjustuseatestvariable.
CitrusReferenceGuide
476Functions
<echo><message><![CDATA[citrus:xpath(citrus:readFile('some/path/to/file.xml'),'/message/id')]]></echo>
Alsoaccessingthelocalmessagestoreisvalidhere:
<echo><message><![CDATA[citrus:xpath(citrus:message(myRequest.payload()),'/message/id')]]></message</echo>
Thiscombinationisquitepowerfulasallpreviouslyexchangedmessagesinthetestareautomaticallystoredtothelocalmessagestore.Reusingdynamicmessagevaluesfromothermessagesbecomesveryeasythen.
jsonPath()
ThejsonPathfunctionevaluatesaJsonPathexpressionsonsomeJSONsourceandreturnstheexpressionresultasString.
<echo><message><![CDATA[citrus:jsonPath('"message":"id":1000,"text":"Sometextcontent"','$.message.id')]]></echo>
TheJSONsourceisgivenasfirstfunctionparameterandcanbeloadedindifferentways.IntheexampleaboveastaticJSONsourcehasbeenused.WecouldloadtheJSONcontentfromexternalfileorjustuseatestvariable.
<echo><message><![CDATA[citrus:jsonPath($jsonSource,'$.message.id')]]></message></echo>
Alsoaccessingthelocalmessagestoreisvalidhere:
<echo><message><![CDATA[citrus:jsonPath(citrus:message(myRequest.payload()),'$.message.id')]]></echo>
CitrusReferenceGuide
477Functions
Thiscombinationisquitepowerfulasallpreviouslyexchangedmessagesinthetestareautomaticallystoredtothelocalmessagestore.Reusingdynamicmessagevaluesfromothermessagesbecomesveryeasythen.
CitrusReferenceGuide
478Functions
ValidationmatcherMessagevalidationinCitrusisessential.Theframeworkoffersseveralvalidationmechanismsfordifferentmessagetypesandformats.Withtestvariablesweareabletocheckforsimplevalueequality.Weensurethatmessageentriesareequaltopredefinedexpectedvalues.Validationmatcheraddpowerfulassertionfunctionalityontopofthat.YoujustcanusethepredefinedvalidationmatcherfunctionalitiesinordertoperformmorecomplexassertionslikecontainsorisNumberinyourvalidationstatements.
ThefollowingsectionsdescribetheCitrusdefaultvalidationmatcherimplementationsthatarereadyforusage.Thematcherimplementationsshouldcoverthebasicassertionsoncharactersequencesandnumbers.Ofcourseyoucanaddcustomvalidationmatcherimplementationsinordertomeetyourveryspecificvalidationassertions,too.
Firstofallletushavealookatavalidationmatcherstatementinactionsoweunderstandhowtousetheminatestcase.
<message><payload><RequestMessage><MessageBody><Customer><Id>@greaterThan(0)@</Id><Name>@equalsIgnoreCase('foo')@</Name></Customer></MessageBody></RequestMessage></payload></message>
Thelistingabovedescribesanormalmessagevalidationblockinsideareceivetestaction.WeusesomeinlinemessagepayloadtemplateasCDATA.AsyouknowCitruswillcomparetheactualmessagepayloadtothisexpectedtemplateinDOMtreecomparison.Inadditiontothatyoucansimplyincludevalidationmatcherstatements.ThemessageelementIdisautomaticallyvalidatedtobeanumbergreaterthanzeroandtheNamecharactersequenceissupposedtomatch'foo'ignoringcasespellingconsiderations.
CitrusReferenceGuide
479ValidationMatchers
Pleasenotethespecialvalidationmatchersyntax.Thestatementsaresurroundedwith'@'markersandareidentifiedbysomeuniquename.Theoptionalparameterspassedtothematcherimplementationstatetheexpectedvaluestomatch.
TipYoucanusevalidationmatcherwithallvalidationmechanisms-notonlywithXMLvalidation.Plaintext,JSON,SQLresultsetvalidationarealsosupported.
Asetofvalidationmatcherimplementationsisusuallycombinedtoavalidationmatcherlibrary.Thelibraryhasaprefixthatwillidentifythevalidationmatcherinsidethetestcase.Thedefaulttestframeworkvalidationmatcherlibraryusesadefaultprefix(citrus).Youcanwriteyourownvalidationmatcherlibraryusingyourownprefixinordertoextendthetestframeworkfunctionalitywheneveryouwant.
ThelibraryisbuiltintheSpringconfigurationandcontainsasetofvalidationmatcherthatareofpublicuse.
<citrus:validationmatcher-libraryid="testMatcherLibrary"prefix="foo:"><citrus:matchername="isNumber">class="com.consol.citrus.validation.matcher.core.IsNumberValidationMatcher"/><citrus:matchername="contains">class="com.consol.citrus.validation.matcher.core.ContainsValidationMatcher"/><citrus:matchername="customMatcher">ref="customMatcherBean"/>...</citrus:validationmatcher-library>
AsyoucanseethelibrarydefinesonetomanyvalidationmatchermemberseitherreferencedasnormalSpringbeanorbyitsimplementingJavaclassname.Citrusconstructsthelibraryandyouareabletousethevalidationmatcherinyourtestcasewiththeleadinglibraryprefixjustlikethis:
@foo:isNumber()@@foo:contains()@@foo:customMatcher()@
TipYoucanaddcustomvalidationmatcherimplementationsandcustomvalidationmatcherlibraries.Justuseacustomprefixforyourlibrary.ThedefaultCitrusvalidationmatcherlibraryusesnoprefix.SeenowthefollowingsectionsdescribingthedefaultvalidationvalidationmatcherinCitrus.
ignore()
CitrusReferenceGuide
480ValidationMatchers
Theignorevalidationmatcherisaspecialmatcherthatignoresthevalueandisalwayspositiveinitsoutcome.Youshouldusetheignorevalidationmatcherwhenonlyvalidatingthepureexistenceofanelement.Thevalueisignoredbuttheelementhastobepresentinthemessagepayload.
<message><payload><RequestMessage><MessageBody><Customer><Id>@ignore()@</Id><Name>@equalsIgnoreCase('foo')@</Name></Customer></MessageBody></RequestMessage></payload></message>
Note
Theignorevalidationmatcheristheonlyvalidationmatcherthatisabletoskipthefunctionparameterbody.Soyoucanuseboth@ignore()@and@ignore@.
matchesXml()
TheXMLvalidationmatcherimplementationisthepossiblymostexcitingone,aswecanvalidatenestedXMLwithfullvalidationpower(e.g.ignoringelements,variablesupport).ThematcherchecksanestedXMLfragmenttocompareagainstexpectedXML.ForinstancewereceivefollowingXMLmessagepayloadforvalidation:
<GetCustomerMessage><CustomerDetails><Id>5</Id><Name>Christoph</Name><Configuration><![CDATA[<config><premium>true</premium><last-login>2012-02-24T23:34:23</last-login><link>http://www.citrusframework.org/customer/5</link></config>]]></Configuration></CustomerDetails></GetCustomerMessage>
CitrusReferenceGuide
481ValidationMatchers
AsyoucanseethemessagepayloadcontainssomeconfigurationasnestedXMLdatainaCDATAsection.WecouldvalidatethisCDATAsectionasstaticcharactersequencecomparison,true.Butthetimestampchangesitsvaluecontinuously.ThisbreaksthestaticvalidationforCDATAelementsinXML.FortunatelythenewXMLvalidationmatcherprovidesasolutionforus:
<message><payload><GetCustomerMessage><CustomerDetails><Id>5</Id><Name>Christoph</Name><Configuration>citrus:cdataSection('@matchesXml('<config><premium>$isPremium</premium><last-login>@ignore@</last-login><link>http://www.citrusframework.org/customer/5</link></config>')@')</Configuration></CustomerDetails></GetCustomerMessage></payload></message>
WiththevalidationmatcheryouareabletovalidatethenestedXMLwithfullvalidationpower.IgnoringelementsispossibleandwecanalsousevariablesinourcontrolXML.
NoteNestedCDATAelementswithinotherCDATAsectionsarenotallowedbyXMLstandard.ThisiswhywecreatethenestedCDATAsectionontheflywiththefunctioncdataSection().###equalsIgnoreCase()
Thismatcherimplementationchecksforequalitywithoutanycasespellingconsiderations.Thematcherexpectsasingleparameterastheexpectedcharactersequencetocheckfor.
<value>@equalsIgnoreCase('foo')@</value>
contains()
Thismatchersearchesforacharactersequenceinsidetheactualvalue.Ifthecharactersequenceisnotfoundsomewherethematcherstartscomplaining.
<value>@contains('foo')@</value>
CitrusReferenceGuide
482ValidationMatchers
Thevalidationmatcheralsoexistinacaseinsensitivevariant.
<value>@containsIgnoreCase('foo')@</value>
startsWith()
Thematcherimplementationassertsthatthegivenvaluestartswithacharactersequenceotherwisethematcherwillarisesomeerror.
<value>@startsWith('foo')@</value>
endsWith()
Endswithmatchervalidatesavaluetoendwithagivencharactersequence.
<value>@endsWith('foo')@</value>
matches()
Youcancheckavaluetomeetaregularexpressionwiththisvalidationmatcher.Thisisforinstanceveryusefulforemailaddressvalidation.
<value>@matches('[a-z0-9]')@</value>
matchesDatePattern()
Datevaluesarealwaysdifficulttocheckforequality.Especiallywhenyouhavemillisecondtimestampstodealwith.Thereforethedatepatternvalidationmatchershouldhavesomeimprovementforyou.Yousimplyvalidatethedateformatpatterninsteadofcheckingfortotalequality.
<value>@matchesDatePattern('yyyy-MM-dd')@</value>
Theexamplelistingusesadateformatpatternthatisexpected.Theactualdatevalueisparsedaccordingtothispatternandmaycauseerrorsincasethevalueisnovaliddatematchingthedesiredformat.
CitrusReferenceGuide
483ValidationMatchers
isNumber()
Checkingonvaluestobeofnumericnatureisessential.Theactualvaluemustbeanumericnumberotherwisethematcherraiseserrors.Thematcherimplementationdoesnotevaluateanyparameters.
<value>@isNumber()@</value>
lowerThan()
Thismatcherchecksanumbertobelowerthanagiventhresholdvalue.
<value>@lowerThan(5)@</value>
greaterThan()
Thematcherimplementationwillcheckonnumericvaluestobegreaterthanaminimumvalue.
<value>@greaterThan(5)@</value>
isWeekday()
Thematcherworksondatevaluesandchecksthatagivendateevaluatestotheexpecteddayoftheweek.Theuserdefinestheexpecteddaybyitsnameinuppercasecharacters.Thematcherfailsincasethegivendateisanotherweekdaythanexpected.
<someDate>@isWeekday('MONDAY')@</someDate>
Possiblevaluesfortheexpecteddayoftheweekare:MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAYorSUNDAY.
Thefieldvaluehastobeadatevalueotherwisethematcherwillfailtoparsethedate.Thematcherrequiresadateformatwhichisdd.MM.yyyybydefault.Youcanchangethisdateformatasfollows:
<someDate>@isWeekday(MONDAY('yyyy-MM-dd'))@</someDate>
CitrusReferenceGuide
484ValidationMatchers
Nowthematcherusesthecustomdateformatinordertoparsethedatevalueforevaluation.Thevalidationmatcheralsoworkswithdatetimevalues.Inthiscaseyouhavetogiveavaliddatetimeformatrespectively(e.g.FRIDAY('yyyy-MM-dd'T'hh:mm:ss')).
variable()
Thisisaveryspecialvalidationmatcher.Insteadofperformingavalidationlogicyoucansavetheactualvaluepassedtothevalidationmatcherasnewtestvariable.Thiscomesveryhandyasyoucanusethematcherwhereveryouwant:JSONmessagepayloads,XMLmessagepayloads,headersandsoon.
<value>@variable('foo')@</value>
Thevalidationmatchercreatesanewvariablefoowiththeactualelementvalueasvariablevalue.Whenleavingoutthecontrolvaluethefieldnameitselfisusedasvariablename.
<date>@variable()@</date>
Thiscreatesanewvariabledatewiththeactualelementvalueasvariablevalue.
dateRange()
Thematcherworksondatevaluesandchecksthatagivendateiswithintheexpecteddaterange.Theuserdefinestheexpecteddaterangebyspecifyingafrom-date,ato-dateandoptionallyadateformat.Thematcherfailswhenthegivendateliesoutsidetheexpecteddaterange.
<someDate>@dateRange('01-12-2015','31-12-2015','dd-MM-yyyy')@</someDate>
Possiblevalidvalueswouldbe'somedate'>='01-12-2015'and'somedate'<='31-12-2015'
Thedate-formatisoptionalandwhenomitteditisassumedthatalldatesmatchthedefaultdateformatyyyy-MM-dd.Whenspecifyingacustomdateformatusejava'sdateformatasareferenceforvaliddateformats.Onlydateswereusedintheexampleabovebutwecouldjustaseasilyuseddateandtimeasshownintheexamplebelow
CitrusReferenceGuide
485ValidationMatchers
<someDate>@dateRange('2015.12.0107:00:00','2015.12.0119:00:00','yyyy.MM.ddHH:mm:ss')@</someDate
assertThat()
Hamcrestisaverypowerfulmatcherlibrarywithextraordinarymatcherimplementations.YoucanuseHamcrestmatchersalsoasCitrusvalidationmatcher.
<someValue>@assertThat(equalTo(foo))@</someValue>
InthelistingaboveweareusingtheequalTo()matcher.AllHamcrestmatchersaresurroundedbyaassertThatexpression.YouareabletocombineseveralHamcrestmatcherstheninordertoconstructverypowerfulvalidationlogic.Seethefollowingexamplesonwhatispossiblethen:
<someValue>@assertThat(equalTo(value))@</someValue><someValue>@assertThat(not(equalTo(other))@</someValue><someValue>@assertThat(is(not(other)))@</someValue><someValue>@assertThat(not(is(other)))@</someValue><someValue>@assertThat(equalToIgnoringCase(VALUE))@</someValue><someValue>@assertThat(containsString(lue))@</someValue><someValue>@assertThat(not(containsString(other)))@</someValue><someValue>@assertThat(startsWith(val))@</someValue><someValue>@assertThat(endsWith(lue))@</someValue><someValue>@assertThat(anyOf(startsWith(val),endsWith(lue)))@</someValue><someValue>@assertThat(allOf(startsWith(val),endsWith(lue)))@</someValue><someValue>@assertThat(isEmptyString())@</someValue><someValue>@assertThat(not(isEmptyString()))@</someValue><someValue>@assertThat(isEmptyOrNullString())@</someValue><someValue>@assertThat(nullValue())@</someValue><someValue>@assertThat(notNullValue())@</someValue><someValue>@assertThat(empty())@</someValue><someValue>@assertThat(not(empty())@</someValue><someValue>@assertThat(greaterThan(4))@</someValue><someValue>@assertThat(allOf(greaterThan(4),lessThan(6),not(lessThan(5)))@</someValue><someValue>@assertThat(is(not(greaterThan(5))))@</someValue><someValue>@assertThat(greaterThanOrEqualTo(5))@</someValue><someValue>@assertThat(lessThan(5))@</someValue><someValue>@assertThat(not(lessThan(1)))@</someValue><someValue>@assertThat(lessThanOrEqualTo(4))@</someValue><someValue>@assertThat(hasSize(5))@</someValue>
Citruswillautomaticallyperformvalidationmatchersontheelementvalue.Onlyifallmatchersaresatisfiedthevalidationwillpass.
CitrusReferenceGuide
486ValidationMatchers
CitrusReferenceGuide
487ValidationMatchers
DatadictionariesDatadictionariesinCitrusprovideanewwaytomanipulatemessagepayloaddatabeforeamessageissentorreceived.Thedictionarydefinesasetofkeysandrespectivevalues.Justlikeeveryotherdictionaryitisusedtotranslatethings.Inourcasewetranslatemessagedataelements.
Youcantranslatecommonmessageelementsthatareusedwidelythroughoutyourdomainmodel.AsCitrusdealswithdifferenttypesofmessagedata(e.g.XML,JSON)wehavedifferentdictionaryimplementationsthataredescribedinthenextsections.
XMLdatadictionaries
XMLdatadictionariesdoapplytoXMLmessageformatpayloads,ofcourse.IngeneralweaddadictionarytothebasicCitrusSpringapplicationcontextinordertomakethedictionaryvisibletoalltestcases:
<citrus:xml-data-dictionaryid="nodeMappingDataDictionary"><citrus:mappings><citrus:mappingpath="TestMessage.MessageId"value="$messageId"/><citrus:mappingpath="TestMessage.CorrelationId"value="$correlationId"/><citrus:mappingpath="TestMessage.User"value="Christoph"/><citrus:mappingpath="TestMessage.TimeStamp"value="citrus:currentDate()"/></citrus:mappings></citrus:xml-data-dictionary>
AsyoucanseethedictionaryisnothingbutanormalSpringbeandefinition.TheNodeMappingDataDictionaryimplementationreceivesamapofkeyvaluepairswherethekeyisamessageelementpathexpression.ForXMLpayloadsthemessageelementtreeistraversedsothepathexpressionisbuiltforanexactmessageelementinsidethepayload.Ifmatchedtherespectivevalueissetaccordinglythroughthedictionary.
Besidesdefiningthedictionarykeyvaluemappingsaspropertymapinsidethebeandefinitionwecanextractthemappingdatatoanexternalfile.
<citrus:xml-data-dictionaryid="nodeMappingDataDictionary"><citrus:mapping-filepath="classpath:com/consol/citrus/sample.dictionary"/></citrus:xml-data-dictionary>
ThemappingfilecontentjustlookslikeanormalpropertyfileinJava:
CitrusReferenceGuide
488Datadictionary
TestMessage.MessageId=$messageIdTestMessage.CorrelationId=$correlationIdTestMessage.User=ChristophTestMessage.TimeStamp=citrus:currentDate()
YoucansetanymessageelementvalueinsidetheXMLmessagepayload.ThepathexpressionalsosupportsXMLattributes.Justusetheattributenameaslastpartofthepathexpression.LetushaveacloserlookatasampleXMLmessagepayloadwithattributes:
<TestMessage><Username="Christoph"age="18"/></TestMessage>
WiththissampleXMLpayloadgivenwecanaccesstheattributesinthedatadictionaryasfollows:
<citrus:mappingpath="TestMessage.User.name"value="$userName"/><citrus:mappingpath="TestMessage.User.age"value="$userAge"/>
TheNodeMappingDataDictionaryimplementationiseasytouseandfitsthebasicneedsforXMLdatadictionaries.Themessageelementpathexpressionsareverysimpleanddofitbasicneeds.HoweverwhenmorecomplexXMLpayloadsapplyfortranslationwemightreachtheboundarieshere.
FormorecomplexXMLmessagepayloadsXPathdatadictionariesareveryeffective:
<citrus:xpath-data-dictionaryid="xpathMappingDataDictionary"><citrus:mappings><citrus:mappingpath="//TestMessage/MessageId"value="$messageId"/><citrus:mappingpath="//TestMessage/CorrelationId"value="$correlationId"/><citrus:mappingpath="//TestMessage/User"value="Christoph"/><citrus:mappingpath="//TestMessage/User/@id"value="123"/><citrus:mappingpath="//TestMessage/TimeStamp"value="citrus:currentDate()"/></citrus:mappings></citrus:xpath-data-dictionary>
AsexpectedXPathmappingexpressionsarewaymorepowerfulandcanalsohandleverycomplexscenarioswithXMLnamespaces,attributesandnodelists.JustlikethenodemappingdictionarytheXPathmappingdictionarydoesalsosupportvariables,functionsandanexternalmappingfile.
CitrusReferenceGuide
489Datadictionary
XPathworksfinewithnamespaces.IngeneralitisgoodpracticetodefineanamespacecontextwhereyoumapnamespaceURIvalueswithprefixvalues.SoyourXPathexpressionisalwaysexactandevaluationisstrict.InCitrustheNamespaceContextBuilderwhichisalsoaddedasnormalSpringbeantotheapplicationcontextmanagesnamespacesusedinyourXPathexpressions.SeeourXMLandXPAthchaptersinthisdocumentationfordetaileddescriptionhowtoaccomplishfailsafeXPathexpressionswithnamespaces.
ThiscompletestheXMLdatadictionaryusageinCitrus.Lateronwewillseesomemoreadvanceddatadictionaryscenarioswherewewilldiscusstheusageofdictionaryscopesandmappingstrategies.ButbeforethatletushavealookatothermessageformatslikeJSONmessages.
JSONdatadictionaries
JSONdatadictionariescomplementwithXMLdatadictionaries.AsusualwehavetoaddtheJSONdatadictionarytothebasicSpringapplicationcontextfirst.OncethisisdonethedatadictionaryautomaticallyappliesforallJSONmessagepayloadsinCitrus.ThismeansthatallJSONmessagessentandreceivedgettranslatedwiththeJSONdatadictionaryimplementation.
Citrususesmessagetypesinordertoevaluatewhichdatadictionarymayfittothemessagethatiscurrentlyprocessed.Asusualyoucandefinethemessagetypedirectlyinyourtestcaseasattributeinsidethesendingandreceivingmessageaction.
LetusseeasimpledictionaryforJSONdata:
<citrus:json-data-dictionaryid="jsonMappingDataDictionary"><citrus:mappings><citrus:mappingpath="TestMessage.MessageId"value="$messageId"/><citrus:mappingpath="TestMessage.CorrelationId"value="$correlationId"/><citrus:mappingpath="TestMessage.User"value="Christoph"/><citrus:mappingpath="TestMessage.TimeStamp"value="citrus:currentDate()"/></citrus:mappings></citrus:json-data-dictionary>
ThemessagepathexpressionsdolookverysimilartothoseusedinXMLdatadictionaries.HerethepathexpressionkeysdoapplytotheJSONobjectgraph.SeethefollowingsampleJSONdatawhichperfectlyappliestothedictionaryexpressionsabove.
"TestMessage":"MessageId":"1122334455",
CitrusReferenceGuide
490Datadictionary
"CorrelationId":"100000001","User":"Christoph","TimeStamp":1234567890
ThepathexpressionswillmatchaveryspecificmessageelementinsidetheJSONobjectgraph.Thedictionarywillautomaticallysetthemessageelementvaluesthen.ThepathexpressionsareeasytouseasyoucantraversetheJSONobjectgraphveryeasy.
Ofcoursethedatadictionarydoesalsosupporttestvariables,functions.AlsoveryinterestingistheusageofJSONarrays.AJSONarrayelementisreferencedinadatadictionarylikethis:
<citrus:mappingpath="TestMessage.Users[0]"value="Christoph"/><citrus:mappingpath="TestMessage.Users[1]"value="Julia"/>
TheUserselementisaJSONarray,sowecanaccesstheelementswithindex.NestingJSONobjectsandarraysisalsosupportedsoyoucanalsohandlemorecomplexJSONdata.
TheJsonMappingDataDictionaryimplementationiseasytouseandfitsthebasicneedsforJSONdatadictionaries.Themessageelementpathexpressionsareverysimpleanddofitbasicneeds.HoweverwhenmorecomplexJSONpayloadsapplyfortranslationwemightreachtheboundarieshere.
FormorecomplexJSONmessagepayloadsJsonPathdatadictionariesareveryeffective:
<citrus:json-path-data-dictionaryid="jsonMappingDataDictionary"><citrus:mappings><citrus:mappingpath="$.TestMessage.MessageId"value="$messageId"/><citrus:mappingpath="$..CorrelationId"value="$correlationId"/><citrus:mappingpath="$..Users[0]"value="Christoph"/><citrus:mappingpath="$.TestMessage.TimeStamp"value="citrus:currentDate()"/></citrus:mappings></citrus:json-path-data-dictionary>
JsonPathmappingexpressionsarewaymorepowerfulandcanalsohandleverycomplexscenarios.YoucanapplyforallelementsnamedCorrelationIdinonesingleentryforinstance.
Dictionaryscopes
CitrusReferenceGuide
491Datadictionary
NowthatwehavelearnedhowtoadddatadictionariestoCitrusweneedtodiscusssomeadvancedtopics.Datadictionaryscopesdodefinetheboundarieswherethedictionarymayapply.Bydefaultdatadictionariesareglobalscopedictionaries.ThismeansthatthedatadictionaryappliestoallmessagessentandreceivedwithCitrus.OfcoursemessagetypesareconsideredsoXMLdatadictionariesdoonlyapplytoXMLmessagetypes.Howeverglobalscopedictionarieswillbeactivatedthroughoutalltestcasesandactions.
Youcanoverwritethedictionaryscope.Forinstanceinordertouseanexplicitscope.Whenthisisdonethedictionarywilnotapplyautomaticallybuttheuserhastoexplicitlysetthedatadictionaryinsendingorreceivingtestaction.Thiswayyoucanactivatethedictionarytoaveryspecialsetoftestactions.
<citrus:xml-data-dictionaryid="specialDataDictionary"global-scope="false"><citrus:mapping-filepath="classpath:com/consol/citrus/sample.dictionary"/></citrus:xml-data-dictionary>
Wesettheglobalscopepropertytofalsesothedictionaryishandledinexplicitscope.Thismeansthatyouhavetosetthedatadictionaryexplicitlyinyourtestactions:
XMLDSL
<sendendpoint="myEndpoint"><messagedata-dictionary="specialDataDictionary"><payload><TestMessage>HelloCitrus"/TestMessage></payload></message></send>
JavaDSLdesignerandrunner
@CitrusTestpublicvoiddictionaryTest()send(myEndpoint).payload("<TestMessage>HelloCitrus"/TestMessage>").dictionary("specialDataDictionary");
Thesampleaboveisasendingtestactionwithanexplicitdatadictionaryreferenceset.Beforesendingthemessagethedictionaryisaskedfortranslation.Soallmatchingmessageelementvalueswillbesetbythedictionaryaccordingly.Otherglobaldata
CitrusReferenceGuide
492Datadictionary
dictionariesdoalsoapplyforthismessagebuttheexplicitdictionarywillalwaysoverwritethemessageelementvalues.
Pathmappingstrategies
Anotheradvancedtopicaboutdatadictionariesisthepathmappingstrategy.WhenusingsimplepathexpressionsthedefaultstrategyisalwaysEXACT.Thismeansthatthepathexpressionhastoevaluateexactlytoamessageelementwithinthepayloaddata.Andonlythisexactmessageelementistranslated.
Youcansetyourownpathmappingstrategyinordertochangethisbehavior.ForinstanceanothermappingstrategywouldbeSTARS_WITH.Allelementsaretranslatedthatstartwithacertainpathexpression.Letusclarifythiswithanexample:
<citrus:xml-data-dictionaryid="nodeMappingDataDictionary"mapping-strategy="STARTS_WITH"><citrus:mappings><citrus:mappingpath="TestMessage.Property"value="citrus:randomString()"/></citrus:mappings></citrus:xml-data-dictionary>
NowwiththepathmappingstrategysettoSTARS_WITHallmessageelementpathexpressionsstartingwithTestMessage.Propertywillfindtranslationinthisdictionary.Followingsamplemessagepayloadwouldbetranslatedaccordingly:
<TestMessage><Property>XXX</Property><PropertyName>XXX</PropertyName><PropertyValue>XXX</PropertyValue></TestMessage>
AllchildelementsofTestMessagestartingwithPropertywillbetranslatedwiththisdatadictionary.IntheresultingmessagepayloadCitruswillusearandomstringasvaluefortheseelementsasweusedthecitrus:randomString()functioninthedictionarymapping.
ThenextmappingstrategywouldbeENDS_WITH.Nosurpriseshere-thismappingstrategylooksformessageelementsthatendwithacertainpathexpression.Againasimpleexamplewillclarifythisforyou.
<citrus:xml-data-dictionaryid="nodeMappingDataDictionary"mapping-strategy="ENDS_WITH"><citrus:mappings><citrus:mappingpath="Id"value="citrus:randomNumber()"/>
CitrusReferenceGuide
493Datadictionary
</citrus:mappings></citrus:xml-data-dictionary>
Againletusseesomesamplemessagepayloadforthisdictionaryusage:
<TestMessage><RequestId>XXX</RequestId><Properties><Property><PropertyId>XXX</PropertyId><PropertyValue>XXX</PropertyValue></Property><Property><PropertyId>XXX</PropertyId><PropertyValue>XXX</PropertyValue></Property></Properties></TestMessage>
InthissampleallmessageelementsendingwithIdwouldbetranslatedwitharandomnumber.Nomatterwhereinthemessagetreetheelementsarelocated.Thisisquiteusefulbutalsoverypowerful.Sobecarefultousethisstrategyinglobaldatadictionariesasitmaytranslatemessageelementsthatyouwouldnotexpectinthefirstplace.
CitrusReferenceGuide
494Datadictionary
TestactorsTheconceptoftestactorscametoourmindwhenreusingCitrustestcasesinend-to-endtestscenarios.UsuallyCitrussimulatesallinterfacepartnerswithinatestcasewhichisgreatforcontinuousintegrationtesting.Inend-to-endintegrationtestscenariossomeofourinterfacepartnersmayberealandalive.SomeotherinterfacepartnersstillrequireCitrussimulationlogic.
ItwouldbegreatifwecouldreusetheCitrusintegrationtestsinthistestsetupaswehavethecompletetestflowofmessagesavailableintheCitrustests.Weonlyhavetoremovethesimulatedsend/receiveactionsforthoserealinterfacepartnerapplicationswhichareavailableinourend-to-endtestsetup.
Withtestactorswehavetheopportunitytolinktestactions,inparticularsend/receivemessageactions,toatestactor.Thetestactorcanbedisabledinconfigurationveryeasyandfollowingfromthatalllinkedsend/receiveactionsaredisabled,too.OneCitrustestcaseisrunnablewithdifferenttestsetupscenarioswheredifferentpartnerapplicationsontheonehandareavailableasreallifeapplicationsandontheotherhandmyrequiresimulation.
Definetestactors
FirstthingtodoistodefineoneormoretestactorsinCitrusconfiguration.Atestactorrepresentsaparticipatingparty(e.g.interfacepartner,backendapplication).WewritethetestactorsintothecentralSpringapplicationcontext.WecanuseaspecialCitrusSpringXMLschemasodefinitionsarequiteeasy:
<citrus:actorid="travelagency"name="TRAVEL_AGENCY"/><citrus:actorid="royalairline"name="ROYAL_AIRLINE"/><citrus:actorid="smartariline"name="SMART_AIRLINE"/>
Thelistingabovedefinesthreetestactorsparticipatinginourtestscenario.AtravelagencyapplicationwhichissimulatedbyCitrusasacallingclient,thesmartairlineapplicationandaroyalairlineapplication.Nowwehavethetestactorsdefinedwecanlinkthosetomessagesender/receiverinstancesand/ortestactionswithinourtestcase.
Linktestactors
CitrusReferenceGuide
495Testactors
Weneedtolinkthetestactorstomessagesendandreceiveactionsinourtestcases.Wecandothisintwodifferentways.Firstwecansetatestactorreferenceonamessagesenderandmessagereceiver.
<citrus-jms:sync-endpointid="royalAirlineBookingEndpoint"destination-name="$royal.airline.request.queue"actor="royalairline"/>
Nowalltestactionsthatareusingthesemessagereceiverandmessagesenderinstancesarelinkedtothetestactor.Inadditiontothatyoucanalsoexplicitlylinktestactionstotestactorsinatest.
<receiveendpoint="royalAirlineBookingEndpoint"actor="royalairline"><message>[...]</message></receive>
<sendendpoint="royalAirlineBookingEndpoint"actor="royalairline"><message>[...]</message></send>
Thisexplicitlylinkstestactorstotestactionssoyoucandecidewhichlinkshouldbesetwithouthavingtorelyonthemessagereceiverandsenderconfiguration.
Disabletestactors
Usuallybothairlineapplicationsaresimulatedinourintegrationtests.Butthistimewewanttochangethisbyintroducingaroyalairlineapplicationwhichisonlineasarealapplicationinstance.SoweneedtoskipallsimulatedmessageinteractionsfortheroyalairlineapplicationinourCitrustests.Thisiseasyaswehavelinkedallsend/receiveactionstooneofourtestactors.Sowencandisabletheroyalairlinetestactorinourconfiguration:
<citrus:actorid="royalairline"name="ROYAL_AIRLINE"disabled="true"/>
Anytestactionlinkedtothistestactorisnowskipped.Asweintroducedarealroyalairlineapplicationinourtestscenariotherequestsgetansweredandthetestshouldbesuccessfulwithinthisend-to-endtestscenario.Thetravelagencyandthesmartairline
CitrusReferenceGuide
496Testactors
stillgetsimulatedbyCitrus.ThisisaperfectwayofreusingintegrationtestsindifferenttestscenarioswhereyouenableanddisablesimulatedparticipatingpartiesinCitrus.
ImportantServerportsmaybeofspecialinterestwhendealingwithdifferenttestscenarios.YoumayhavetoalsodisableaCitrusembeddedJettyserverinstanceinordertoavoidportbindingconflictsandyoumayhavetowireendpointURIsaccordinglybeforeexecutingatest.ThereallifeapplicationmaynotusethesameportandipastheCitrusembeddedserversforsimulation.
CitrusReferenceGuide
497Testactors
TestsuiteactionsAtestframeworkshouldalsoprovidethefunctionalitytodosomeworkbeforeandafterthetestrun.Youcouldthinkofpreparing/deletingthedatainadatabaseorstarting/stoppingaserverinthissectionbefore/afteratestrun.ThesetasksfitbestintotheinitializationandcleanupphasesofCitrus.
NoteItisimportanttonoticethattheCitrusconfigurationcomponentsthatwearegoingtouseinthenextsectionbelongtoaseparateXMLnamespacecitrus-test.WehavetoaddthenamespacedeclarationtotheXMLrootelementofourXMLconfigurationfileaccordingly.
<spring:beansxmlns="http://www.citrusframework.org/schema/testcase"xmlns:spring="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:citrus-test="http://www.citrusframework.org/schema/testcase"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/testcasehttp://www.citrusframework.org/schema/testcase/citrus-testcase.xsd">
[...]
</beans>
Beforesuite
Youcaninfluencethebehaviorofatestrunintheinitializationphaseactuallybeforethetestsareexecuted.Seethenextcodeexampletofindouthowitworkswithactionsthattakeplacebeforethefirsttestisexecuted:
XMLConfig
<citrus:before-suiteid="actionsBeforeSuite"><citrus:actions><!--listofactionsbeforesuite--></citrus:actions></citrus:before-suite>
CitrusReferenceGuide
498Testsuite
TheCitrusconfigurationcomponentholdsalistofCitrustestactionsthatgetexecutedbeforethetestsuiterun.YoucanaddallCitrustestactionshereasyouwoulddoinanormaltestcasedefinition.
XMLConfig
<citrus:before-suiteid="actionsBeforeSuite"><citrus:actions><citrus-test:sqldataSource="testDataSource"/><citrus-test:statement>CREATETABLEPERSON(IDinteger,NAMEchar(250))</citrus-test:statement</citrus-test:sql></citrus:actions></citrus:before-suite>
NotethatwemustusetheCitrustestcasenamespaceforthenestedtestactiondefinitions.WeaccessthedatabaseandcreateatablePERSONwhichisobviouslyneededinourtestcases.Youcanthinkofseveralactionsheretopreparethedatabaseforinstance.
TipCitrusoffersspecialstartupandshutdownactionsthatmaystartandstopserverimplementationsautomatically.ThismightbehelpfulwhendealingwithHttpserversorWebServicecontainerslikeJetty.Youcanalsothinkofstarting/stoppingaJMSbrokerbeforeatestrun.
SofarwehaveusedXMLDSLactionsinbeforesuiteconfiguration.NowifyouexclusivelywanttouseJavaDSLyoucandothesamewithaddingacustomclassthatextendsTestDesignerBeforeSuiteSupportorTestRunnerBeforeSuiteSupport.
JavaDSLdesigner
publicclassMyBeforeSuiteextendsTestDesignerBeforeSuiteSupport@OverridepublicvoidbeforeSuite(TestDesignerdesigner)designer.echo("Thisactionshouldbeexecutedbeforesuite");
ThecustomimplementationextendsTestDesignerBeforeSuiteSupportandthereforehastoimplementthemethodbeforeSuite.ThismethodaddsomeJavaDSLdesignerlogictothebeforesuite.Thedesignerinstanceisinjectedasmethodargument.Youcan
CitrusReferenceGuide
499Testsuite
useallJavaDSLmethodstothisdesignerinstance.Citruswillautomaticallyfindandexecutethebeforesuitelogic.WeonlyneedtoaddthisclasstotheSpringbeanapplicationcontext.Youcandothisexplicitly:
<beanid="myBeforeSuite"class="my.company.citrus.MyBeforeSuite"/>
OfcourseyoucanalsouseotherSpringbeanmechanismssuchascomponent-scansheretoo.TherespectivetestrunnerimplementationextendstheTestRunnerBeforeSuiteSupportandgetsatestrunnerinstanceasmethodargumentinjected.
JavaDSLrunner
publicclassMyBeforeSuiteextendsTestRunnerBeforeSuiteSupport@OverridepublicvoidbeforeSuite(TestRunnerrunner)runner.echo("Thisactionshouldbeexecutedbeforesuite");
Youcanhavemanybefore-suiteconfigurationcomponentswithdifferentidsinaCitrusproject.Bydefaultthecontainersarealwaysexecuted.Butyoucanrestricttheaftersuiteactioncontainerexecutionbydefiningasuitename,testgroupnames,environmentorsystempropertiesthatshouldmatchaccordingly:
XMLConfig
<citrus:before-suiteid="actionsBeforeSuite"suites="databaseSuite"groups="e2e"><citrus:actions><citrus-test:sqldataSource="testDataSource"/><citrus-test:statement>CREATETABLEPERSON(IDinteger,NAMEchar(250))</citrus-test:statement</citrus-test:sql></citrus:actions></citrus:before-suite>
TheabovebeforesuitecontainerisonlyexecutedwiththetestsuitecalleddatabaseSuiteorwhenthetestgroupe2eisdefined.TestgroupsandsuitenamesareonlysupportedwhenusingtheTestNGunittestframework.UnfortunatelyJUnitdoesnotallowtohookintosuiteexecutionaseasilyasTestNGdoes.Thisiswhyaftersuite
CitrusReferenceGuide
500Testsuite
actioncontainersarenotrestrictedinexecutionwhenusingCitruswiththeJUnittestframework.Youcandefinemultiplesuitenamesandtestgroupswithcommadelimitedstringsasattributevalues.
WhenusingtheJavaDSLbeforesuitesupportyoucansetsuitenamesandtestgroupfiltersbysimplycallingtherespectivesettermethodsinyourcustomimplementation.
<beanid="myBeforeSuite"class="my.company.citrus.MyBeforeSuite"><propertyname="suiteNames"><list><value>databaseSuite</value></list></property><propertyname="testGroups"><list><value>e2e</value></list></property></bean>
Environmentorsystempropertiesaredefinedaslistofkey-valuepairs.Whenspecifiedthepropertieshavetobepresentwithrespectivevalue.Incasethepropertyvalueisleftoutinconfigurationthepropertymustsimplyexistsonthesysteminordertoenablethebeforesuitesequenceinthattestrun.
XMLConfig
<citrus:before-suiteid="actionsBeforeSuite"suites="databaseSuite"groups="e2e"><citrus:env><citrus:propertyname="USER"/></citrus:env><citrus:system><citrus:propertyname="test-stage"value="e2e"/></citrus:system><citrus:actions><citrus-test:sqldataSource="testDataSource"/><citrus-test:statement>CREATETABLEPERSON(IDinteger,NAMEchar(250))</citrus-test:statement</citrus-test:sql></citrus:actions></citrus:before-suite>
IntheexampleabovethesuitesequencewillonlyapplyonenvironmentswithUSERpropertysetandthesystempropertytest-stagemustbesettoe2e.Otherwisethesequenceexecutionisskipped.
CitrusReferenceGuide
501Testsuite
Aftersuite
Atestrunmayrequirethetestenvironmenttobeclean.ThereforeitisagoodideatopurgeallJMSdestinationsorcleanupthedatabaseafterthetestruninordertoavoiderrorsinfollow-uptestruns.Justlikewepreparedsomedatainactionsbeforesuitewecancleanupthetestruninactionsafterthetestsarefinished.TheSpringbeansyntaxhereisnotsignificantlydifferenttothoseinbeforesuitesection:
XMLConfig
<citrus:after-suiteid="actionsAfterSuite"><citrus:actions><!--listofactionsaftersuite--></citrus:actions></citrus:after-suite>
Againwegivetheaftersuiteconfigurationcomponentauniqueidwithintheconfigurationandputonetomanytestactionsasnestedconfigurationelementstothelistofactionsexecutedafterthetestsuiterun.
XMLConfig
<citrus:after-suiteid="actionsAfterSuite"><citrus:actions><citrus-test:sqldataSource="testDataSource"/><citrus-test:statement>DELETEFROMTABLEPERSON</citrus-test:statement></citrus-test:sql></citrus:actions></citrus:after-suite>
WehavetousetheCitrustestcaseXMLnamespacewhendefiningnestedtestactionsinaftersuitelist.Wejustremovealldatafromthedatabasesowedonotinfluencefollow-uptests.Quitesimpleisn'tit!?
OfcoursewecanalsodefineJavaDSLaftersuiteactions.YoucandothisbyaddingacustomclassthatextendsTestDesignerAfterSuiteSupportorTestRunnerAfterSuiteSupport.
JavaDSLdesigner
publicclassMyAfterSuiteextendsTestDesignerAfterSuiteSupport@OverridepublicvoidafterSuite(TestDesignerdesigner)designer.echo("Thisactionshouldbeexecutedaftersuite");
CitrusReferenceGuide
502Testsuite
ThecustomimplementationextendsTestDesignerAfterSuiteSupportandthereforehastoimplementthemethodafterSuite.ThismethodaddsomeJavaDSLdesignerlogictotheaftersuite.Thedesignerinstanceisinjectedasmethodargument.YoucanuseallJavaDSLmethodstothisdesignerinstance.Citruswillautomaticallyfindandexecutetheaftersuitelogic.WeonlyneedtoaddthisclasstotheSpringbeanapplicationcontext.Youcandothisexplicitly:
<beanid="myAfterSuite"class="my.company.citrus.MyAfterSuite"/>
OfcourseyoucanalsouseotherSpringbeanmechanismssuchascomponent-scansheretoo.TherespectivetestrunnerimplementationextendstheTestRunnerAfterSuiteSupportandgetsatestrunnerinstanceasmethodargumentinjected.
JavaDSLrunner
publicclassMyAfterSuiteextendsTestRunnerAfterSuiteSupport@OverridepublicvoidafterSuite(TestRunnerrunner)runner.echo("Thisactionshouldbeexecutedaftersuite");
Youcanhavemanyafter-suiteconfigurationcomponentswithdifferentidsinaCitrusproject.Bydefaultthecontainersarealwaysexecuted.Butyoucanrestricttheaftersuiteactioncontainerexecutionbydefiningasuitename,testgroupnames,environmentorsystempropertiesthatshouldmatchaccordingly:
XMLConfig
<citrus:after-suiteid="actionsAfterSuite"suites="databaseSuite"groups="e2e"><citrus:actions><citrus-test:sqldataSource="testDataSource"/><citrus-test:statement>DELETEFROMTABLEPERSON</citrus-test:statement></citrus-test:sql></citrus:actions></citrus:after-suite>
CitrusReferenceGuide
503Testsuite
TheaboveaftersuitecontainerisonlyexecutedwiththetestsuitecalleddatabaseSuiteorwhenthetestgroupe2eisdefined.TestgroupsandsuitenamesareonlysupportedwhenusingtheTestNGunittestframework.UnfortunatelyJUnitdoesnotallowtohookintosuiteexecutionaseasilyasTestNGdoes.ThisiswhyaftersuiteactioncontainersarenotrestrictedinexecutionwhenusingCitruswiththeJUnittestframework.
Youcandefinemultiplesuitenamesandtestgroupswithcommadelimitedstringsasattributevalues.
WhenusingtheJavaDSLbeforesuitesupportyoucansetsuitenamesandtestgroupfiltersbysimplycallingtherespectivesettermethodsinyourcustomimplementation.
<beanid="myAfterSuite"class="my.company.citrus.MyAfterSuite"><propertyname="suiteNames"><list><value>databaseSuite</value></list></property><propertyname="testGroups"><list><value>e2e</value></list></property></bean>
Environmentorsystempropertiesaredefinedaslistofkey-valuepairs.Whenspecifiedthepropertieshavetobepresentwithrespectivevalue.Incasethepropertyvalueisleftoutinconfigurationthepropertymustsimplyexistsonthesysteminordertoenablethebeforesuitesequenceinthattestrun.
XMLConfig
<citrus:after-suiteid="actionsBeforeSuite"suites="databaseSuite"groups="e2e"><citrus:env><citrus:propertyname="USER"/></citrus:env><citrus:system><citrus:propertyname="test-stage"value="e2e"/></citrus:system><citrus:actions><citrus-test:sqldataSource="testDataSource"/><citrus-test:statement>DELETEFROMTABLEPERSON</citrus-test:statement></citrus-test:sql></citrus:actions></citrus:after-suite>
CitrusReferenceGuide
504Testsuite
IntheexampleabovethesuitesequencewillonlyapplyonenvironmentswithUSERpropertysetandthesystempropertytest-stagemustbesettoe2e.Otherwisethesequenceexecutionisskipped.
Beforetest
BeforeeachtestisexecuteditalsomightsoundreasonabletopurgeallJMSqueuesforinstance.IncaseaprevioustestfailssomemessagesmightbeleftintheJMSqueues.Alsothedatabasemightbeindirtystate.Thefollow-uptestthenwillbeconfrontedwiththeseinvalidmessagesanddata.PurgingallJMSdestinationsbeforeatestisthereforeagoodidea.Justlikewepreparedsomedatainactionsbeforesuitewecancleanupthedatabeforeateststartstoexecute.
XMLConfig
<citrus:before-testid="defaultBeforeTest"><citrus:actions><!--listofactionsbeforetest--></citrus:actions></citrus:before-test>
Thebeforetestconfigurationcomponentreceivesauniqueidandalistoftestactionsthatgetexecutedbeforeatestcaseisstarted.Thecomponentreceivesusualtestactiondefinitionsjustlikeyouwouldwritetheminanormaltestcasedefinition.Seetheexamplebelowhowtoaddtestactions.
XMLConfig
<citrus:before-testid="defaultBeforeTest"><citrus:actions><citrus-test:echo><citrus-test:message>Thisisexecutedbeforeeachtest!</citrus-test:message></citrus-test:echo></citrus:actions></citrus:before-test>
NotethatwemustusetheCitrustestcaseXMLnamespaceforthenestedtestactiondefinitions.YouhavetodeclaretheXMLnamespacesaccordinglyinyourconfigurationrootelement.Theechotestactionisnowexecutedbeforeeachtestinourtestsuiterun.
CitrusReferenceGuide
505Testsuite
Alsonoticethatwecanrestrictthebeforetestcontainerexecution.Wecanrestrictexecutionbasedonthetestname,package,testgroupsandenvironmentorsystemproperties.Seefollowingexamplehowthisworks:
XMLConfig
<citrus:before-testid="defaultBeforeTest"test="*_Ok_Test"package="com.consol.citrus.longrunning.*"<citrus:actions><citrus-test:echo><citrus-test:message>Thisisexecutedbeforeeachtest!</citrus-test:message></citrus-test:echo></citrus:actions></citrus:before-test>
Theabovebeforetestcomponentisonlyexecutedfortestcasesthatmatchthenamepattern*_Ok_Testandthatmatchthepackagecom.consol.citrus.longrunning.*.Alsowecouldjustusethetestnamepatternorthepackagenamepatternexclusively.Andtheexecutioncanberestrictedbasedontheincludedtestgroupsinourtestsuiterun.Thisenablesustospecifybeforetestactionsinvariousways.Ofcourseyoucanhavemultiplebeforetestconfigurationcomponentsatthesametime.Citruswillpicktherightcontainersandputittoexecutionwhennecessary.
Environmentorsystempropertiesaredefinedaslistofkey-valuepairs.Whenspecifiedthepropertieshavetobepresentwithrespectivevalue.Incasethepropertyvalueisleftoutinconfigurationthepropertymustsimplyexistsonthesysteminordertoenablethebeforesuitesequenceinthattestrun.
XMLConfig
<citrus:before-testid="specialBeforeTest"><citrus:env><citrus:propertyname="USER"/></citrus:env><citrus:system><citrus:propertyname="test-stage"value="e2e"/></citrus:system><citrus:actions><citrus-test:echo><citrus-test:message>Thisisexecutedbeforeeachtest!</citrus-test:message></citrus-test:echo></citrus:actions></citrus:before-test>
CitrusReferenceGuide
506Testsuite
IntheexampleabovethetestsequencewillonlyapplyonenvironmentswithUSERpropertysetandthesystempropertytest-stagemustbesettoe2e.Otherwisethesequenceexecutionisskipped.
WhenusingtheJavaDSLweneedtoimplementthebeforetestlogicinaseparateclassthatextendsTestDesignerBeforeTestSupportorTestRunnerBeforeTestSupport
JavaDSLdesigner
publicclassMyBeforeTestextendsTestDesignerBeforeTestSupport@OverridepublicvoidbeforeTest(TestDesignerdesigner)designer.echo("Thisactionshouldbeexecutedbeforeeachtest");
AsyoucanseetheclassimplementsthemethodbeforeTestthatisprovidedwithatestdesignerargument.YousimplyaddthebeforetestactionstothedesignerinstanceasusualbycallingJavaDSLmethodsonthedesignerobject.Citruswillautomaticallyexecutetheseoperationsbeforeeachtestisexecuted.ThesamelogicappliestothetestrunnervariationthatextendsTestRunnerBeforeTestSupport:
JavaDSLrunner
publicclassMyBeforeTestextendsTestRunnerBeforeTestSupport@OverridepublicvoidbeforeTest(TestRunnerrunner)runner.echo("Thisactionshouldbeexecutedbeforeeachtest");
ThebeforetestimplementationsareaddedtotheSpringbeanapplicationcontextforgeneralactivation.YoucandothiseitherasexplicitSpringbeandefinitionorviapackagecomponent-scan.Hereisasampleforaddingthebeanimplementationexplicitlywithsomeconfiguration
<beanid="myBeforeTest"class="my.company.citrus.MyBeforeTest"><propertyname="packageNamePattern"value="com.consol.citrus.e2e"></property></bean>
CitrusReferenceGuide
507Testsuite
WecanaddfilterpropertiestothebeforetestJavaDSLactionssotheyappliedtospecificpackagesortestnamepatterns.Theaboveexamplewillonlyapplytotestsinpackagecom.consol.citrus.e2e.Leavethesepropertiesemptyfordefaultactionsthatareexecutedbeforealltests.
Aftertest
Thesamelogicthatappliestothebefore-testconfigurationcomponentcanbedoneaftereachtest.Theafter-testconfigurationcomponentdefinestestactionsexecutedaftereachtest.Justlikewepreparedsomedatainactionsbeforeatestwecancleanupthedataafteratesthasfinishedexecution.
XMLConfig
<citrus:after-testid="defaultAfterTest"><citrus:actions><!--listofactionsaftertest--></citrus:actions></citrus:after-test>
Theaftertestconfigurationcomponentreceivesauniqueidandalistoftestactionsthatgetexecutedafteratestcaseisfinished.Noticethattheaftertestactionsareexecutednomatterwhatresultsuccessorfailuretheprevioustestcasecameupto.Thecomponentreceivesusualtestactiondefinitionsjustlikeyouwouldwritetheminanormaltestcasedefinition.Seetheexamplebelowhowtoaddtestactions.
XMLConfig
<citrus:after-testid="defaultAfterTest"><citrus:actions><citrus-test:echo><citrus-test:message>Thisisexecutedaftereachtest!</citrus-test:message></citrus-test:echo></citrus:actions></citrus:after-test>
PleasebeawareofthefactthatwemustusetheCitrustestcaseXMLnamespaceforthenestedtestactiondefinitions.YouhavetodeclaretheXMLnamespacesaccordinglyinyourconfigurationrootelement.Theechotestactionisnowexecutedaftereachtestinourtestsuiterun.Ofcoursewecanrestricttheaftertestcontainerexecution.Supportedrestrictionsarebasedonthetestname,package,testgroupsandenvironmentorsystemproperties.Seefollowingexamplehowthisworks:
CitrusReferenceGuide
508Testsuite
XMLConfig
<citrus:after-testid="defaultAfterTest"test="*_Error_Test"package="com.consol.citrus.error.*"<citrus:actions><citrus-test:echo><citrus-test:message>Thisisexecutedaftereachtest!</citrus-test:message></citrus-test:echo></citrus:actions></citrus:after-test>
Theaboveaftertestcomponentisobviouslyonlyexecutedfortestcasesthatmatchthenamepattern*_Error_Testandthatmatchthepackagecom.consol.citrus.error.*.Alsowecouldjustusethetestnamepatternorthepackagenamepatternexclusively.Andtheexecutioncanberestrictedbasedontheincludedtestgroupsinourtestsuiterun.Thisenablesustospecifyaftertestactionsinvariousways.Ofcourseyoucanhavemultipleaftertestconfigurationcomponentsatthesametime.Citruswillpicktherightcontainersandputittoexecutionwhennecessary.
Environmentorsystempropertiesaredefinedaslistofkey-valuepairs.Whenspecifiedthepropertieshavetobepresentwithrespectivevalue.Incasethepropertyvalueisleftoutinconfigurationthepropertymustsimplyexistsonthesysteminordertoenablethebeforesuitesequenceinthattestrun.
XMLConfig
<citrus:after-testid="specialAfterTest"><citrus:env><citrus:propertyname="USER"/></citrus:env><citrus:system><citrus:propertyname="test-stage"value="e2e"/></citrus:system><citrus:actions><citrus-test:echo><citrus-test:message>Thisisexecutedaftereachtest!</citrus-test:message></citrus-test:echo></citrus:actions></citrus:after-test>
IntheexampleabovethetestsequencewillonlyapplyonenvironmentswithUSERpropertysetandthesystempropertytest-stagemustbesettoe2e.Otherwisethesequenceexecutionisskipped.
CitrusReferenceGuide
509Testsuite
WhenusingtheJavaDSLweneedtoimplementtheaftertestlogicinaseparateclassthatextendsTestDesignerAfterTestSupportorTestRunnerAfterTestSupport
JavaDSLdesigner
publicclassMyAfterTestextendsTestDesignerAfterTestSupport@OverridepublicvoidafterTest(TestDesignerdesigner)designer.echo("Thisactionshouldbeexecutedaftereachtest");
AsyoucanseetheclassimplementsthemethodafterTestthatisprovidedwithatestdesignerargument.YousimplyaddtheaftertestactionstothedesignerinstanceasusualbycallingJavaDSLmethodsonthedesignerobject.Citruswillautomaticallyexecutetheseoperationsaftereachtestisexecuted.ThesamelogicappliestothetestrunnervariationthatextendsTestRunnerAfterTestSupport:
JavaDSLrunner
publicclassMyAfterTestextendsTestRunnerAfterTestSupport@OverridepublicvoidafterTest(TestRunnerrunner)runner.echo("Thisactionshouldbeexecutedaftereachtest");
TheaftertestimplementationsareaddedtotheSpringbeanapplicationcontextforgeneralactivation.YoucandothiseitherasexplicitSpringbeandefinitionorviapackagecomponent-scan.Hereisasampleforaddingthebeanimplementationexplicitlywithsomeconfiguration
<beanid="myAfterTest"class="my.company.citrus.MyAfterTest"><propertyname="packageNamePattern"value="com.consol.citrus.e2e"></property></bean>
WecanaddfilterpropertiestotheaftertestJavaDSLactionssotheyappliedtospecificpackagesortestnamepatterns.Theaboveexamplewillonlyapplytotestsinpackagecom.consol.citrus.e2e.Leavethesepropertiesemptyfordefaultactionsthatareexecutedafteralltests.
CitrusReferenceGuide
510Testsuite
CitrusReferenceGuide
511Testsuite
CustomizemetainformationTestcasesinCitrusareusuallyprovidedwithsomemetainformationliketheauthor’snameorthedateofcreation.InCitrusyouareabletoextendthistestcasemetainformationwithyourownveryspecificcriteria.
Bydefaultatestcasecomesshippedwithmetainformationthatlookslikethis:
<testcasename="PwdChange_OK_1_Test"><meta-info><author>Christoph</author><creationdate>2010-01-18</creationdate><status>FINAL</status><last-updated-by>Christoph</last-updated-by><last-updated-on>2010-01-18T15:00:00</last-updated-on></meta-info>
[...]</testcase>
Youcanquiteeasilyadddatatothissectioninordertomeetyourindividualtestingstrategy.Letushaveasimpleexampletoshowhowitisdone.
FirstofallwedefineacustomXSDschemadescribingthenewelements:
<?xmlversion="1.0"encoding="UTF-8"?><schemaxmlns="http://www.w3.org/2001/XMLSchema"xmlns:tns="http://www.citrusframework.org/samples/my-testcase-info"targetNamespace="http://www.citrusframework.org/samples/my-testcase-info"elementFormDefault="qualified">
<elementname="requirement"type="string"/><elementname="pre-condition"type="string"/><elementname="result"type="string"/><elementname="classification"type="string"/></schema>
Wehavefoursimpleelements(requirement,pre-condition,resultandclassification)alltypedasstring.Thesenewelementslatergointothetestcasemetainformationsection.
CitrusReferenceGuide
512Metainfo
AfterweaddedthenewXMLschemafiletotheclasspathofourprojectweneedtoannouncetheschematoSpring.AsyoumightknowalreadyaCitrustestcaseisnothingelsebutasimpleSpringconfigurationfilewithcustomizedXMLschemasupport.IfweaddnewelementstoatestcaseSpringneedstoknowtheXMLschemaforparsingthetestcaseconfigurationfile.Seethespring.schemasfileusuallyplacedintheMETA-INF/spring.schemasinyourproject.
Thefilecontentforourexamplewilllooklikefollows:
http://www.citrusframework.org/samples/my-testcase-info/my-testcase-info.xsd=com/consol/citrus/schemas/my-testcase-info.xsd
Sonowwearefinallyreadytousethenewmeta-infoelementsinsidethetestcase:
<?xmlversion="1.0"encoding="UTF-8"?><spring:beansxmlns="http://www.citrusframework.org/schema/testcase"xmlns:spring="http://www.springframework.org/schema/beans"xmlns:custom="http://www.citrusframework.org/samples/my-testcase-info"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/testcasehttp://www.citrusframework.org/schema/testcase/citrus-testcase.xsdhttp://www.citrusframework.org/samples/my-testcase-infohttp://www.citrusframework.org/samples/my-testcase-info/my-testcase-info.xsd">
<testcasename="PwdChange_OK_1_Test"><meta-info><author>Christoph</author><creationdate>2010-01-18</creationdate><status>FINAL</status><last-updated-by>Christoph</last-updated-by><last-updated-on>2010-01-18T15:00:00</last-updated-on><custom:requirement>REQ10001</custom:requirement><custom:pre-condition>Existinguser,sufficientrights</custom:pre-condition><custom:result>Passwordresetindatabase</custom:result><custom:classification>PasswordChange</custom:classification></meta-info>
[...]</testcase></spring:beans>
NoteWeuseaseparatenamespacedeclarationwithacustomnamespaceprefix“custom”inordertodeclarethenewXMLschematoourtestcase.Ofcourseyoucanpickanamespaceurlandprefixthatfitsbestforyourproject.Asyouseeitisquiteeasy
CitrusReferenceGuide
513Metainfo
toaddcustommetainformationtoyourCitrustestcase.Thecustomizedelementsmaybepreciousforautomaticreporting.XSLtransformationsforinstanceareabletoreadthosemetainformationelementsinordertogenerateautomatictestreportsanddocumentation.
YoucanalsodeclareournewXMLschemaintheEclipsepreferencessectionasuserspecificXMLcatalogentry.TheneventheschemacodecompletioninyourEclipseXMLeditorwillbeavailableforourcustomizedmeta-infoelements.
CitrusReferenceGuide
514Metainfo
Tracingincoming/outgoingmessagesAswedealwithmessagebasedinterfacesCitruswillsendandreceivealotofmessagesduringatestrun.NowwemaywanttoseethesemessagesinchronologicalorderastheywereprocessedbyCitrus.WecanenablemessagetracinginCitrusinordertosavemessagestothefilesystemforfurtherinvestigations.
Citrusoffersaneasywaytodebugallreceivedmessagestothefilesystem.YouneedtoenablesomespecificloggersandinterceptorsintheSpringapplicationcontext.
<beanclass="com.consol.citrus.report.MessageTracingTestListener"/>
JustaddthisbeantotheSpringconfigurationandCitruswilllistenforsentandreceivedmessagesforsavingthosetothefilesystem.Youwillfindfilesliketheseinthedefaulttest-outputfolderafterthetestrun:
Forexample:
logs/trace/messages/MyTest.msgslogs/trace/messages/FooTest.msgslogs/trace/messages/SomeTest.msgs
EachCitrustestwritesa.msgsfilecontainingallmessagesthatwentoverthewireduringthetest.Bydefaultthedebugdirectoryissettologs/trace/messages/relativetotheprojecttestoutputdirectory.Butyoucansetyourownoutputdirectoryintheconfiguration
<beanclass="com.consol.citrus.report.MessageTracingTestListener"><propertyname="outputDirectory"value="file:/path/to/folder"/></bean>
NoteAsthefilenamesdonotchangewitheachtestrunmessagetracingfilesmaybeoverwritten.Soyoueventuallyneedtosavethegeneratedmessagedebugfilesbeforerunninganothergroupoftestcases.
LetsseesomesampleoutputforatestcasewithmessagecommunicationoverSOAPHttp:
SendingSOAPrequest:
CitrusReferenceGuide
515Messagetracing
<?xmlversion="1.0"encoding="UTF-8"?><SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"<SOAP-ENV:Header><Operationxmlns="http://citrusframework.org/test">sayHello</Operation></SOAP-ENV:Header><SOAP-ENV:Body><ns0:HelloRequestxmlns:ns0="http://www.consol.de/schemas/samples/sayHello.xsd"><ns0:MessageId>0857041782</ns0:MessageId><ns0:CorrelationId>6915071793</ns0:CorrelationId><ns0:User>Christoph</ns0:User><ns0:Text>HelloWebServer</ns0:Text></ns0:HelloRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>
======================================================================
ReceivedSOAPresponse:<?xmlversion="1.0"encoding="UTF-8"?><SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"<SOAP-ENV:Header/><SOAP-ENV:Body><ns0:HelloResponsexmlns:ns0="http://www.consol.de/schemas/samples/sayHello.xsd"><ns0:MessageId>0857041782</ns0:MessageId><ns0:CorrelationId>6915071793</ns0:CorrelationId><ns0:User>WebServer</ns0:User><ns0:Text>HelloChristoph</ns0:Text></ns0:HelloResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
Forthismessagetracingtoworkweneedtoaddlogginglistenerstooursenderandreceivercomponentsaccordingly.
<citrus-ws:clientid="webServiceClient"request-url="http://localhost:8071"message-factory="messageFactory"interceptors="clientInterceptors"/>
<util:listid="clientInterceptors"><beanclass="com.consol.citrus.ws.interceptor.LoggingClientInterceptor"/></util:list>
ImportantBeawareofaddingtheSpringutilXMLnamespacetotheapplicationcontextwhenusingtheutil:listconstruct.
CitrusReferenceGuide
516Messagetracing
CitrusReferenceGuide
517Messagetracing
ReportingandtestresultsTheframeworkgeneratesdifferentreportsandresultsafteratestrunforyou.Thesereportandresultpageswillhelpyoutogetanoverviewofthetestcasesthatwereexecutedandwhichonewerefailing.
Consolelogging
Duringthetestruntheframeworkwillprovideahugeamountofinformationthatisprintedouttotheconsole.Thisincludescurrenttestprogress,validationresultsanderrorinformation.Thisenablestheusertoquicklysupervisethetestrunprogress.Failuresintestswillbeprintedtotheconsolejustthetimetheerroroccurred.Thedetailedstacktraceinformationandthedetailederrormessagesarehelpfultogettheideawhatwentwrong.
Astheconsoleoutputmightbelimitedtoadefinedbufferlimit,theusermaynotbeabletofollowtheoutputtotheverybeginningofthetestrun.Thereforetheframeworkadditionallyprintsallinformationtoalogfileaccordingtotheloggingconfiguration.
TheloggingmechanismusestheSLF4Jloggingframework.SLF4Jisindependentofloggingframeworkimplementationsonthemarket.SoincaseyouuseLog4Jloggingframeworkthespecifiedlogfilepathaswellaslogginglevelscanbefreelyconfiguredintherespectivelog4j.xmlfileinyourproject.Attheendofatestrunthecombinedtestresultsgetprintedtobothconsoleandlogfile.Theoveralltestresultslooklikefollowingexample:
CITRUSTESTRESULTS
[...]HelloService_Ok_1:SUCCESSHelloService_Ok_2:SUCCESSEchoService_Ok_1:SUCCESSEchoService_Ok_2:SUCCESSEchoService_TempError_1:SUCCESSEchoService_AutomacticRetry_1:SUCCESS[...]
Found175testcasestoexecuteSkipped0testcases(0.0%)Executed175testcasesTestsfailed:0(0.0%)
CitrusReferenceGuide
518Reporting
Testssuccessfully:175(100.0%)
Failedtestswillbemarkedasfailedintheresultlist.Theframeworkwillgiveashortdescriptionoftheerrorcausewhilethedetailedstacktraceinformationcanbefoundinthelogmessagesthatweremadeduringthetestrun.
HelloService_Ok_3:failed-ExceptionisActiontimedout
JUnitreports
AstestsareexecutedasTestNGtestcases,theframeworkwillalsogenerateJUnitcompliantXMLandHTMLreports.JUnittestreportsareverypopularandfindsupportinmanybuildmanagementanddevelopmenttools.IngeneraltheCitrustestreportsgiveyouanoverallpictureofalltestsandtellyouwhichofthemwerefailing.
BuildmanagementtoolslikeJenkinscaneasilyimportanddisplaythegeneratedJUnitXMLresults.PleasehavealookattheTestNGandJUnitdocumentationformoreinformationaboutthistopicaswellasthebuildmanagementtools(e.g.Jenkins)tofindouthowtointegratethetestsresults.
HTMLreports
CitruscreatesHTMLreportsaftereachtestrun.Thereportprovidesdetailedinformationonthetestrunwithasummaryofalltestresults.Youcanfindthereportafteratestruninthedirectory$project.build.directory/test-output/citrus-reports.
Thereportconsistsoftwoparts.Thetestsummaryontopshowsthetotalnumberexecutedtests.Themainpartlistsalltestcaseswithdetailedinformation.Withthisreportyouimmediatelyidentifyallteststhatwerefailing.Eachtestcaseismarkedincoloraccordingtoitsresultoutcome.
ThefailedtestsgivedetailederrorinformationwitherrormessagesandJavaStackTraceinformation.InadditiontothatthereporttriestofindthetestactioninsidetheXMLtestpartthatfailedinexecution.Withthefailingcodesnippetyoucanseewheretheteststopped.
NoteJavaScriptshouldbeactiveinyourwebbrowser.Thisistoenablethedetailedinformationwhichcomestoyouinformoftooltipsliketestauthorordescription.IfyouwanttoaccessthetooltipsJavaScriptshouldbeenabledinyourbrowser.
CitrusReferenceGuide
519Reporting
TheHTMLreportsarecustomizablebysystemproperties.Usefollowingpropertiese.g.inyourcitrus.propertiesfile:
citrus.html.report.enabled:Enables/disablesHTMLreportgeneration(default=true).citrus.html.report.directory:Outputdirectorypath(default=$project.build.directory/test-output/citrus-reports).citrus.html.report.file:Filenameforthereportfile(default=citrus-test-results.html).citrus.html.report.template:TemplateHTMLfilewithplaceholdersforreportresults.citrus.html.report.detail.template:Templatefilefordetailedtestresults.citrus.html.report.logo:FileresourcepathpointingtoaimagethatisaddedtotopofHTMLreport.
TheHTMLreportisbasedonatemplatefilethatiscustomizabletoyourspecialneeds.Thedefaulttemplatescanbefoundinreport-templatessources.
CitrusReferenceGuide
520Reporting
SamplesThischaptergivessomesampleswhereyoucanseeCitrusinaction.
samples-flightbooking
CitrusReferenceGuide
521Samples
TheFlightBookingsample
AsimpleprojectexampleshouldgiveyoutheideahowCitrusworks.Thesystemundertestisaflightbookingservicethathandlestravelrequestsfromatravelagency.Atravelrequestconsistsofacompletetravelrouteincludingseveralflights.TheFlightBookingServiceapplicationwillsplitthecompletetravelbookingintoseparateflightbookingsthataresenttotherespectiveairlinesincharge.Thebookingandcustomerdataispersistedinadatabase.
Theairlineswillconfirmordenytheflightbookings.TheFlightBookingServiceapplicationconsolidatesallincomingflightconfirmationsandcombinesthemtoacompletetravelconfirmationordenialthatissentbacktothetravelagency.Nextpicturetriestoputthearchitectureintographics:
InourexampletwodifferentairlinesareconnectedtotheFlightBookingServiceapplication:theSmartArilineoverJMSandtheRoyalAirlineoverHttp.
Theusecase
Theusecasethatwewouldliketotestisquitesimple.Thetestshouldhandleasimpletravelbookingandexpectapositiveprocessingtotheend.Thetestcaseneithersimulatesbusinesserrorsnortechnicalproblems.Nextpictureshowstheusecaseasasequencediagram.
CitrusReferenceGuide
522FlightBookingSample
Thetravelagencyputsatravelbookingrequesttowardsthesystem.Thetravelbookingcontainstwoseparateflights.Theflightrequestsarepublishedtotheairlines(SmartAirlineandRoyalAirline).Bothairlinesconfirmtheflightbookingswithapositiveanswer.Theconsolidatedtravelbookingresponseisthensentbacktothetravelagency.
Configurethesimulatedsystems
Citrussimulatesallsurroundingapplicationsintheirbehaviorduringthetest.Thesimulatedapplicationsare:TravelAgency,SmartAirlineandRoyalAirline.ThesimulatedsystemshavetobeconfiguredintheCitrusconfigurationfirst.TheconfigurationisdoneinSpringXMLconfigurationfiles,asCitrususesSpringtoglueallitsservicestogether.
FirstofallwehavealookattheTravelAgencyconfiguration.TheTravelAgencyisusingJMStoconnecttoourtestedsystem,soweneedtoconfigurethisJMSconnectioninCitrus.
<beanname="connectionFactory"class="org.apache.activemq.ActiveMQConnectionFactory"><propertyname="brokerURL"value="tcp://localhost:61616"/></bean>
<citrus-jms:endpointid="travelAgencyBookingRequestEndpoint"destination-name="$travel.agency.request.queue"/>
<citrus-jms:endpointid="travelAgencyBookingResponseEndpoint"destination-name="$travel.agency.response.queue"/>
CitrusReferenceGuide
523FlightBookingSample
ThisisallCitrusneedstosendandreceivemessagesoverJMSinordertosimulatetheTravelAgency.BydefaultallJMSmessagesendersandreceiversneedaconnectionfactory.ThereforeCitrusissearchingforabeannamed"connectionFactory".IntheexampleweconnecttoaActiveMQmessagebroker.AconnectiontootherJMSbrokerslikeTIBCOEMSorApacheActiveMQispossibletoobysimplychangingtheconnectionfactoryimplementation.
Theidentifiersofthemessagesendersandreceiversareveryimportant.Weshouldthinkofsuitableidsthatgivethereaderafirsthintwhatthesender/receiverisusedfor.AswewanttosimulatetheTravelAgencyincombinationwithsendingbookingrequestsouridis"travelAgencyBookingRequestEndpoint"forexample.
ThesenderandreceiversdoalsoneedaJMSdestination.Herethedestinationnamesareprovidedbypropertyexpressions.TheSpringIoCcontainerresolvesthepropertiesforus.AllweneedtodoispublishthepropertyfiletotheSpringcontainerlikethis.
<beanname="propertyLoader"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><propertyname="locations"><list><value>citrus.properties</value></list></property><propertyname="ignoreUnresolvablePlaceholders"value="true"/></bean>
Thecitrus.propertiesfileislocatedinourproject'sresourcesfolderanddefinestheactualqueuenamesbesidesotherpropertiesofcourse:
#JMSqueuestravel.agency.request.queue=Travel.Agency.Request.Queuetravel.agency.response.queue=Travel.Agency.Response.Queuesmart.airline.request.queue=Smart.Airline.Request.Queuesmart.airline.response.queue=Smart.Airline.Response.Queueroyal.airline.request.queue=Royal.Airline.Request.Queue
WhatelsedoweneedinourSpringconfiguration?TherearesomebasicbeansthatarecommonlydefinedinaCitrusapplicationbutIdonotwanttoboreyouwiththesedetails.SoifyouwanttohavealookattheSpringapplicationcontextfileintheresourcesfolderandseehowthingsaredefinedthere.
CitrusReferenceGuide
524FlightBookingSample
WecontinuewiththefirstairlinetobeconfiguredtheSmartAirline.TheSmartAirlineisalsousingJMStocommunicatewiththeFlightBookingService.Sothereisnothingnewforus,wesimplydefineadditionalJMSmessagesendersandreceivers.
<citrus-jms:endpointid="smartAirlineBookingRequestEndpoint"destination-name="$smart.airline.request.queue"/>
<citrus-jms:endpointid="smartAirlineBookingResponseEndpoint"destination-name="$smart.airline.response.queue"/>
WedonotdefineanewJMSconnectionfactorybecauseTravelAgencyandSmartAirlineareusingthesamemessagebrokerinstance.Incaseyouneedtohandlemultipleconnectionfactoriessimplydefinetheconnectionfactorywiththeattribute"connection-factory".
<citrus-jms:endpointid="smartAirlineBookingRequestEndpoint"destination-name="$smart.airline.request.queue"connection-factory="smartAirlineConnectionFactory"/>
<citrus-jms:endpointid="smartAirlineBookingResponseEndpoint"destination-name="$smart.airline.response.queue"connection-factory="smartAirlineConnectionFactory"/>
ConfiguretheHttpadapter
TheRoyalAirlineisconnectedtooursystemusingHttprequest/responsecommunication.ThismeanswehavetosimulateaHttpserverinthetestthatacceptsclientrequestsandprovidesproperresponses.CitrusoffersaHttpserverimplementationthatwilllistenonaportforclientrequests.TheadapterforwardsincomingrequesttothetestengineoverJMSandreceivesaproperresponsethatisforwardedasaHttpresponsetotheclient.Thenextpictureshowsthismechanismindetail.
TheRoyalAirlineadapterreceivesclientrequestsoverHttpandsendsthemoverJMStoamessagereceiveraswealreadyknowit.Thetestenginevalidatesthereceivedrequestandprovidesaproperresponsebacktotheadapter.Theadapterwilltransform
CitrusReferenceGuide
525FlightBookingSample
theresponsetoHttpagainandpublishesittothecallingclient.CitrusoffersthesekindofadaptersforHttpandSOAPcommunication.BywritingyourownadapterslikethisyouwillbeabletoextendCitrussoitworkswithprotocolsthatarenotsupportedyet.
LetusdefinetheHttpadapterintheSpringconfiguration:
<citrus-http:serverid="royalAirlineHttpServer"port="8091"uri="/flightbooking"endpoint-adapter="jmsEndpointAdapter"/>
<citrus-jms:endpoint-adapterid="jmsEndpointAdapterdestination-name="$royal.airline.request.queue"/>connection-factory="connectionFactory"/>timeout="2000"/>
<citrus-jms:sync-endpointid="royalAirlineBookingEndpoint"destination-name="$royal.airline.request.queue"/>
WeneedtoconfigureaHttpserverinstancewithaport,arequestURIandtheendpointadapter.WedefinetheJMSendpointadaptertohandlerequestasdescribed.InAdditiontotheendpointadapterwealsoneedsynchronousJMSmessagesenderandreceiverinstances.That'sit!WeareabletoreceiveHttprequestinordertosimulatetheRoyalAirlineapplication.Whatismissingnow?Thetestcasedefinitionitself.
Thetestcase
ThetestcasedefinitionisalsoaSpringconfigurationfile.CitrusoffersacustomizedXMLsyntaxtodefineatestcase.ThisXMLtestdefininglanguageissupposedtobeeasytounderstandandmorespecifictothedomainwearedealingwith.Nextlistingshowsthewholetestcasedefinition.Keepinmindthatatestcasedefineseverystepintheusecase.Sowedefinesendingandreceivingactionsoftheusecaseasdescribedinthesequencediagramwesawearlier.
<?xmlversion="1.0"encoding="UTF-8"?><spring:beansxmlns="http://www.citrusframework.org/schema/testcase"xmlns:spring="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.citrusframework.org/schema/testcasehttp://www.citrusframework.org/schema/testcase/citrus-testcase.xsd"><testcasename="FlightBookingTest"><meta-info>
CitrusReferenceGuide
526FlightBookingSample
<author>ChristophDeppisch</author><creationdate>2009-04-15</creationdate><status>FINAL</status><last-updated-by>ChristophDeppisch</last-updated-by><last-updated-on>2009-04-15T00:00:00</last-updated-on></meta-info><description>Testflightbookingservice.</description><variables><variablename="correlationId"value="citrus:concat('Lx1x','citrus:randomNumber(10)')"/><variablename="customerId"value="citrus:concat('Mx1x',citrus:randomNumber(10))"/></variables><actions><sendendpoint="travelAgencyBookingRequestEndpoint"><message><data><![CDATA[<TravelBookingRequestMessagexmlns="http://www.consol.com/schemas/TravelAgency"><correlationId>$correlationId</correlationId><customer><id>$customerId</id><firstname>John</firstname><lastname>Doe</lastname></customer><flights><flight><flightId>SM1269</flightId><airline>SmartAirline</airline><fromAirport>MUC</fromAirport><toAirport>FRA</toAirport><date>2009-04-15</date><scheduledDeparture>11:55:00</scheduledDeparture><scheduledArrival>13:00:00</scheduledArrival></flight><flight><flightId>RA1780</flightId><airline>RoyalAirline</airline><fromAirport>FRA</fromAirport><toAirport>HAM</toAirport><date>2009-04-15</date><scheduledDeparture>16:00:00</scheduledDeparture><scheduledArrival>17:10:00</scheduledArrival></flight></flights></TravelBookingRequestMessage>]]></data></message>
CitrusReferenceGuide
527FlightBookingSample
<header><elementname="correlationId"value="$correlationId"/></header></send>
<receiveendpoint="smartAirlineBookingRequestEndpoint"><message><data><![CDATA[<FlightBookingRequestMessagexmlns="http://www.consol.com/schemas/AirlineSchema"><correlationId>$correlationId</correlationId><bookingId>???</bookingId><customer><id>$customerId</id><firstname>John</firstname><lastname>Doe</lastname></customer><flight><flightId>SM1269</flightId><airline>SmartAirline</airline><fromAirport>MUC</fromAirport><toAirport>FRA</toAirport><date>2009-04-15</date><scheduledDeparture>11:55:00</scheduledDeparture><scheduledArrival>13:00:00</scheduledArrival></flight></FlightBookingRequestMessage>]]></data><ignorepath="//:FlightBookingRequestMessage/:bookingId"/></message><header><elementname="correlationId"value="$correlationId"/></header><extract><messagepath="//:FlightBookingRequestMessage/:bookingId"variable="$smartAirlineBookingId"/></extract></receive>
<sendendpoint="smartAirlineBookingResponseEndpoint"><message><data><![CDATA[<FlightBookingConfirmationMessagexmlns="http://www.consol.com/schemas/AirlineSchema"><correlationId>$correlationId</correlationId><bookingId>$smartAirlineBookingId</bookingId><success>true</success><flight><flightId>SM1269</flightId>
CitrusReferenceGuide
528FlightBookingSample
<airline>SmartAirline</airline><fromAirport>MUC</fromAirport><toAirport>FRA</toAirport><date>2009-04-15</date><scheduledDeparture>11:55:00</scheduledDeparture><scheduledArrival>13:00:00</scheduledArrival></flight></FlightBookingConfirmationMessage>]]></data></message><header><elementname="correlationId"value="$correlationId"/></header></send>
<receiveendpoint="royalAirlineBookingEndpoint"><message><data><![CDATA[<FlightBookingRequestMessagexmlns="http://www.consol.com/schemas/FlightBooking/AirlineSchema"><correlationId>$correlationId</correlationId><bookingId>???</bookingId><customer><id>$customerId</id><firstname>John</firstname><lastname>Doe</lastname></customer><flight><flightId>RA1780</flightId><airline>RoyalAirline</airline><fromAirport>FRA</fromAirport><toAirport>HAM</toAirport><date>2009-04-15</date><scheduledDeparture>16:00:00</scheduledDeparture><scheduledArrival>17:10:00</scheduledArrival></flight></FlightBookingRequestMessage>]]></data><ignorepath="//:FlightBookingRequestMessage/:bookingId"/></message><header><elementname="correlationId"value="$correlationId"/></header><extract><messagepath="//:FlightBookingRequestMessage/:bookingId"variable="$royalAirlineBookingId"/></extract></receive>
CitrusReferenceGuide
529FlightBookingSample
<sendendpoint="royalAirlineBookingEndpoint"><message><data><![CDATA[<FlightBookingConfirmationMessagexmlns="http://www.consol.com/schemas/AirlineSchema"><correlationId>$correlationId</correlationId><bookingId>$royalAirlineBookingId</bookingId><success>true</success><flight><flightId>RA1780</flightId><airline>RoyalAirline</airline><fromAirport>FRA</fromAirport><toAirport>HAM</toAirport><date>2009-04-15</date><scheduledDeparture>16:00:00</scheduledDeparture><scheduledArrival>17:10:00</scheduledArrival></flight></FlightBookingConfirmationMessage>]]></data></message><header><elementname="correlationid"value="$correlationId"/></header></send>
<receiveendpoint="travelAgencyBookingResponseEndpoint"><message><data><![CDATA[<TravelBookingResponseMessagexmlns="http://www.consol.com/schemas/TravelAgency"><correlationId>$correlationId</correlationId><success>true</success><flights><flight><flightId>SM1269</flightId><airline>SmartAirline</airline><fromAirport>MUC</fromAirport><toAirport>FRA</toAirport><date>2009-04-15</date><scheduledDeparture>11:55:00</scheduledDeparture><scheduledArrival>13:00:00</scheduledArrival></flight><flight><flightId>RA1780</flightId><airline>RoyalAirline</airline><fromAirport>FRA</fromAirport><toAirport>HAM</toAirport><date>2009-04-15</date><scheduledDeparture>16:00:00</scheduledDeparture>
CitrusReferenceGuide
530FlightBookingSample
<scheduledArrival>17:10:00</scheduledArrival></flight></flights></TravelBookingResponseMessage>]]></data></message><header><elementname="correlationId"value="$correlationId"/></header></receive>
</actions></testcase></spring:beans>
Similartoasequencediagramthetestcasedescribeseverystepoftheusecase.Attheverybeginningthetestcasegetsnameanditsmetainformation.Followingwiththevariablevaluesthatareusedalloverthetest.HereitisthecorrelationIdandthecustomerIdthatareusedastestvariables.Insidemessagetemplatesheadervaluesthevariablesarereferencedseveraltimesinthetest
<correlationId>$correlationId</correlationId><id>$customerId</id>
Thesending/receivingactionsuseapreviouslydefinedmessagesender/receiver.ThisisthelinkbetweentestcaseandbasicSpringconfigurationwehavedonebefore.
sendendpoint="travelAgencyBookingRequestEndpoint"
Thesendingactionchoosesamessagesendertoactuallysendthemessageusingamessagetransport(JMS,Http,SOAP,etc.).Aftersendingthisfirst"TravelBookingRequestMessage"requestthetestcaseexpectsthefirst"FlightBookingRequestMessage"messageontheSmartAirlineJMSdestination.Incasethismessageisnotarrivingintimethetestwillfailwitherrors.InpositivecaseourFlightBookingServiceworkswellandthemessagearrivesintime.Thereceivedmessageisvalidatedagainstadefinedexpectedmessagetemplate.Onlyincaseallcontentvalidationstepsaresuccessfulthetestcontinueswiththeactionchain.Andsothetestcaseproceedsandworksthroughtheusecaseuntileverymessageissentrespectivelyreceivedandvalidated.Theusecaseisdoneautomaticallywithouthumaninteraction.Citrussimulatesallsurroundingapplicationsandprovidesdetailedvalidationpossibilitiesofmessages.
CitrusReferenceGuide
531FlightBookingSample
CitrusReferenceGuide
532FlightBookingSample
AppendixThischaptergivesabriefoverviewofallarchivedchanges.
Changes2.5Changes2.4Changes2.3Changes2.2Changes2.1Changes2.0Changes1.4Changes1.3Changes1.2
CitrusReferenceGuide
533Appendix
ChangesinCitrus2.6Citrus2.6comeswithasetofnewmodulesthatenablecompletelynewaspectsofintegrationtesting.NamelythesearethenewmodulesforCucumberbehaviordrivendevelopmentandZookeepersupport.Justhavealookatthefollowingfeaturesthatareshippedwithinthe2.6box.
Gzipcompression
CitrusnowsupportsGzipmessagecompression.ForHttpclientserverendpointsweintroducedspecialcompressionfiltersthatautomaticallytakcareoncompressionwhenthehttpheaderAccept-Encoding=gziporContent-Encoding=gzipisset.Forotherendpointsweintroducedthemessagetypegzipandthemessagevalidatorgzip-base64whichautomaticallycompressesanddecompressesmessagepayloadsandenablesbase64Stringcomparisonforvalidationpurpose.Thenewcompressionfeaturesaredescribedinhttpandvalidation-gzip.
Customservletfilters
TheCitrushttpservercomponentnowacceptscustomservletfilterimplementations.Thisisusefulforimplementingcustomlogiconrequest/responseprocessingsuchasautomaticmessagecompressionorcaching.Youcansetoneormanycustomfilterimplementationsandmapthosetorequestpathsfortheserver.Readaboutthisinchapterhttp.
Escapetestvariablesyntax
Citrususestestvariablesandlooksfortheexpressionsoftype$variable-name.NowwhenthissamesyntaxispartofamessagecontentwerunintoerrorsasCitruswantstofindatestvariable.AttheendCitruscomplainsabouttheunknownvariable.ThereforeweintroducedanescapesyntaxforvariablessoyoucanskiptheCitrusvariableexpressionevaluation.Youcandothisbyusing$//escaped//syntax.Readmoreaboutthisintest-variables.
ConfigurableXMLserializer
CitrusReferenceGuide
534Changes2.6
WeoftendealwithXMLmessageformatandthereforeneedtoparseandserializeXMLdata.ThedefaultXMLserializerusesprettyprintformatandcdatasectionsupport.Nowsometimesitismandatorytocustomizethesesettingswhichispossiblewiththenewversion.YoucanaddacustomXMLserializerintheSpringapplicationcontextandCitruswillautomaticallyusethisimplementationandconfiguration.Youcanseehowitworksinchaptervalidation-xml.
Localmessagestore
Weintroducedalocalmessagestorethatautomaticallysavesallexchangedmessages(inboundandoutbound).Thismessagestorecanbeusedtogetexchangedmessagesduringandafterthetest.Testactionsaswellastestlistenerscanaccessthelocalmessagestore.Readmoreaboutthisinchaptersendpoints,actions-sendandactions-receive.
Waitmessagecondition
Thewaittestactionhasanewcondition.Besideswaitingforfilestoexistandhttprequeststoberespondedyoucannowwaitformessagesinthelocalmessagestore.Thiswayyoucanwaitforacertainmessagetoarrive.Thisisdescribedinchapteractions-wait.
XpathandJsonPathFunction
TherearenewfunctionsavailabletoevaluatesomeXpathorJsonPathexpressiononaXML/Jsonsource.Thesourcecanbeastaticstructurecomingfromanexternalfileoramessagepayloadstoredinthelocalstorage.Seehowtousethisfunctionsinchapterfunctions.
Staticresponseadaptervariablessupport
Servercomponentscanusestaticresponseadaptersthatautomaticallysendresponsemessagestoanycallingclient.Theresponseadapterisnowabletousetestvariablesandfunctions.InadditiontothatyoucanmapvaluesfromtheactualrequestmessagethathastriggeredtheresponseadapterbyusingthelocalmessagestoreincombinationwithXpathorJsonPath.Readaboutthisinendpoint-adapter.
CucumberBDDsupport
CitrusReferenceGuide
535Changes2.6
Behaviordrivendevelopmentismoreandmorecomingupalsointheintegrationtestingenvironment.CucumberisafantasticbehaviordrivendevelopmentlibrarythatprovidessupportforBDDconceptswithGherkin.ThenewCitrusintegrationwithCucumberenablesthemixofGherkinsyntaxfeaturescenarioswithCitrustestcaseexecution.YouwritefeaturestoriesasusualandcreateCitrustestcaseswithlotsofactionsfortheintegrationtest.Seedetailsforthisfeatureincucumber.
Zookeepersupport
ZookeeperfromApacheletsyoumanageconfigurationwithdistributedcoordination.AsauseryoucreateandeditvaluesonaZookeeperserver.Otherclientsthencanretrievethisinformation.WithCitrusyouareabletoaccessthisinformationfromwithinatestcase.TheZookeeperCitrusclientletsyoumanageinformationontheZookeeperserver.Seedetailsforthisfeatureinzookeeper.
SpringRestdocssupport
RestdocsisafantasticwayofgeneratingdocumentationforRESTfulAPIs.Whileexchangingrequest/responsedatawiththeserverRestdocscreatesdocumentationinformationonthedata.Thedocumentationincludesfielddescriptions,headersandsnippetsforbodycontent.WithnewCitrusversionHttpclientsinCitruscanaddRestdocinterceptorsthatgeneratethedocumentationwhileexecutingthetestcases.Thiswayyouareabletodocumentwhatmessageswereexchangedintests.TheRestdocssupportisalsoavailablefortheSOAPHttpclientinCitrus.Seedetailsinrestdocs.
Hamcrestmatcherconditions
IteratingtestactioncontainersinCitrusevaluatebooleanexpressionsfordeterminationofhowtoexecutethenestedactionsinaloop.Alsotheconditionalcontainerexecutesnestedactionsbasedonbooleanexpressionevaluation.TheCitrusbooleanexpressionsupportislimitedtoverybasicoperationssuchaslowerthanorgreaterthan.Furthermorethecombinationofbooleanexpressionswithvariableshasnotbeensupported.FollowingfromthatwehaveimprovedthebooleanexpressionevaluationmechanismwithextensiontoHamcrestmatchers.Sonowyoucanevaluatematchersiniteratingconditions.Thisfeatureisdescribedincontainers-conditionalandcontainers-iterate.
SOAPJavaDSL
CitrusReferenceGuide
536Changes2.6
CitrusprovidesanewJavafluentAPIforsendingandreceivingSOAPrelatedmessagecontent.TheJavaDSLenhancementsarebasedonthoseofHttp.NowyoucandefineSOAPmessageswithspecialSOAPactionheadersmoreeasily.OntopofthatyoucanhandleSOAPfaultsonclientandserverwiththefluentAPI.Checkoutsoap-webservicesfordetails.
Refactoring
Refactoringintermsofsimplificationandstandardizationispartofourdailylifeasadeveloper.WehavebeenworkingonimprovingtheJavaDSLfluentAPIforSOAP.Wealsointroducedamorecommonwayofhandlingthetestactioncontainerslikeiterate,parallelandsoon.Thisleadstosomeclassesandmethodsthatweremarkedasdeprecated.SopleasehavealookatyourJavaDSLcodeandifyouseesomeusageofdeprecatedstuffpleaseusethenewapproachesassoonaspossible.Thedeprecatedstuffwilldefinitelydisappearinupcomingreleases.
Someofthechangesthatwehavemademighthityourightaway.Thesechangesare:
ws:assertelementinSOAPtestcaseschemahasbeenrenamedtows:assert-fault.Thiswasdoneforbetterinteroperabilityreasonswithassertactionincoreschemaandtobecomplianttosend-faultaction.
JavaDSLmodulehashadMavendependenciestoseveralothermodulesinCitrus(e.g.citrus-jms,citrus-soap).Thesedependenciesweredeclaredascompiledependencies,whichisnotveryniceasyoumightnotneedJMSorSOAPfunctionalitiesinyourproject.Wehaveaddedoptionalandprovidedmarkerstothatdependencieswhichmeansthatyouhavetodecideinyourprojectwhichofthemodulestoinclude.
YoumayfacesomemissingdependencieserrorswhenrunningtheMavenproject.AsaresultyouneedtoincludetheCitrusmodules(e.g.citrus-http,citrus-docker,andsoon)inyourprojectMavenPOMexplicitly.
CitrusReferenceGuide
537Changes2.6
ChangesinCitrus2.5WehaveaddedlotsofnewfeaturesandimprovementswithCitrus2.5.NamelythesearethenewmodulesforRMIandJMXsupport,anewx-www-form-urlencodedmessagevalidatorandnewfunctionsanctestactions.Justhavealookatthefollowingfeaturesthatmadeittothebox.
Hamcrestmatchersupport
Hamcrestisaverypowerfulmatcherlibrarythatprovidesafantasticsetofmatcherimplementationsformessagevalidationpurpose.CitrusnowsupportsthesematcherscomingfromHamcrestlibrary.OntheonehandyoucanuseHamcrestmatchersasaCitrusvalidationmatcherasdescribedinvalidation-matcher-hamcrest.OntheotherhandyoucanuseHamcrestmatchersnowdirectlyusingtheCitrusJavaDSL.Seedetailsforthisfeatureinjson-path-validate.
Binarybase64messagevalidator
Thereisanewmessagevalidatorimplementationthatautomaticallyconvertsbinarymessagecontenttoabase64encodedStringrepresentationforcomparison.Thisistheeasiestwaytocomparebinarymessagecontentwithanexpectedmessagepayload.Seevalidation-binaryhowthisisworkingforyou.
RMIsupport
RemotemethodinvocationisastandardJavatechnologyandAPIforcallingmethodsonremoteobjectsacrossdifferentJVMinstances.AlthoughRMIhaslostitspopularityitisstillusedinlegacycomponents.TestingRMIbeaninvocationisahardthingtodo.NowCitrusprovidesclientandserversupportforremoteinterfaceinvocation.Seermifordetails.
JMXsupport
SimilartoRMIJMXcanbeusedtoconnecttoremotebeaninvocation.ThistimeweexposesomebeanstoamanagedbeanserverinordertobemanagedbyJMXoperationsforreadandwrite.WithCitrus2.5wehaveaddedaclientandserversupportforcallingandprovidingmanagedbeansonambeanserver.Seejmxfordetails.
CitrusReferenceGuide
538Changes2.5
Resourceinjection
With2.5wehaveaddedmechanismsforinjectingCitruscomponentstoyourJavaDSLtestmethods.ThisisveryusefulwhenneedingaccesstotheCitrustestcontextforinstance.Alsoweareabletousenewinjectionoftestdesignerandrunnerinstancesinordertosupportparalleltestexecutionwithmultiplethreads.Seetheexplanationsintestcase-resource-injectionandtestcase-context-injection.
Httpx-www-form-urlencodedmessagevalidator
HTMLformdatacanbetransmittedwithdifferentmethodsandcontenttypes.Oneofthemostcommonwaysistousex-www-form-urlencodedformdatacontent.Asvalidationcanbetrickywehaveaddedaspecialmessagevalidatorforthat.Seehttp-www-form-urlencodedfordetails.
Daterangevalidationmatcher
Addedanewvalidationmatcherimplementationthatisabletocheckthatadatevalueisbetweenacertaindaterange(fromandto)Thedaterangeisabletofocusondaysaswellasadditionaltime(hour,minute,second)specifications.Seevalidation-matcher-daterangefordetails.
Readfileresourcefunction
Anewfunctionimplementationoffersyouthepossibilitiestoreadfileresourcecontentsasinlinedata.Thefunctioniscalledandreturnsthefilecontentasreturnvalue.Thefilecontentisthenplacedrightwherethefunctionwascallede.g.insideofamessagepaylaodelementorasmessageheadervalue.Seefunctions-read-filefordetails.
Timercontainer
Thenewtimertestactioncontainerrepeatsitsexecutionbasedonatimeexpression(e.g.every5seconds).Withthistimerwecanrepeattestactionswithafixedtimedelayorconstantlyexecutetestactionswithtimeschedule.Seecontainers-timerandactions-stop-timerfordetails.
UpgradetoVert.x3.2.0
CitrusReferenceGuide
539Changes2.5
TheVert.xmodulewasupgradedtouseVert.x3.2.0version.TheCitrusmoduleimplementationwasupdatedtoworkwiththisnewVert.xversion.LearnmoreabouttheVert.xintegrationinCitruswithvertx.
CitrusReferenceGuide
540Changes2.5
ChangesinCitrus2.4Citrus2.4comeswithasetofnewfeaturesespeciallyregardingApacheCamelandDockerintegrations.Bugfixesofcoursearealsopartofthepackage.Seethefollowingoverviewonwhathaschanged.
Dockersupport
DockerandMicroservicesarefrequenttopicsinsoftwaredevelopmentrecently.WehaveaddedinteractionwithDockerinCitrussotheusercanmanageDockercontainerswithinatestcase.CitrusnowprovidesspecialDockertestactionsforbuilding,starting,stoppingandinspectingDockerimagesandcontainersinatest.Seedockerfordetails.
HttpRESTactions
WehavesignificantlyimprovedtheHttpRESTsupportinCitrus.ThefocusisonsimplifyingtheHttpRESTusageinCitrustestcases.WithnewHttpspecifictestactionsonclientandserverwecansendandreceiveHttpRESTmessagesveryeasy.Seehttpfordetails.
Waittestaction
Withthenewwaittestactionwecanexplicitlywaitforsomeremoteconditiontobecometrueinsideofatestcase.TheconditionssupportedatthemomentareHttpurlrequestsandfilebasedconditions.AusercaninvokeaHttpserverurlandwaitforittoreturnasuccessHttp200OKresponse.Thisisanawesomefeaturewhenwaitingforaservertostartupbeforethetestcontinues.WecanalsothinkofwaitingforaDockercontainertostartupbeforecontinuing.Oryoucanwaituntilafileispresentonthelocalfilesystem.Seeactions-waitfordetails.
Camelactions
CitrushasalreadyhadsupportforApacheCamelroutesandCamelcontextloading.Nowwith2.4versionwehaveaddedsomespecialApacheCameltestactionsforinteractingwithaCamelcontextanditsroutes.ThisenablesthetestertocreateanduseanewCamelrouteontheflyinsideatestcase.AlsoCitrusisnowabletointeractwith
CitrusReferenceGuide
541Changes2.4
theCamelcontrolbusaccessingroutestatisticsandstatusinformation.Alsopossiblearestart,stop,suspend,resumeoperationsonaCamelroute.Seecamel-actionsandcamel-controlbusfordetails.
Purgeendpointsaction
PurgingJMSqueuesandinmemorychannelsattestruntimehasbecomeawidelyusedfeatureespeciallywhenaimingtomaketestsmorestableintermsofindependenttests.Wehaveaddedapurgeendpointtestactionthatworksonanyconsumerendpoint.Soyoudonotneedtoseparatebetweenendpointimplementationsanymoreandmoreimportantyoucanpurgeserverinmemorychannelcomponentsveryeasy.Seeactions-purge-endpointsfordetails.
ReleasetoMavenCentral
Thisisnotanewfeaturebutalsoworthtotellhereasitisasignificantimprovementonthewholeframeworkproject.WecannowreleasetheCitrusartifactstoMavencentralrepository.Soyoudonotneedtheadditionallabs.consol.derepositoryinyourMavenPOManymore.Thelabs.consol.derepositorywillcontinuetoexistthoughaswewillreleaseSNAPSHOTversionsofCitrushereinfuture.
CitrusReferenceGuide
542Changes2.4
ChangesinCitrus2.3WewanttogiveyouashortoverviewofwhathaschangedinCitrus2.3.Thereleaseaddssomenewfeaturesandimprovementstothebox.Bugfixesofcoursearealsopartofthepackage.Seethefollowingoverviewonwhathaschanged.
Testrunnerandtestdesigner
OneofthebiggestissueswiththeCitrusJavaDSListhefactthattheCitrusJavaDSLmethodsfirstbuildthewholetestcasetogetherbeforetheactualexecutiontakesplace.SocallingaJavaDSLmethodsendforinstancejustpreparesthesendingtestaction.Theactualsendingofthemessagetakesplacetoalatertimewhenalltestactionsaresetupandthetestcaseisreadytorun.ThisseparationofdesigntimeandruntimeofatestcaseleadstomisunderstandingsasaJavadeveloperisusedtoworkwithstatementsandmethodcallsthatperformimmediately.BasedonthatthemixtureofCitrusJavaDSLmethodcallsandnormalJavacodelogicinyourtestmayhaveleadtounexpectedbehavior.FollowingfromthatwedecidedtorefactortheJavaDSLmethodexecution.TheresultisanewTestRunnerconceptthatexecutesallJavaDSLmethodcallsimmediately.TheoldwayofbuildingthewholetestcasebeforeexecutionisrepresentedwithTestDesignerconcept.Sobothworldsarenowavailabletoyou.Seetestcasefordetails.
WebSocketsupport
TheWebSocketmessageprotocolbuildsontopofHttpstandardandbringsbidirectionalcommunicationtotheHttpclient-serverworld.WiththisreleaseCitrususersareabletosendandreceivemessageswithWebSocketconnections.TheHttpserverimplementationisnowabletodefinemultipleWebSocketendpoints.ThenewCitrusWebSocketclientisabletopublishmessagestotheserverviabidirectionalWebSocketprotocol.Seehttp-websocketfordetails.
JSONPathsupport
CitrusisabletoworkwithXpathexpressionsinseveralfieldswithinthetestingdomain(overwriteelements,ignoreelements,extractvaluesfrompayloads).NowthissupportofmanipulatingmessagepayloadsviaexpressionsisextendedwithJSONPath.SimilartoXpaththeJSONPathexpressionstatementsenableyoutofindelementsandvalues
CitrusReferenceGuide
543Changes2.3
withinamessagepayload.NotverysurprisingtheJSONPathexpressionsworkwithJsonmessagepayloads.Withthenewreleaseyoucanoverwrite,ignoreandmanipulateJsonelementsusingJSONPathexpressions.Seejson-pathfordetails.
Customizemessagevalidators
TheframeworkoffersseveralmessagevalidatorimplementationsfordifferentmessageformatslikeXML,JSON,plaintextandsoon.InadditiontothatCitrushasasetofGroovyscriptmessagevalidators.AllthesevalidatorimplementationsareactivebydefaultsoyouareabletovalidateincomingmessagesaccordinglyinCitrus.Nowwiththisreleaseweaddedamorecomfortablewayofchangingtheframeworkvalidationfunctionality,particularwhenaddingnewcustomizedmessagevalidatorimplementations.Seevalidationfordetails.
Libraryupgrades
WehaveupgradedtheversionsofthemajordependencylibrariesofCitrus.ThisincludesTestNG,JUnit,SpringFramework,SpringWS,SpringIntegration,ApacheCamel,Arquillian,Jettyandmore.SoCitrusisnowworkingwithup-to-dateversionsofthewholemessagingandmiddlewareintegrationgang.
UpgradefromCitrus2.2
AlongwithnewfeaturesandimprovementswerefactoredandchangedsomepartsofCitrussoyoumighthavetosetthingsstraightwhenupgradingto2.3.Seethefollowinglistofthingsthatmightbebroughtuptoyou:
@CitrusTestannotation:Wehavemovedthe@CitrusTestannotationtoamorecommonpackage.Theoldpackagewascom.consol.citrus.dsl.annotations.CitrusTest.Thenewpackageiscom.consol.citrus.annotations.CitrusTest.SoyouhavetochangetheJavaimportstatementsinyourTestclasseswhenupgrading.
TestResult:WechangedtheTestResultinstantiationwhengeneratingthetestreports.TheTestResultclassnowworkswithstaticinstantiationmethodsforsuccess,skippedandfailedtests.Thisonlyaffectsyourcodewhenyouhavecreatedcustomtestreporters.
CitrusReferenceGuide
544Changes2.3
CitrusTestBuilderdeprecation:AmajorrefactoringwasdoneintheTestBuilderJavaDSLcode.com.consol.citrus.dsl.TestBuilderandallitssubclassesweremarkedasdeprecatedandwilldisappearinnextversions.Soinsteadwenowsupportcom.consol.citrus.dsl.design.TestDesignerwhichbasicallyoffersthesamefunctionalityasformerTestBuilder.InadditionthatrefactoringbroughtanewwayofexecutingtheJavaDSLtestcases.Insteadofbuildingthewholetestcasebeforeexecutionisdoneasawholeyoucannowusethecom.consol.citrus.dsl.runner.TestRunnerimplementationinordertoexecuteeachtestactionintheJavaDSLimmediately.ThisisamoreJavalikewayofwritingCitrustestcasesasyoucanmixCitrustestactionexecutionwithnormalJavastatementsasusual.Readmoreaboutthenewapproachintestcase
Bugfixes
Bugsarepartofoursoftwaredevelopersworldandfixingthemispartofyourdailybusiness,too.FindingandsolvingissuesmakesCitrusbettereveryday.ForadetailedlistingofallbugfixespleaserefertothecompletechangeslogofeachreleaseinJIRA(http://www.citrusframework.org/changes-report.html).
CitrusReferenceGuide
545Changes2.3
ChangesinCitrus2.2Citrus2.2isareleasemostlyaddingnewfeaturesaswellasimprovementstogivenCitrusfeatures.Bugfixesofcoursearealsopartofthepackage.Seethefollowingoverviewonwhathaschanged.
Arquilliansupport
ArquillianisawellknownintegrationtestframeworkthatcomeswithagreatfeaturesetwhenitcomestoJavaEEtestinginsideofafullqualifiedapplicationserver.WithArquiliianyoucandeployyourJavaEEservicesinarealapplicationserverofyourchoiceandexecutethetestsinsidetheapplicationserverboundaries.ThismakesitveryeasytotestyourJavaEEservicesinscopewithproperJNDIresourceallocationandotherresourcesprovidedbytheapplicationserver.CitrusisabletoconnectwiththeArquilliantestcase.SpeakinginmoredetailyourArquilliantestisabletouseaCitrusextensioninordertousetheCitrusfeaturesetinsidetheArquillianboundaries.Seearquillianfordetails.
JUnitsupport
CitrussupportsbothmajorplayersinunittestingTestNGandJUnit.UnfortunatelywedidnotofferthesamefeaturesupportforJUnitasitwasdoneforTestNG.NowwithCitrus2.2weimprovedtheJUnitsupportinCitrussoyouareabletouseallfeatureswithbothframeworks.Thisisespeciallyrelatedtousingthe@CitrusTestand@CitrusXmlTestmethodannotationsintestclasses.Seerun-junithowitworks.
Start/Stopserveraction
CitruswasmissingadedicatedtestactiontostartandstopCitrusservercomponentsattetruntime.Withthenewlyaddedtestactionsyouareabletostartandstopservercomponentsasyoulikewithinyourtestcase.Seeactions-manage-serverwithadetaileddescription.
CitrusAnttasks
CitrusReferenceGuide
546Changes2.2
WediscontinuetosupporttheCitrusAnttasks.TheAnttaskswerenotverystableanlackedfullfeaturesupportwhenexecutingtestcaseswithJUnitinApacheAnt.InsteadweaddedabriefdescriptiononhowtoexecuteCitrustestswiththewelldocumentedandstabledefaultJUnitandTestNGanttasks.Seesetup-using-anthowitworks.
Bugfixes
Bugsarepartofoursoftwaredevelopersworldandfixingthemispartofyourdailybusiness,too.FindingandsolvingissuesmakesCitrusbettereveryday.ForadetailedlistingofallbugfixespleaserefertothecompletechangeslogofeachreleaseinJIRA(http://www.citrusframework.org/changes-report.html).
CitrusReferenceGuide
547Changes2.2
ChangesinCitrus2.1Citrus2.1addssomeenhancementstotheCitrusfeaturesetaswellasbugfixesandimprovements.Seethefollowingoverviewonwhathaschanged.
SOAPMTOMsupport
SOAPMTOMstandsforMessageTransmissionOptimizationMechanismwhichallowsyoutosendandreceivelargeSOAPattachmentcontentsstreamedwithoptimizedresourceallocationonserverandclient.Manythankstocommunitycontributions(github/stonator)thatmadethishappenwithCitrusSOAPclientandserver.AsauseryoucanshoosetosendandreceiveSOAPattachmentswithMTOMoptimization.Seesoap-attachment-mtomfordetails.
SOAPenvelopehandling
InitsdefaultbehaviorCitruswillremovetheSOAPenvelopeforincomingSOAPrequestsjustprovidingtheSOAPbodyasmessagepayload.Thisismorestraightforwardinatestcasetoperformfurthervalidationsteps.HoweveritmightbemandatorytoseethewholeSOAPenvelopeinsidethetestcaseforspecialvalidation.AsauseryoucannowchoosehowtohandleincomingSOAPenvelopebydefinigthekeep-soap-envelopesettingontheCitrusSOAPservercomponents.Seesoap-keep-envelopefordetails.
SOAP1.2messagefactory
TheCitrusSOAPservercomponentwasmissingasettingfortheSOAPmessagefactorytouse.TheSOAPmessagefactoryimplementationdecideswhichSOAPversiontouse1.1or1.2.NowyoucansetthemessagefactoryontheservercomponentanddefinetheSOAPversiontouse.Seesoap-12fordetails.
TestNGdataproviderhandling
WeimprovedtheTestNGdataproviderhandlinginCitrus.NowyoucanusetheusualTestNGdataproviderannotationsinyourtestmethods.TestNGwillcalltheCitrustestcaseseveraltimeswithrespectiveparametersprovidedastestvariables.Thisreplaces
CitrusReferenceGuide
548Changes2.1
theoldcitrusDataProvidermechanismthattriedtomakethingsworkinginakindofworkaround.Thenewproviderhandlingalsosupportsmultipledataprovidersinatestclass.run-testng-data-providersdescribeshowthisisworkingforyou.
Mailmessagenamespace
TheCitrusmailcomponentsenablemessageexchangeasmailclientandserver.ForvalidationpurposethecomponentsofferaXMLmailmessagerepresentation.Wehaveaddedatargetnamespacexmlns="http://www.citrusframework.org/schema/mail/message"andaXSDschemaforthisXMLmailmessagerepresentation.FromnowonyouhavetousethenamespaceaccordinglyinyourmailmessagepayloadswhensendingandreceivingmailmessagesinCitrus.SeemailhowtousethenewXMLmailmessagenamespace.
Sshmessagenamespace
WhensendingandreceivingmessagesviasshCitrusprovidesaXMLrepresentationforrequestandresponsedata.Thesesshmessagesfollowanewtargetnamespacexmlns="http://www.citrusframework.org/schema/ssh/message"andaXSDschema.ThismeansyouhavetousethenamespaceaccordinglyinyoursshmessagepayloadswhensendingandreceivingsshmessagesinCitrus.Seesshforfurtherdetails.
CitrusReferenceGuide
549Changes2.1
ChangesinCitrus2.0Citrus2.0isamajorversionupgradeandthereforebigthingsshouldbehappening.InthefollowingsectionsweshortlydescribetheCitrusevolution.WewantyoutogetaquickoverviewofwhathashappenedandallthenewthingsinCitrus.Sohopefullyyoucanspotyourfavoritenewfeature.
Refactoring
InCitrus1.4webegantorefactortheconfigurationcomponentsinCitrus.ThisrefactoringwasfinalizedinCitrus2.0whichmeansthatalldeprecatedclassesandapiarenolongersupported.Theclasseswereremovedsoyougetcompilationerrorswhenusingthoseoldstuff.Ifyoustillusetheoldconfigurationseethishttp://citrusframework.org/migration-sheet.htmlinordertolearnhowtoupgradetothenewconfiguration.Itisworthtodoso!Inadditiontothatwedidrefactoringinfollowingfields:
ReplymessagecorrelationInsynchronouscommunicationscenariosCitrusoptionallycorrelatedmessagesacrosssendandreceivetestactions.Indefaultsettingthemessagecorrelationwasdisabled.With2.0releasewechangedthisbehaviortotheopposite.Nowmessagecorrelationisdonebydefaultwithadefaultcorrelationalgorithm.SoincaseyouusedtheDefaultReplyMessageCorrelatorinCitrusbeforeyouwillnothavetodosoinfutureasthisisdonebydefault.Themessagecorrelationgivesusmorerobusttestsespeciallywhenexecutingtestsinparallel.Inthetestcaseyoudonothavetodoanythingforpropermessagecorrelation.
CitrusmessageAPIWehaverefactoredtheCitrusmessageAPItousecustommessageobjectsinendpoints,consumersandproducers.Thishasnoaffectonyourtestsorconfigurationunlessyouhavewrittenendpointextensionsorcustomendpointsonyourown.Youmighthavetorefectoryourcodeaccordingly.HavealookattheCitrusendpointimplementationsinordertoseehowthenewmessageAPIworksforyou.
SleeptimeinmillisecondsThisissomethingthatwedefinitelycarryaroundsincethebeginningofCitrus.Thetimevaluesinsleeptestactionweredoneinseconds,whichisinconvenientwhenusingtimeperiodsbelowonesecondornonnatural
CitrusReferenceGuide
550Changes2.0
numbers.Nowyoucanchoosetousemillisecondswhichismorelikelyhowyoushouldconfiguretimeperiodsanyway.Seeactions-sleepfordetails
AutosleeptimeinmillisecondsWeusedsecondswhenusingautosleepinrepeatonerrorcontainer.Thisledtothefactthatwewerenotabletosleeptimeperiodsbelowonesecond.Alsoitwasnotpossibletospecifynonnaturalnumberssuchas1.5secondsautosleeptime.Wechangedtomillisecondswhichismorelikelyhowyouareusedtoconfiguretimeperiodsanyway.Seecontainers-repeat-onerrorfordetails
Messagehandlervs.endpointadapterInpreviousreleasespriorto1.4wehadmessagehandlersonserversidethatwereabletoforwardincomingrequeststomessagechannelsorjmsdestinations.Theoldmessagehandlerimplementationswereremovedin2.0.Insteadyoushouldusetheendpoint-adapterimplementations.Seeendpoint-adapterhowthatworks.
XMLendpointreferenceattributeInaXMLtestcaseyoureferencethemessageendpointinthesendandreceiveactionswithaspecialattributecalledwith.Thisattributeisnolongersupportedandwasremoved.InsteadyoushouldusetheendpointattributewhichwasintroducedinCitrus1.4andhastheexactsamefunctionality.
Removedcitrus-adaptermoduleTheMavenmodulecitrus-adapterwasremoved.ClassesandAPImovedtocitrus-coremodule.ForendpointadaptersdousethenewconfigurationcomponentsthatwereintroducedinCitrus1.4.Seeendpoint-adapterfordetails.
WebServiceEndpointclassrenamedIntermsofpackagerefactoringthecom.consol.citrus.ws.WebServiceEndpointwasrenamedtocom.consol.citrus.ws.server.WebServiceEndpoint
Springframework4.x
IntermsofupgradingtheCitrusAPIdependenciesweintroducedSpring4.xversions.ThisincludesthecoreSpringframeworklibrariesaswellastheSpringIntegrationandSpringWebServiceprojectartifacts.SowiththemajorversionupgradelotsofAPIchangeswerealsodoneinCitruscodeinordertomeetthenewSpring4.xAPI.SowerecommendforyoutoalsouseSpring4.xversioninyourCitrusprojects.
FTPsupport
CitrusReferenceGuide
551Changes2.0
NewmemberoftheCitrusfamilydealswithFTPconnectivity.Thenewcitrus-ftpmoduleprovidesaneatftpserverandclientimplementationsoyoucansendandreceivemessagesvieFTPmessagetransport.ftpdescribesthenewfunctionalityindetail.
Functionswithtestcontextaccess
Functionsarenowabletoaccessthetestcontext.Thisenablesyoutoaccessalltestvariablesandothercentraltestrelatedcomponentsinafunctionimplementation.ThereforethefunctionJavainterfacehasnowanadditionaltestcontextparameter.Refactoryourcustomwrittenfunctionsaccordinglytomeetthenewinterfacerules.Seehttp://www.citrusframework.org/tutorials-functions.htmlfordetails.
Validationmatcherwithtestcontextaccess
Justlikefunctionsnowvalidationmatchersareabletoaccessthetestcontext.Thisenablesyoutoaccessalltestvariablesandothercentraltestrelatedcomponentsinavalidationmatcherimplementation.ThevalidationmatcherJavainterfacehaschangedaccordinglywithanadditionaltestcontextparameter.Refactoryourcustomwrittenmatcherimplementationaccordinglytomeetthenewinterfacerules.
Messagelistenerwithtestcontextaccess
Messagelistenersdonowalsohaveaccesstothetestcontext.Thisismorepowerfulasyoucanaccesstestvariablesandothercentralcomponentswithinthetestcontext.
SOAPoverJMS
SOAPoverJMSwassupportedinCitrusfromtheverybeginning.UnfortunatelyyouhadtoalwaysspecifythewholeSOAPenvelopeinyourtestcase.SOAPenvelopehandlingisnowdoneautomaticallybyCitruswhenusingthenewSoapJmsMessageConverter.TheconvertertakescareonconstructingaproperSOAPenvelopemessage.Seejms-soapfordetails.
MultipleSOAPattachments
WhensendingandreceivingSOAPmessageswithCitrusasclientorserveryoucanaddonetomanyattachmentstothemessage.Beforeitwasonlypossibletohaveonesingleattachmentinamessage.NowyouhavenolimitsindefiningSOAPattachments.
CitrusReferenceGuide
552Changes2.0
Seesoap-webservicesfordetails.
MultipleSOAPXMLheaderfragments
TheSOAPheadercanholdmultipleXMLheaderfragmentswithdifferentnamespacesandcontent.WithCitrus2.0youareabletoconstructsuchaSOAPmessagewithmultipleheadercontents.Seesoap-webservicesfordetails.
Createvariablevalidationmatcher
Anewvalidationmatcherimplementationisabletocreateanewvariableonthefly.Theactualfieldnameisusedasvariablenameandtheelementvalueasvariablevalue.Thevariablenamecanslobecustomizedwithoptionalvalidationmatcherparameter.ThisisagreatalternativetotheXPathexpressionevaluatingvariableextraction.AlsoveryhandsometousethisvalidationmatcherinJsonmessagepayloads.Seevalidation-matcher-variablefordetails.
Newconfigurationcomponents
AmajorpartoftheCitrusconfigurationisdoneinaSpringbeanapplicationcontext.CentralCitruscomponentsandfeaturesareaddedasSpringbeanstotheapplicationcontext.NowwithCitrus2.0wehaveaddedspecialconfigurationcomponentsforalmostallfeatures.ThismeansthatyoucaneasilyaddconfigurationusingthenewXMLschemacomponents.Seewhichcomponentsareavailable:
FunctionlibraryCustomfunctionlibrarieswithcustomfunctionimplementationsarenowconfiguredwiththefunction-libraryXMLschemacomponentsintheSpringapplicationcontextconfiguration.Seefunctionsfordetails.
ValidationmatcherlibraryCustomvalidationmatcherimplementationsarenowconfiguredwiththevalidation-matcher-libraryXMLschemacomponentsintheSpringapplicationcontextconfiguration.Seevalidation-matchersfordetails.
DatadictionaryDatadictionariesapplytoallmessagessendandreceivedintestcases.Youcandefinemultipledictionariesusingthedata-dictionaryXMLschemacomponentsintheSpringapplicationcontextconfiguration.Seedata-dictionaryfordetails.
CitrusReferenceGuide
553Changes2.0
NamespacecontextConfigurationofaglobalnamespacecontextisnecessaryforXMLmessagepayloadsandXPathexpressionsusedinthetestcases.Thenamespace-contextXMLschemacomponentisusedintheSpringapplicationcontextconfigurationandsimplifiestheconfiguration.Seexpathfordetails.
Before/aftersuitecomponents
Whenexecutingtestactionsbeforetheactualtestrunyoucanusethesequencebeforesuitecomponents.WehaveimprovedthesecomponentstouseaspecialXMLschema.Thisenableseasyconfigurationofbothbeforeandaftersuiteactions.Inadditiontothatyoucanbindthesuiteactionstospecialpackages,testnamesorsuitenames.Soyoucannowhavemorethanonesequencebeforesuiteatthesametime.Accordingtotheenvironmentsettingsthebeforesuiteactionsareexecutedorleftout.Lastnotleastwehavedonethesameimprovementtothebeforetestactionsandwehaveintroducedaaftertestsequencecomponentforexecutionaftereachtest.Seehowthisisdoneintestsuite.
CitrusJMSmodule
JMSsupporthasbeenamajorpartofCitrusfromtheverybeginning.UptonowtheJMSfeatureswerelocatedincitrus-coreMavenmodule.WithCitrus2.0weintroducedaseparatecitrus-jmsMavenmodule.ThismeansthatyoumighthavetoaddproperMavendependencyofthisnewmoduleinyourexistingprojectwhenusingJMS.Seehowthisisdoneinjms.
CitrusReferenceGuide
554Changes2.0
ChangesinCitrus1.4.x
Refactoring
ItwastimeforustodosomecoderefactoringinCitrus.ManyusersstruggledwiththeconfigurationoftheCitruscomponentsandprojectsetupwastooverboseinsomeareas.ThisiswhywetriedtoimprovethingswithworkingoverthebasicconceptsandcomponentsinCitrus.
TheoutcomeisanewCitrus1.4whichhasnewconfigurationcomponentsforsendingandreceivingmessages.AlsotheclientandservercomponentsforHTTPandSOAPhavechangedintermsofsimplification.Unfortunatelyrefactoringcomesalongwithcodedeprecation.Thisiswhyyouhavetoalsochangeyourprojectcodeandconfigurationinthefuture.ThisisespeciallywhenyouhavemadecodeadjustmentsandextensionstotheCitrusAPI.
ThegoodnewsnowisthatwithCitrus1.4botholdandnewconfigurationworksfine,soyoudonothavetochangeyourexistingprojectconfigurationwhencomingfromCitrus1.3.xandearlierversions.ButthereisalotofcodemarkedasdeprecatedinCitrus1.4.HavealookatwhathasbeenmarkedasdeprecatedandupdateyourcodetousethenewAPI.
WehavesetupamigrationsheetforuserscomingfromCitrus1.3.xandearlierversionsinordertofindaquickoverviewofwhathaschangedandhowtousethenewconfigurationcomponents:http://citrusframework.org/migration-sheet.html
Datadictionaries
Datadictionariesdefinedynamicplaceholdersformessagepayloadelementvaluesingeneralmanner.Intermsofsettingthesamemessageelementacrossalltestcasesandalltestactionsthedictionaryprovidesaneasykey-valueapproach.
WhendealingwithanykindofmessagepayloadCitruswillaskthedatadictionaryforpossibletranslationofthemessageelementscontained.ThedictionarykeysdomatchtoaspecificmessageelementdefinedbyXPathexpressionordocumentpathexpressionforinstance.TherespectivevalueisthensetonallmessagesinCitrus(inboundandoutbound).
CitrusReferenceGuide
555Changes1.4
DictionariesdoapplytoXMLorJSONmessagedataandcanbedefinedinglobalorspecificscope.Findoutmoredetailedinformationaboutthistopicindata-dictionary
Mailadapter
Withthenewmailadapteryouareabletobothsendandreceivemailmessageswithinatestcase.ThenewCitrusmailclientproducesamailmimepartmessagewithusualmailheadersandatextbodypart.Optionalattachmentpartsaresupported,too.
OntheserversideCitrusprovidesaSMTPservertoacceptclientmailmessages.Theincomingmailmessagescanhavemultipletextpartsandattachmentparts.AsusualyoucanvalidatetheincomingmailmessagesregardingheadersandpayloadwiththewellknownvalidationcapabilitiesinCitrus.
Readmoreaboutthenewmailmoduleinmail
Endpointadapter
EndpointadaptershelptocustomizethebehaviorofaCitrusserversuchasHTTPorSOAPwebservers.Theendpointadapterisresponsibleofcreatinganendpointthatrespondstoinboundrequests.YoucancustomizethebehaviorsotheCitrusserverhandlesincomingrequestsasyoulike.
BydefaulttheCitrusserverusesachannelendpointadaptersoincomingmessagesgetforwardedtoaninmemorymessagechannel.Thereareseveralotherimplementationsavailableasendpointadapter.Readmoreaboutthatinendpoint-adapter
Globalvariablescomponent
WeaddedaglobalvariablesXMLconfigurationcomponentformorecomfortableusageinbasicSpringapplicationcontextconfiguration.ThecomponentisabletocreatenewglobalvariablesthatarevalidacrossallCitrustestcases.Thiscanalsobedonebyloadingapropertyfilefromanexternalfileresource.Findouthowtousitintestcase-global-variables
Jsontextvalidatormode
TheJsontextvalidatorisnowabletooperateintwodifferentmodes.Thestrictmodeisthedefaultmodeandvalidationincludesalsoastrictcheckonallsub-objectsandJSONarrayelements.Soifthereisanobjectmissingthevalidationwillfailimmediately.
CitrusReferenceGuide
556Changes1.4
SometimesitmaybeaccuratetoonlyvalidateasubsetofallJSONobjectsinthedatastructure.Thereforethenon-strictmodedoesnotcheckonobjectattributecounts.Seemoredescriptioninvalidation-json
HTTPRESTspecificJavaDSLoptions
WhensendingandreceivingHTTPmessagesonRESTAPIsyoucannowuseinterfacespecificoptionsintheJavaDSL.Thisreferstorequesturi,contextpath,queryparametersandHTTPstatuscodesforinstance.WiththisenhancementyouarenowmorecomfortableinhandlingRESTAPIcallsinCitrus.Findouthowtousitinhttp
SOAPHTTPvalidation
WhilereceivingSOAPmessagesoverHTTPwearenowabletoalsoverifytheusedHTTPuri,context-pathandqueryparameters.YoucanexpectclientstousethosevaluesinyourreceiveactionasyouwoulddoinnormalHTTPcommunicationwithinCitrus.ThiscompletestheHTTPservervalidationwhenusingSOAPoverHTTP.Readmoreaboutitinsoap-webservices
ApacheCamelintegration
ApacheCamelisagreatenterpriseintegrationplatformthatimplementstheenterpriseintegrationpatternsforbuildingpowerfulmediationandroutingrulesformessagebasedintegrationapplications.WiththenewsupportforcamelendpointsinCitrusyoucaninteractwithApacheCamelcomponentsforsendingandreceivingmessages.ApacheCameloffersafinesupportfordifferentmessagetransportsthatnowcanbeusedinCitrusalso.InadditiontothatyoucanputyourCamelapplicationtothetestwithloadingoftheApacheCamelcontextwithallyourroutedefinitions.Citrusisabletointeractwiththeseroutesinasynchronousandsynchronouscommunicationscenarios.Readaboutitincamel.
Vert.xintegration
Vert.xisaverypowerfulapplicationplatformthatprovidesscalablemessagingforseveralmessagetransportssuchasHTTP,WebSockets,TCPandmore.Vert.xalsohasadistributedeventbusthatconnectsmultipleVert.xinstancesacrossthenetwork.WithCitrus1.4theVert.xplatformisintegratedwithCitruseventbusendpoints.Soyou
CitrusReferenceGuide
557Changes1.4
canparticipateincommunicatingtotheVert.xeventbusfromCitrustestcase.ThisenablesyoutoaddautomatedintegrationteststotheVert.xplatform.Readaboutthatinvertx.
Dynamicendpointcomponents
EndpointsrepresentthebasecomponentinCitrusforsendingandreceivingmessages.TheendpointusuallyisdefinedinsidetheCitrusSpringapplicationcontextasSpringbeancomponent.Nowitisalsopossibletocreatedynamicendpointdefinitionsattestruntime.ThiscomesinveryhandywhenyoujustwanttosendorreceiveamessagewithCitrusasis.Youdonotneedtoaddthecompleteendpointconfigurationbutonlyuseaspecialendpointuripattern.Citruswillcreatetheendpointatruntimeautomatically.Learnhowtousethedynamicendpointpatterninendpoint-components.
CitrusReferenceGuide
558Changes1.4
ChangesinCitrus1.3.x
@CitrusTestand@CitrusXmlTestannotations
WiththenewJavaDSLcapabilitiesCitruscreatednewwaysofexecutingtestcaseswithinaTestNGorJUnittestclass.Nowweevenimprovedtheusageherewithtwonewannotations@CitrusTestandCitrusXmlTest.Theintegrationintotheunittestclasshasneverbeeneasierforyou.
ThenewCitrusannotationsgodirectlytoyourunittestmethods.WiththisenhancementyoucanhavemultipleCitrustestcasesinonesingleJavaclassandtheCitrustestsnowareabletocoexistwithotherunittestmethods.YoucanevenmixJavaDSLandXMLCitrustestcasesinasingleJavaclass.
TheXMLCitrustestscanbegroupedtoasingleJavaclasswithmultipleXMLfilesloadedduringexecution.ThereisevenapackagescanforallCitrusXMLfileswithinadirectorystructuresoyoudonothavetocreateaJavaclassforeachtestcaseanymore.
Wehavechangedthedocumentationinthisguidesoyoucanseehowtousethenewannotations.Fordetailedoverviewseerun-config-testng.AlsoseeourCitrusblogwherewecontinuouslydescribethemanypossibilitiesthatyouhavewiththenewannotations.
@CitrusParametersannotation
CitrusisabletousethefabulousTestNGdataprovidercapabilitiesinordertoexecuteatestcaseseveraltimeswithdifferentdataprovidedformexternalresources.Thenew@CitrusParametersannotationhelpstosetparameternameswhichareusedastestvariablenamesinthetestcase.
Schemarepositoryconfigurationcomponents
Definingschemarepositoriesandschemas(xsd,wsdl)iscommonuseinCitrus.WehaveaddedXMLbeandefinitionparserssodefiningthosecomponentsislessverbose.YoucanusetheCitruscitrus:schema-repositoryandcitrus:schemacomponentsinyourSpringapplicationcontextconfiguration.Thecomponentsdoreceiveseveralattributesforfurtherconfiguration.XSD,WSDLandschemacollectionsaresupportedhere.
CitrusReferenceGuide
559Changes1.3
Checkoutxsd-validationforexampleshowtousethenewconfigurationcomponents.
Changedatefunction
WehaveaddedanewCitrusfunctioncitrus:changeDate()thatisavailableforyoubydefault.Thefunctionchangesagivendatevalueaddingorremovingadatetimeoffset(e.g.year,month,day,hour,minute,second).Soyoucanmanipulateeachdatevaluealsothoseofdynamicnaturecomingwithsomemessage.
Seefunctions-changedateforexamplesanddetailedsyntaxusageofthisfunction.
Weekdayvalidationmatcher
Thenewweekdayvalidationmatcheralsoworksondatevalues.Thematcherchecksthatagivendatevalueevaluatestoaexpecteddayoftheweek.Sotheusercancheckthatadatefieldisalwaysasaturdayforinstance.Thisisveryhelpfulwhencheckingthatagivendatevalueisnotaworkingdayforexample.
Seevalidation-matcher-weekdayforsomemoredetaileddescriptionofthematcher'scapabilities.
JavaDSL
Citrususers,inparticularthosewithdevelopmentexperience,dooftentellmeaboutthenastyXMLcodetheyhavetodealwithforwritingCitrustestdefinitions.DeveloperswanttowriteJavacoderatherthanXML.AlthoughIpersonallydoliketheCitrusXMLtestsyntaxwehaveintroducedaJavaDSLlanguageforwritingCitrustestswithJavaonly.
WehaveintroducedtheJavaDSLtoallmajortestactionfeaturesinCitrussoyoucanswitchwithouthavingtoworryaboutloosingfunctionality.DetailscanbeseeninthetestactionsectionwhereweaddedJavaDSLexamplesalmosteverywhere(actions).ThebasicJavaDSLsetupisdescribedintestcase.
XHTMLmessagevalidation
MessagevalidationforHtmlcodewasnotreallycomfortableasHtmldoesnotconfirmtobewellformedandvalidXMLsyntax.XHTMLtriestoclosethisgapbyautomaticallyresolvingallHtmlspecificXMLsyntaxruleviolations.WithCitrus1.3weintroducedaXHTMLmessagevalidatorwhichdoesthemagicforconvertingHtmlcodetoproper
CitrusReferenceGuide
560Changes1.3
wellformedandvalidXML.InatestcaseyoucanthenusethefullXMLvalidationpowerinCitrusinordertovalidateincomingHtmlmessages.Sectionvalidation-xhtmldealswiththenewvalidationcapabilitiesforHtml.
MultipleSOAPfaultdetailsupport
SOAPfaultmessagescanholdmanySOAPfaultdetailelements.CitruswasabletohandleasingleSOAPfaultdetailonsendingandreceivingtestactionsfromtheverybeginningbutmultipleSOAPfaultdetailelementswerenotsupported.FortunatelythingsaregettingbetterandyoucansendandreceiveasmanyfaultdetailelementsasyoulikeinCitrus1.3.ForeachSOAPfaultdetailyoucanspecifyindividualvalidationrulesandexpectations.Seesoap-faultsfordetaileddescription.
Jettyserversecurityhandler
WithourJettyservercomponentyoucansetupHttpmockserversveryeasy.TheserverisautomaticallyconfiguredtoacceptHttpclientconnectionsandtoloadaSpringapplicationcontextonstartup.Nowyoucanalsosetsomemoredetailsonthisautomaticserverconfiguration(e.g.servercontextpath,servletnamesorservletmappings).Inadditiontothatyoucanaccessthesecuritycontextofthewebcontainer.Thisenablesyoutosetupsecurityconstraintssuchasbasicauthenticationonserverresources.Clientsarethenforcedtoauthenticateproperlywhenaccessingtheserver.Unauthorizeduserswillget401accessdeniederrorsimmediately.Seehttp-basic-auth-serverfordetails.OfcoursethisalsoappliestoourSOAPWebServiceJettyservercomponents(soap-basic-auth-server).
Testactors
Weintroducedanewconceptoftestactorsforsendingandreceivingtestactions.Thisenablesyoutolinkatestactor(e.g.interfacepartnerapplication,backendapplication)toatestactioninyourtest.Followingfromthatyoucanenable/disabletestactorsandalllinkedtestactionsveryeasy.ThisenablesustoreuseCitrustestcasesinend-to-endtestscenarioswherenotallinterfacepartnersgetsimulatedbyCitrus.Ifyouliketoreadmoreaboutthisconceptfollowthedetailedinstructionintest-actors.
SimulateHttperrorcodeswithSOAP
CitrusReferenceGuide
561Changes1.3
CitrusprovidesSOAPWebServicesserversimulationwithclientsconnectingtotheserversendingSOAPrequests.AsaserverCitrusisnowabletosimulateHttperrorcodeslike404Notfoundand500Internalservererror.BeforethattheCitrusSOAPserverhadtoalwaysrespondwithaproperSOAPresponseorSOAPfault.Seesoap-http-errorsfordetails.
SSHserverandclient
TheCitrusfamilyhasraisedanewmemberinaddingSSHconnectivity.WiththenewSSHmoduleyouareabletoprovideafullstackSSHserver.TheSSHserveracceptsclientconnectionsandyouasatestercansimulateanySSHserverfunctionalitywithpropervalidationasitisknowntoCitrusSOAPandHTTPmodules.InadditiontothatyoucanalsousetheCitrusSSHclientinordertoconnecttoanexternalSSHserver.YoucanexecuteSSHcommandsontheSSHserverandvalidatetherespectiveresponsedata.Thefulldescriptionisprovidedinssh.
ANTruntestaction
WiththisnewtestactionyoucancallANTbuildsfromyourtestcase.TheactionexecutesoneormoreANTbuildtargetsonabuild.xmlfile.YoucanspecifybuildpropertiesthatgetpassedtotheANTbuildandyoucanaddacustombuildlistener.IncasetheANTbuildrunfailsthetestfailsaccordinglywiththebuildexception.Seeactions-antrunfordetails.
Pollingintervalforreplyhandlers
WithsynchronouscommunicationinCitruswealwayshaveacombinationofasynchronousmessagesenderandareplyhandlercomponent.Thesetwoperformahandshakewhenpassingsynchronousreplymessagestothetestforfurtherprocessingsuchasmessagevalidation.Whilethesenderiswaitingforthesynchronousresponsetoarrivethereplyhandlerpollsforthereplymessage.Thispollingforreplymessageswasdoneinastaticwaywhichoftenledtotimedelaysaccordingtolongpollingintervals.NowwithCitrus1.3youcansetthepolling-intervalforthereplyhandlerasyoulike.ThissettingisvalidforallreplyhandlercomponentsinCitrus(citrus-jms,citrus-http,citrus-ws,citrus-channel,citrus-shh,andsoon).
Upgradingfromversion1.2
CitrusReferenceGuide
562Changes1.3
IfyouarecomingfromCitrus1.2youmayhavetolookatthefollowingpointsinordertohaveasmoothupgradetothenewreleaseversion.
JettyversionupgradeWeareusingJettyalotforstartingHttpservermockswithinCitrus.InordertostayuptodateweupgradedtoJetty8.1.7versionwiththisCitrusrelease.ThisimpliesthatpackagenamesdidchangeforJettyAPI.IngeneralthereisnoconflictforyouasaCitrususer,butyoumaywanttoadjustyourloggingconfigurationaccordingtonewJettypackages.Jettypackagenamesdidchangefromord.mortbaytoorg.eclipse.jetty.
SpringversionupgradeStayinguptodatewiththeversionsof3rdlibrarydependenciesisquiteimportantforus.Soweupgradeourdependenciestonewerversionswitheachrelease.Aswedidonlyupgrademinorversionsthereisnosignificantchangeorproblemstobeexpected.HoweveryoumaytakecareonversionsandreleasechangesintheSpringworldfordetailsandmigration.
TIBCOmoduleWedecidedtoputtheTIBCOmoduleseparatelyasitisaveryspecialconnectivityadapterforTIBCOsoftwareonly.SoyouwillnotfindtheTIBCOmodulewithintheCitrusdistributionanymore.WewillmaintainaTIBCOconnectivityadapterseparatelyinthefuture.
CitrusReferenceGuide
563Changes1.3
ChangesinCitrus1.2
Springversionupdate
WehavesomemajorversionupgradesinourSpringdependencies.WearenowusingSpring3.1.1,SpringIntegration2.1.2andSpringWS2.1.0.Thisupgradewasoverdueforsometimeandisdefinitelyworthit.WiththeseupgradeswehadtoapplysomechangesinourAPI,too.ThisisbecauseweareusingtheSpringclassesalotinourcode.Seetheupgradeguideinthischapterforallsignificantchangesthatmightaffectyourproject.
Newgroovyfeatures
CitrusextendedthepossibilitiestoworkwithscriptlanguageslikeGroovy.YoucanuseGroovy'sMarkupBuildertocreateXMLmessagepayloads.YourGroovycodegoesrightintothetestcaseorcomesfromexternalfile.WithMarkupBuilderyoudonotneedtocareaboutXMLmessagesyntaxandoverhead.Justfocusonthepuremessagecontent.Youcanreadthedetailsingroovy-markupbuilder.
FurtherGroovyfeaturegoestothevalidationcapabilities.InsteadofworkingwithXMLDOMtreecomparisonandXPathexpressionvalidationyoucanuseGroovyXMLSlurper.ThisisveryusefulforthoseofyouwhoneedtodocomplexmessagevalidationanddonotliketheXML/XPathsyntaxatall.WithXMLSlurperyoucanaccesstheXMLDOMtreevianamedclosureoperationswhichfeelsgreat.ThisespeciallycomesinhandyforcomplexgenericXMLstructuresasyoucansearchforelements,sortelementlistandusethepowerfulcontainsoperation.Thisisalldescribedingroovy-xmlslurper.
SomeotherGroovysupportextensioncomesinSQLresultsetvalidation(actions-database-groovy).WhenreadingdatafromthedatabasesomeoneisabletovalidationtheresultingdatarowsetwithGroovyscript.ThescriptcodeeasilyaccessestherowsandcolumnswithGroovy'sout-of-the-boxlistandmaphandling.Thisaddsverypowerfulvalidationtomulti-rowdatasetsfromthedatabase.
SQLmulti-lineresultsetvalidation
CitrusReferenceGuide
564Changes1.2
InthisnewCitrusversionthetestercanvalidateSQLQueryresultsthathavemultiplerows.InthepastCitruscouldonlyhandleasinglerowintheresultset.Nowthisnewreleasebringslightintothedark.SeealsothenewGroovySQLresultsetvalidationwhichfitsbrilliantforcomplexmulti-rowSQLresultsetvalidation.Thedetailscanbefoundinactions-database-query
Extendedmessageformatsupport
InpreviousversionsCitruswasprimarydesignedtohandleXMLmessagepayloads.WiththisnewreleaseCitrusisalsoabletoworkwithothermessageformatssuchasJSON,CSV,PLAINTEXT.Thisappliestosendingmessagesaswellasreceivingandparticularlyvalidatingmessagepayloads.ThetestercanspecifyseveralmessagevalidatorsinCitrusfordifferentmessageformats.Accordingtotheexpectedmessageformatthepropervalidatorischosentoperformthemessagevalidation.
WehaveimplementedaJSONmessagevalidatorcapableofignoringspecificJSONentriesandhandlingJSONArrays.Wealsoprovideaplaintextmessagevalidatorwhichisverybasictobehonest.Theframeworkisreadytoreceivenewvalidatorimplementationssoyoucanaddcustomvalidatorsforyourspecificneeds.
NewXMLfeatures
XMLnamespacehandlingistediousexpeciallyifyouhavetodealwithalotofXPathexpressionsinyourtests.BeforeyouhadneedtospecifyanamespacecontextfortheXPathexpressioneverytimeyouusetheminyourtest-nowyoucanhaveacentralnamespacecontextwhichdeclaresnamespacesyouuseinyourproject.Thesenamespacesidentifiedbysomeprefixareavailablethroughoutthetestprojectwhichismuchmoremaintainableandeasytouse.Seehowitworksinxpath-namespace.
SOAPsupportimprovements
WsAddressingstandardisnowsupportedinCitrus(soap-ws-adressing).ThismeansyoucandeclarethespecificWsAddressingmessageheadersonmessagesenderlevelinordertosendmessageswithWsAddressingfeature.TheheaderisconstructedautomaticallyforallSOAPmessagesthataresentoverthemessagesender.
DynamicSOAPendpointuriresolverenablesyoutodynamicallyaddressSOAPendpointsduringatest.SometimesamessagesendermaydynamicallyhavetochangetheSOAPurlforeachcall(e.g.addressdifferentrequesturiparts).Withaendpointuri
CitrusReferenceGuide
565Changes1.2
resolversetonthemessagesenderyoucanhandlethisrequirementveryeasy.Thetipfordynamicendpointresolvingwasaddedtosoap-sender
WealsosimplifiedthesynchronousSOAPHTTPcommunicationwithintestcases.InpreviousversionsyouhadtobuildcomplexparallelandsequentialcontainerconstructsinordertocontinuewithtestexecutionwhiletheSOAPmessagesenderiswaitingforthesynchronousresponsetoarrive.NowyoucansimplyforkthemessagesendingactionandcontinuewithfurthertestactionswhilesynchronousSOAPcommunicationtakesplace.Seethesoap-fork-modefordetails
SomereallysmallchangeintroducedwiththisreleaseisthefactthatCitrusnowlogsSOAPmessagesintheirpurenature.ThismeansthatyoucanseethecompleteSOAPenvelopeofmessagesintheCitruslogfiles.Thisismorethanhelpfulwhensearchingforerrorsinsideyourtestcase.
HttpandRESTfulWebServices
WehavechangedHttpcommunicationcomponentsforfullsupportofRESTfulWebServicesonclientandserverside.TheHttpclientnowusesSpring'sRESTsupportforHttprequests(GET,PUT,DELETE,POST,etc.).Theserversidehaschanged,too.TheHttpservernowprovidesRESTfulWebServicesandiscomplianttotheexistingSOAPJettyserverimplementationinCitrus.IfyouwanttoupgradeexistingprojectstothisversionyoumayhavetoadjusttheSpringapplicationcontextconfigurationtosomeextent.
Fordetailshavealookattheupgradeguide(history-upgrading)inthischapterorfinddetailedexplanationstothenewHttpcomponentsinhttp.
HTMLreporting
CitrusprovidesHTMLreportsaftereachtestrunwithdetailedinformationonthefailedtests.Youcanimmediatelyseewhichtestsfailedinexecutionandwheretheteststopped.reporting-htmlprovidesdetailsonthisnewfeature.
Validationmatchers
Thenewvalidationmatcherswillputthemessagevalidationmechanismstoanewlevel.Withvalidationmatchersyouareabletoexecutepowerfulassertionsoneachmessagecontentelement.ForinstanceyoucanusetheisNumbervalidationmatcherforchecking
CitrusReferenceGuide
566Changes1.2
thatamessagevalueisofnumericnature.Weaddedseveralmatcherimplementationsthatarereadyforusageinyourtestbutyoucanalsowriteyourcustomvalidationmatchers.Havealookatvalidation-matchersfordetails.
Conditionalcontainer
Thenewconditionaltestactioncontainerenablesyoutoexecutetestactionsonlyincaseabooleanexpressionevaluatestotrue.Sothenestedtestactionsinsidethecontainermaybenotexecutedatallincaseaconditionisnotmet.Seecontainers-conditionalfordetails.
Supportformessageselectorsonmessagechannels
SpringIntegrationmessagechannelsdonotsupportmessageselectorslikeJMSqueuesdoforexample.WithCitrus1.2weimplementedasolutionforthisissuewithaspecialmessagechannelimplementation.Soyoucanusethemessageselectorfeaturealsowhenusingmessagechannels.Gotomessage-channel-selector-supportfordetails.
Newtestactions
Weintroducedsomecompletelynewtestactionsinthisreleaseforyou.Thenewactionsarelistedbelow:
Purgemessagechannelaction()
Seeactionsfordetailedinstructionshowtousethenewactions.
Newfunctions
WeintroducedsomenewdefaultCitrusfunctionsthatwilleasethetesterslife.Thisincludescommonlyusedfunctionslikeencoding/decodingbase64bindarydata,escapingXMLandgeneratingrandomJavaUUIDvalues.Thesearethenewfunctionsinthisrelease:
citrus:randomUUID()citrus:cdataSection()citrus:encodeBase64()citrus:decodeBase64()citrus:digestAuthHeader()citrus:localHostAddress()
CitrusReferenceGuide
567Changes1.2
Seefunctionsfordetaildescriptionsofeachfunction.
Upgradingfromversion1.1
IfyouarecomingfromCitrus1.1finalyoumayhavetolookatthefollowingpoints.
SpringversionupdateWedidsomemajorversionupgradesonourSpringdependencies.WearenowusingSpring3.1.1,SpringIntegration2.1.2andSpringWS2.1.0.ThesenewmajorreleasesbringsomecodechangesinourCitrusAPIwhichmightaffectyourcodeandconfiguration,too.Sopleaseupdateyourconfiguration,itisdefinitelyworthit!
SpringIntegrationheaders:With2.0.xversionSpringIntegrationhasremovedtheinternalheaderprefix("springintegration_").Soinsomecasesyoumightusethoseinternalheadernamesinyourtestcasesinordertosynchronizesynchronouscommunicationwithinternalmessageids.YourtestcasewillfailaslongasyouusetheoldSpringinternalheaderprefixinthetest.Simplyremovetheheaderprefixwhereverusedandyourtestisupandrunningagain.
Messagevalidator:YouneedtospecifyatleastonemessagevalidatorintheSpringapplicationcontext.BeforethiswasinternallyastaticXMLmessagevalidator,butnowweofferdifferentvalidatorsforseveralmessageformatslikeXMLandJSON.PleaseseetheJavaAPIdoconMessageValidatorinterfaceforavailableimplementations.IfyoujustliketokeepitasitwasbeforeaddthisbeantotheSpringapplicationcontext:
<beanid="xmlMessageValidator"class="com.consol.citrus.validation.xml.DomXmlMessageValidator"
Testsuite:Wehaveeliminated/changedtheCitrustestsuitelogicbecauseitduplicatesthosetestsuitesdefinedinTestNGorJUnit.InolderversionsthetesterhadtodefineaCitrustestsuiteinSpringapplicationcontextinordertoexecutetestactionsbefore/afterthetestrun.Nowthesetasksbeforeandafterthetestrunaredecoupledfromatestsuite.YoudefinetestsuitesexclusivelyinTestNGorJUnit.Thetestactionsbefore/afterthetestrunareseparatelydefinedinSpringapplicationcontextsoyouhavetochangethisconfigurationinyourCitrusproject.
Seetestsuitefordetailsonthisconfigurationchanges.
JUnitvs.TestNG:WesupportbothfamousunittestingframeworksJUnitandTestNG.Withthisreleaseyouarefreetochooseyourpreferedone.Inthismanner
CitrusReferenceGuide
568Changes1.2
youneedtoaddeitheraJUnitdependencyoraTestNGdependencytoyourprojectonyourown.WedonothavestaticdependenciesinourMavenPOMtoneitherofthosetwo.OnoursidethesedependenciesaredeclaredoptionalsoyoufeelfreetoaddtheoneyoulikebesttoyourMavenPOM.JustaddaJUnitorTestNGdependencytoyourMavenprojectoraddtherespectivejarfiletoyourprojectifyouuseANTinstead.
CitrusReferenceGuide
569Changes1.2