citrusframework.org · What's new in Citrus 2.7?! Citrus 2.7 is using Java 8! The Citrus sources...

569

Transcript of citrusframework.org · What's new in Citrus 2.7?! Citrus 2.7 is using Java 8! The Citrus sources...

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

Mail

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:&quot;([^&quot;]*)&quot;$"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:&quot;([^&quot;]*)&quot;$"

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