Post on 23-Mar-2021
TableofContents
AngularJSTest-drivenDevelopment
Credits
AbouttheAuthor
AbouttheReviewers
www.PacktPub.com
Supportfiles,eBooks,discountoffers,andmore
Whysubscribe?
FreeaccessforPacktaccountholders
Preface
Whatthisbookcovers
Whothisbookisfor
Conventions
Readerfeedback
Customersupport
Downloadingtheexamplecode
Errata
Piracy
Questions
1.IntroductiontoTest-drivenDevelopment
AnoverviewofTDD
FundamentalsofTDD
Measuringsuccess
Breakingdownthesteps
Measuretwicecutonce
Divingin
Settingupthetest
Creatingadevelopmentto-dolist
Testfirst
Makingitrun
www.it-ebooks.info
Makingitbetter
Testingtechniques
Testingwithaframework
TestingdoubleswithJasminespies
Stubbingareturnvalue
Testingarguments
Refactoring
Buildingwithabuilder
Self-testquestions
Summary
2.TheKarmaWay
JavaScripttestingtools
Karma
Protractor
JavaScripttestingframeworks
Jasmine
Selenium
Mocha
BirthofKarma
TheKarmadifference
ImportanceofcombiningKarmawithAngularJS
InstallingKarma
Installationprerequisites
ConfiguringKarma
CustomizingKarma’sconfiguration
ConfirmingKarma’sinstallationandconfiguration
Commoninstallation/configurationissues
TestingwithKarma
ConfirmingtheKarmainstallation
UsingKarmawithAngularJS
GettingAngularJS
www.it-ebooks.info
Bower
Bowerinstallation
InstallingAngularJS
InstallingAngularmocks
InitializingKarma
TestingwithAngularJSandKarma
Adevelopmentto-dolist
Testingalistofitems
Testfirst
Assemble,Act,andAssert(3A’s)
Makeitrun
Makeitbetter
Addingafunctiontothecontroller
Testfirst
Assemble,Act,andAssert(3A’s)
Makeitrun
Makeitbetter
Self-testquestions
Summary
3.End-to-endTestingwithProtractor
AnoverviewofProtractor
OriginsofProtractor
Endoflife
ThebirthofProtractor
LifewithoutProtractor
Protractorinstallation
Installationprerequisites
InstallingProtractor
InstallingWebDriverforChrome
Customizingconfiguration
Confirminginstallationandconfiguration
www.it-ebooks.info
Commoninstallation/configurationissues
HelloProtractor
TDDend-to-end
Thepre-setup
Thesetup
Testfirst
Installingthetestwebserver
ConfiguringProtractor
Gettingdowntobusiness
Specification
Thedevelopmentto-dolist
Testfirst
Assemble,Act,Assert(3A’s)
Runningthetest
Makeitrun
Makeitbetter
Cleaningupthegaps
Asyncmagic
Loadingapagebeforetestexecution
Assertiononelementsthatgetloadedinpromises
TDDwithProtractor
Self-testquestions
Summary
4.TheFirstStep
Preparingtheapplication’sspecification
Settinguptheproject
Settingupthedirectory
SettingupProtractor
SettingupKarma
Settinguphttp-server
Top-downorbottom-upapproach
www.it-ebooks.info
Testingacontroller
Asimplecontrollertestsetup
Initializingthescope
Bringonthecomments
Testfirst
Assemble
Act
Assert
Makeitrun
Addingthemodule
Addingtheinput
Controller
Makeitpass
Makeitbetter
ImplementingtheSubmitbutton
ConfiguringKarma
Testfirst
Assemble
Act
Assert
Makeitrun
Makeitbetter
Backupthetestchain
Bindtheinput
Onwardsandupwards
Testfirst
Assemble
Act
Assert
Makeitrun
Fixingtheunittests
www.it-ebooks.info
Makeitbetter
Couplingofthetest
Self-testquestions
Summary
5.FlipFlop
Fundamentals
Protractorlocators
CSSlocators
Buttonandlinklocators
Angularlocators
URLlocationreferences
Creatinganewproject
SettingupheadlessbrowsertestingforKarma
Preconfiguration
Configuration
Walk-throughofAngularroutes
SettingupAngularJSroutes
Definingdirections
ConfiguringngRoute
Definingtheroutecontrollers
Definingtherouteviews
Assemblingtheflipfloptest
Makingtheviewsflip
Assertingaflip
Makingflipfloprun
Makingflipflopbetter
SearchingtheTDDway
Decidingontheapproach
Walk-throughofsearchquery
Thesearchquerytest
ThesearchqueryHTMLpage
www.it-ebooks.info
Thesearchapplication
Showmesomeresults!
Creatingthesearchresultroutes
Testingthesearchresults
Assemblingthesearchresulttest
Selectingasearchresult
Confirmingasearchresult
Makingthesearchresulttestrun
Creatingalocation-awaretest
Makingthesearchresultbetter
ConfirmingtherouteID
SettinguptherouteIDunittest
ConfirmingtheID
Makingtherouteparameter’stestrun
Self-testquestions
Summary
6.TellingtheWorld
Beforetheplunge
Karmaconfiguration
Filewatching
Usingabottom-upapproach
Services
Publishingandsubscribingmessages
Emitting
Testingemit
Testingbroadcast
Testingbroadcast
Publishingandsubscribing–thegoodandbad
Thegood
Communicatingthroughevents
Reducingcoupling
www.it-ebooks.info
Harnessingthepowerofevents
Theplan
Rebranding
Seeingrecentlyvieweditems
Testfirst
AssemblingSearchController
Selectingaproduct
Expectingeventstobepublished
Makingthesearchcontrollerrun
Recentlyviewedunittest
Testfirst
AssemblingRecentlyViewedController
Invokingarecentlyvieweditem
ConfirmingRecentlyViewedController
MakingRecentlyViewedControllerrun
End-to-endtesting
Testfirst
Assemblingtherecentlyviewedend-to-endtest
Selectingasearchresult
Confirmingrecentlyvieweditems
MakingtherecentlyViewedItemstestpass
Makingrecentlyvieweditemsbetter
Creatingaproductcart
Publishertestfirst
AssemblingsearchDetailController
Invokingthesavingofaproduct
Confirmingthesaveevent
MakingthesaveProducttestpass
Testforthesubscriberfirst
Assemblingtheproductcarttest
Invokingasavedcartevent
www.it-ebooks.info
Confirmingthesavedcart
Makingthecartcontrollertestrun
End-to-endtesting
Assemblingthecart’send-to-endtest
Invokingasavetocartaction
Confirmingproductshavebeensaved
Makingthecart’send-to-endtestpass
Self-testquestions
Summary
7.GiveMeSomeData
REST–thelanguageoftheWeb
GettingstartedwithREST
Testingasynchronouscalls
CreatingasynchronouscallsinKarma
CreatingasynchronouscallsinProtractor
MakingRESTrequestsusingAngularJS
TestingwithAngularJSREST
Testingtheproductservice
Testing$httpwithKarma
MockingrequestswithProtractor
DisplayingproductswithREST
Unittestingproductrequests
Settinguptheproject
Karmaconfiguration
UsinganAPIbuilderpattern
Theproductdataservice
Theproductdatacontroller
Assemblingtheproductcontrollertest
Gettingproducts
Assertingproductdataresults
Makingtheproductdatatestsrun
www.it-ebooks.info
Testingmiddle-to-end
Testfirst
Assemblingtheproducttest
Gettingproducts
Expectingproductdataresults
Makingtheproductdatarun
Testingend-to-end
Gettingtheproductdata
Self-testquestions
Summary
A.IntegratingSeleniumServerwithProtractor
Installation
Protractorconfiguration
RunningSelenium
Letitrun
Testfirst
Assemble
Assert
Makeitrun
Summary
B.AutomatingKarmaUnitTestingonCommit
GitHub
Testsetup
Testscripts
Settingthehook
Creatingthehook
AddingaTravisconfigurationfile
References
C.Answers
Chapter1,IntroductiontoTest-drivenDevelopment
Chapter2,TheKarmaWay
www.it-ebooks.info
Chapter3,End-to-endTestingwithProtractor
Chapter4,TheFirstStep
Chapter5,FlipFlop
Chapter6,TellingtheWorld
Chapter7,GiveMeSomeData
Index
www.it-ebooks.info
AngularJSTest-drivenDevelopmentCopyright©2015PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:January2015
Productionreference:1230115
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
BirminghamB32PB,UK.
ISBN978-1-78439-883-5
www.packtpub.com
www.it-ebooks.info
CreditsAuthor
TimChaplin
Reviewers
Md.ZiaulHaq
NiveJayasekar
TimPei
AndiSmith
CommissioningEditor
PramilaBalan
AcquisitionEditor
ReshmaRaman
ContentDevelopmentEditor
ManasiPandire
TechnicalEditor
MadhunikitaSunilChindarkar
CopyEditors
GladsonMonteiro
AdithiShetty
StutiSrivastava
ProjectCoordinator
LeenaPurkait
Proofreaders
SimranBhogal
MariaGould
AmeeshaGreen
PaulHindle
Indexer
HemanginiBari
ProductionCoordinator
www.it-ebooks.info
AbouttheAuthorTimChaplinlivesandbreathessoftwaresolutionsandinnovations.Duringtheday,heworkswithFortune100enterpriseapplications,andintheevening,heperfectshiscraftbycontributingtoanddistributingopensourcesoftware,writing,andconstantlylookingforwaystoincreasehisknowledgeoftechnologyandtheworld.Atanearlyage,Timbegandevelopingsoftwareandhasbeenhookedonitsince.TimisanestablishedconferencespeakerwhohasextensiveexperienceindevelopingandleadingAngularJSprojects.HehasawidebackgroundofJavaScript,C#,Java,andC++languages.Timspecializesinleadingcodequalityandtestingthroughoutallhisapplications.AfterattendingCaliforniaStateUniversity,Chico,hehasgoneontoworkinShanghai,LosAngeles,andLondon.
Iwouldliketothankmywife,Pierra,foralwaysmakingmethinkanddreambigger.Iwouldalsoliketothankmyfamilyfortheirconstantloveandsupport.Pops,thisone’sforyoubabe.
www.it-ebooks.info
AbouttheReviewersMd.ZiaulHaqisaseniorsoftwareengineerfromDhaka,Bangladesh,whohasbeenworkingwiththeoDeskcoreplatformdevelopmentteamasaseniorJavaScriptdevelopersince2011.Helikestoworkmostlyonthefrontend,thoughheisafull-stackdeveloper.JavaScriptishispassionandhelikestocodeinitalldaylong.Heiswellknownasjquerygeekinthewebcommunity.
Md.Ziaulstartedhiscareerin2005asasoftwaredeveloper.HehasworkexperiencewithUNICEFlocallyandinternationally,whereheworkedwithUNICEF’swebCMS.Heiscurrentlypursuingamaster’sdegreeincomputersciencefromUnitedInternationalUniversity,Dhaka,Bangladesh.
Iwouldliketothankmywife,Richi,andmynewbornson,Arabi,whoismyinspiration.
NiveJayasekarstartedprogramminginhighschool.Inherlastyearofhighschool,shewon$10,500ataHackathonforbuildingamobileartificial-intelligenceapp.ShehasinternedatFacebookandLinkedIn,andwillsoongraduatefromCarnegieMellonUniversitywithadegreeincomputerscienceandaminorinmachinelearning.Sheisalwaysinterestedinbuildinggame-changingproducts.Shehas5yearsofexperiencebuildingwebandmobileapplicationsusingPython,AngularJS,Java,andObjectiveC.
I’dliketothankthepeopleatPacktPublishing,LeenaPurkaitandKirtiPatil,fortheirhelpinproducingthisbook.
TimPieisacomputerscienceandbusinessadministrationdoubledegreestudentattheUniversityofWaterloo,Ontario.Hehasgainedawiderangeoftechnicalskillsthroughpastprojectsandinternships,includingcloudcomputing,datamining,andfullstackwebdevelopment.Tim’scurrenttechnicalinterestisfocusingonbuildingwebapplicationsusingmodernwebtechnologies,specificallyHTML5andwebcomponents.
I’dliketothankmyparentsfortheirconstantsupportofmypursuits,whileprovidingmegreatadvicealongtheway.
AndiSmith(@andismith)isaseniorarchitectwhospecializesinfrontendsolutionsatideasandinnovationagency,AKQA.
Andihasover15yearsofexperiencebuildingfortheWebandhasworkedwithclientssuchasNike,Ubisoft,Sainsburys,Barclays,Heineken,andMINI.HehasalsocreatedanumberofopensourcepluginsandsitessuchasGruntResponsiveImages(http://www.andismith.com/grunt-responsive-images/)andSecretsoftheBrowserDeveloperTools(http://devtoolsecrets.com/).
Andimaintainsablogfocusedonfrontenddevelopmentathttp://www.andismith.com/.
Iwouldliketothankmywife,Amy,forallherloveandsupport.
www.it-ebooks.info
Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<service@packtpub.com>formoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www2.packtpub.com/books/subscription/packtlib
DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.
www.it-ebooks.info
Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
www.it-ebooks.info
FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.
www.it-ebooks.info
PrefaceThebookwillprovidethereaderwithacompleteguidetothetest-drivendevelopment(TDD)approachforAngularJS.Itwillprovidestep-by-step,clearexamplestocontinuallyreinforceTDDbestpractices.ThebookwilllookatbothunittestingwithKarmaandend-to-endtestingwithProtractor.Itwillnotonlyfocusonhowtousethetools,butalsoonunderstandingthereasontheywerebuilt,andwhytheyshouldbeused.Throughout,therewillbefocusonwhen,where,andhowtousethesetools,constantlyreinforcingtheprinciplesoftheTDDlifecycle(test,execute,refactor).
www.it-ebooks.info
WhatthisbookcoversThisbookisbasicallysplitintotwoparts.TheinitialchaptersfocusontheTDDlifecycle,andhowKarmaandProtractorfitintothelifecycleanddevelopmentofanAngularJSapplication.Asweproceed,you’llgetastep-by-stepapproachtoAngularJSTDDusingKarmaandProtractor.EachofthechaptersbuildsuponthepreviousoneandintroduceshowtotestseveraldifferentAngularJScomponents.
Chapter1,IntroductiontoTest-drivenDevelopment,isanintroductiontotheconceptsofTDDandtestingtechniques.
Chapter2,TheKarmaWay,explorestheoriginsofKarmaandwhyitisanessentialtoolforanyAngularJSproject.
Chapter3,End-to-endTestingwithProtractor,introducesthesimplicityofProtractor,anend-to-endtestingtoolbuiltspecificallyforAngularJS.
Chapter4,TheFirstSteps,coverstheTDDjourneyandshowsthefundamentalsandtoolsinaction.
Chapter5,FlipFlop,expandstoincludetestingformultiplecontrollers,partialviews,locationreferences,CSS,andHTMLelementbuildingontheinitialfoundationalaspectslearnedinthepreviouschapter.
Chapter6,TellingtheWorld,divesintocommunicatingacrosscontrollers,andtestingservicesandbroadcasting.
Chapter7,GiveMeSomeData,divesintohowtoapplyseveraloftheconceptsshownpreviously,andextendthemtopulldatausinganexternalAPI.
AppendixA,IntegratingSeleniumServerwithProtractor,walksthroughsettingupandconfiguringProtractortouseastandaloneSeleniumserver.
AppendixB,AutomatingKarmaUnitTestingonCommit,covershowtosetupTravisCI,aplatformforcontinuousintegration,andsettingupKarmatotestyourapplication.
www.it-ebooks.info
WhothisbookisforThisbookisforthedeveloperwhowantstogobeyondthebasictutorials,andwantstotaketheplungeintoAngularJSdevelopment.ThisbookisforthedeveloperwhohasexperiencewithAngularJSandhaswalkedthroughthebasictutorialsbutwantstounderstandthewidercontextofwhen,why,andhowtoapplytestingtechniquesandbestpracticestocreatequality-cleancode.Togetthemostoutofthisbook,itispreferredthatthereaderhasbasicunderstandingofHTML,JavaScript,andAngularJS.
www.it-ebooks.info
ConventionsInthisbook,youwillfindanumberofstylesoftextthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Createawebpageandimportcalculator.jsfortesting.”
Ablockofcodeissetasfollows:
<!DOCTYPEhtml>
<html>
<head>
<title></title>
</head>
<body>
<scriptsrc="calculator.js"></script>
</body>
</html>
Anycommand-lineinputoroutputiswrittenasfollows:
$nodecalculator.js
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,inmenusordialogboxesforexample,appearinthetextlikethis:“Traditionally,testswererunbyhavingtomanuallylaunchabrowserandcheckforresultsbycontinuallyhittingtheRefreshbutton.”
NoteWarningsorimportantnotesappearinaboxlikethis.
TipTipsandtricksappearlikethis.
www.it-ebooks.info
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,simplye-mail<feedback@packtpub.com>,andmentionthebook’stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
www.it-ebooks.info
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
www.it-ebooks.info
DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
www.it-ebooks.info
ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.
Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.
www.it-ebooks.info
PiracyPiracyofcopyrightmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.Ifyoucomeacrossanyillegalcopiesofourworks,inanyform,ontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusat<copyright@packtpub.com>withalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluablecontent.
www.it-ebooks.info
QuestionsYoucancontactusat<questions@packtpub.com>ifyouarehavingaproblemwithanyaspectofthebook,andwewilldoourbesttoaddressit.
www.it-ebooks.info
Chapter1.IntroductiontoTest-drivenDevelopmentAngularJSisattheforefrontofclient-sideJavaScripttesting.EveryAngularJStutorialincludesanaccompanyingtest,andeventtestmodulesarepartofthecoreAngularJSpackage.TheAngularteamisfocusedonmakingtestingfundamentaltowebdevelopment.
Thischapterintroducesyoutothefundamentalsoftest-drivendevelopmentwithAngularJSincluding:
Anoverviewoftest-drivendevelopment(TDD)TheTDDlifecycle:testfirst,makeitrun,makeitbetterCommontestingtechniques
www.it-ebooks.info
AnoverviewofTDDTDDisnotusedonlytodevelopsoftware.Thefundamentalprinciplescanbeseeninmanyindustries.ThissectionwillexplorethefundamentalsofTDDandhowtheyareappliedbyatailor.
www.it-ebooks.info
FundamentalsofTDDKnowwhattocodebeforeyoucode.Thismaysoundcliché,butthisisessentiallywhatTDDgivesyou.TDDbeginsbydefiningexpectations,thenmakesyoumeettheexpectations,andfinallyforcesyoutorefinethechangesaftertheexpectationshavebeenmet.
HereareacoupleofclearbenefitsofusingTDD:
Knowingbeforeyoucode:Atestprovidesaclearvisionofwhatcodeneedstodoinordertobesuccessful.Settinguptestsfirstallowsfocusononlycomponentsthathavebeendefinedintests.Confidenceinrefactoring:Refactoringinvolvesmoving,fixing,andchangingaproject.Testsprotectthecorelogicfromrefactoringbyensuringthatthelogicbehavesindependentlyofthecodestructure.Documentation:Testsdefineexpectationsthataparticularobjectorfunctionmustmeet.Theexpectationactsasacontract,andcanbeusedtoseehowamethodshouldorcanbeused.Thismakesthecodereadableandeasiertounderstand.
www.it-ebooks.info
MeasuringsuccessTDDisnotjustasoftwaredevelopmentpractice.Thefundamentalprinciplesaresharedbyothercraftsmenaswell.Oneofthesecraftsmenisatailor,whosesuccessdependsonprecisemeasurementsandcarefulplanning.
BreakingdownthestepsHerearethehigh-levelstepsatailortakestomakeasuit:
1. Testfirst:
DeterminingthemeasurementsforthesuitHavingthecustomerdeterminethestyleandmaterialtheywantfortheirsuitMeasuringthecustomer’sarms,shoulders,torso,waist,andlegs
2. Makingthecuts:
MeasuringthefabricandcutSelectingthefabricbasedonthedesiredstyleMeasuringthefabricbasedonthecustomer’swaistandlegsCuttingthefabricbasedonthemeasurements
3. Refactoring:
Comparingtheresultingproducttotheexpectedstyle,reviewing,andmakingchangesComparingthecutandlooktothecustomer’sdesiredstyleMakingadjustmentstomeetthedesiredstyle
4. Repeating:
Testfirst:DeterminingthemeasurementsforthepantsMakingthecuts:MeasuringthefabricandmakingthecutsRefactor:Makingchangesbasedonthereviews
TheprecedingstepsareanexampleofaTDDapproach.Themeasurementsmustbetakenbeforethetailorcanstartcuttinguptherawmaterial.Imagineforamomentifthetailordidn’tuseatest-drivenapproachanddidn’tuseameasuringtape(testingtool).Itwouldberidiculousifthetailorstartedcuttingbeforemeasuring.
Asadeveloper,doyou“cutbeforemeasuring”?Wouldyoutrustatailorwithoutameasuringtape?Howwouldyoufeelaboutadeveloperwhodoesn’ttest?
MeasuretwicecutonceThetailoralwaysstartswithmeasurements.Whatwouldhappenifthetailormadecutsbeforemeasuring?Whatwouldhappenifthefabricwascuttooshort?Howmuchextratimewouldgointothetailoring?Measuretwice,cutonce.
Softwaredeveloperscanchoosefromanendlessamountofapproachestousebefore
www.it-ebooks.info
startingdeveloping.Onecommonapproachistoworkoffaspecification.Adocumentedapproachmayhelpindefiningwhatneedstobebuilt;however,withouttangiblecriteriaforhowtomeetaspecification,theactualapplicationthatgetsdevelopedmaybecompletelydifferentthanthespecification.WithaTDDapproach(testfirst,makeitrun,andmakeitbetter),everystageoftheprocessverifiesthattheresultmeetsthespecification.Thinkabouthowatailorcontinuestouseameasuringtapetoverifythesuitthroughouttheprocess.
TDDembodiesatest-firstmethodology.TDDgivesdeveloperstheabilitytostartwithacleargoalandwritecodethatwilldirectlymeetaspecification.Developlikeaprofessionalandfollowthepracticesthatwillhelpyouwritequalitysoftware.
www.it-ebooks.info
DivinginItistimetodiveintosomeactualcode.Thiswalk-throughwilltakeyouthroughaddingthemultiplicationfunctionalitytoacalculator.RemembertheTDDlifecycle:testfirst,makeitrun,andmakeitbetter.
SettingupthetestTheinitialcalculatorisinafilecalledcalculator.jsandisinitializedasanobjectasfollows:
varcalculator={};
ThetestwillberunthroughawebbrowserusingabasicHTMLpage.Createawebpageandimportcalculator.jstotestit.SavethewebpageastestRunner.html.Torunthetest,openabrowserandruntestRunner.html.HereisthecodefortestRunner.html:
<!DOCTYPEhtml>
<html>
<head>
<title></title>
</head>
<body>
<scriptsrc="calculator.js"></script>
</body>
</html>
Nowthattheprojectissetup,thenextstepistocreatethedevelopmentto-dolist.
Creatingadevelopmentto-dolistAdevelopmentto-dolisthelpsorganizeandfocusyourtasks.Italsoprovidesaplacetowritedownideasduringthedevelopmentprocess.
Hereistheinitialstepforcreatingadevelopmentto-dolist:
Addmultiplicationfunctionality:3*3=9
Theprecedinglistdescribeswhatneedstobedone.Italsoprovidesaclearexampleofhowtoverifymultiplication:3*3=9.
TestfirstAlthoughyoucanwritethemultiplicationfunctionquickly,rememberthatoncethehabitofTDDissetinplace,itwillbejustasquicktowritethetestandcode.Herearethestepsforthefirsttest:
1. Opencalculator.js.2. Createanewfunctiontotestmultiplying3*3:
functionmultipleTest1(){
//Test
www.it-ebooks.info
varresult=calculator.multiply(3,3);
//AssertResultisexpected
if(result===9){
console.log('TestPassed');
}
else{
console.log('TestFailed');
}
};
Thetestcallsamultiplyfunction,whichstillneedstobedefined.Itthenassertsthattheresultsareasexpectedbydisplayingapassorfailmessage.Remember,inTDD,youarelookingattheuseofthemethodandexplicitlywritinghowitshouldbeused.Thisallowsyoutodefinetheinterfacethroughausecase,asopposedtoonlylookingatthelimitedscopeofthefunctionbeingdeveloped.
ThenextstepintheTDDlifecyclewillbefocusedonmakingthetestrun.
MakingitrunThisstepisaboutmakingthetestrun,justasthetailordidwiththesuit.Themeasurementsweretakenduringtheteststep,andnowtheapplicationcanbemoldedtofitthemeasurements.Herearethestepstorunthetest:
1. OpenthebrowserwithtestRunner.html.2. OpentheJavaScriptdeveloperConsolewindow.
Thetestthrowsanerror,asshowninthefollowingscreenshot:
Theerrorthrownisexpectedasthecalculatorapplicationcallsafunctionthathasn’tbeencreatedyet:calculator.multiply.
InTDD,thefocusisonaddingthesmallestchangetogetatesttopass.Thereisnoneedtoactuallyimplementthemultiplicationlogic.Thismayseemunintuitive.Thepointisonceapassingtestexists,itshouldalwayspass.Whenamethodcontainsfairlycomplexlogic,itiseasiertorunapassingtestagainstittoensureitmeetstheexpectations.
Whatisthesmallestchangethatcanbemadetomakethetestpass?Byreturningtheexpectedvalueof9,thetestshouldpass.Althoughthiswon’taddthemultiplyfunction,itwillconfirmtheapplicationwiring.Inaddition,afteryouhavepassedthetest,makingfuturechangeswillbeeasyasyouhavetosimplykeepthetestpassing!
Now,addthemultiplyfunctionandhaveitreturntherequiredvalue9:
www.it-ebooks.info
varcalculator={
multiply:function(){
return9;
}
};
Inthebrowser,theJavaScriptconsolererunsthetest.Theresultshouldbeasfollows:
Yes!Thetestpassed.Timetocrossoutthefirstitemfromtheto-dolist:
Addmultiplicationfunctionality:3*3=9
Nowthatthereisapassingtest,thenextstepwillbetoremovethehardcodedvalueinthemultiplyfunction.
MakingitbetterTherefactoringstepneedstoremovethehardcodedreturnvalueofthemultiplyfunction.Therequiredlogicisasfollows:
varcalculator={
multiply:function(amount1,amount2){
returnamount1*amount2;
}
};
Rerunthetestsandconfirmthetestpasses.Excellent!Nowthemultiplyfunctioniscomplete.Hereisthefullcodeforthecalculatorandtest:
varcalculator={
multiply:function(amount1,amount2){
returnamount1*amount2;
}
};
varmultipleTest1=function(){
varresult=calculator.multiply(3,3);
if(result===9){
console.log('TestPassed');
}
else{
console.log('TestFailed');
}
};
www.it-ebooks.info
TestingtechniquesItisimportanttounderstandsomefundamentaltechniquesandapproachestotesting.Thissectionwillwalkyouthroughacoupleofexamplesoftechniquesthatwillbeleveragedinthisbook.Thisincludes:
TestingdoubleswithJasminespiesRefactoringBuildingpatterns
Inaddition,hereareadditionaltermsthatwillbeused:
Functionundertest:Thisisthefunctionbeingtested.Itisalsoreferredtoassystemundertest,objectundertest,andsoon.The3A’s(Arrange,Act,andAssert):Thisisatechniqueusedtosetuptests,firstdescribedbyBillWake(http://xp123.com/articles/3a-arrange-act-assert/).The3A’swillbediscussedfurtherinChapter2,TheKarmaWay.
www.it-ebooks.info
TestingwithaframeworkAlthoughasimplewebpagecanbeusedtoperformtests,asseenearlierinthischapter,itismucheasiertouseatestingframework.Atestingframeworkprovidesmethodsandstructurestotest.Thisincludesastandardstructuretocreateandruntests,theabilitytocreateassertions/expectations,theabilitytousetestdoubles,andmore.ThisbookusesJasmineasthetestframework.Jasmineisabehavior-driventestingframework.ItishighlycompatiblewithtestingAngularJSapplications.InChapter2,TheKarmaWay,wewilltakeamorein-depthlookatJasmine.
www.it-ebooks.info
TestingdoubleswithJasminespiesAtestdoubleisanobjectthatactsandisusedinplaceofanotherobject.Takealookatthefollowingobjectthatneedstobetested:
varobjectUnderTest={
someFunction:function(){}
};
Usingatestdouble,youcandeterminethenumberoftimessomeFunctiongetscalled.Hereisanexample:
varobjectUnderTest={
someFunction:function(){}
};
jasmine.spyOn(objectUnderTest,'someFunction');
objectUnderTest.someFunction();
objectUnderTest.someFunction();
console.log(objectUnderTest.someFunction.count);
TheprecedingcodecreatesatestdoubleusingaJasminespy(jasmine.spyOn).ThetestdoubleisthenusedtodeterminethenumberoftimessomeFunctiongetscalled.AJasminetestdoubleoffersthefollowingfeaturesandmore:
ThecountofcallsonafunctionTheabilitytospecifyareturnvalue(stubareturnvalue)Theabilitytopassacalltotheunderlyingfunction(passthrough)
Throughoutthisbook,youwillgainfurtherexperienceintheuseoftestdoubles.
StubbingareturnvalueThegreatthingaboutusingatestdoubleisthattheunderlyingcodeofamethoddoesnothavetobecalled.Withatestdouble,youcanspecifyexactlywhatamethodshouldreturnforagiventest.Hereisanexamplefunction:
varobjectUnderTest={
someFunction:function(){return'stubme!';}
};
Theprecedingobject(objectUnderTest)hasafunction(someFunction)thatneedstobestubbed.HereishowyoucanstubthereturnvalueusingJasmine:
jasmine.spyOn(objectUnderTest,'someFunction')
.and
.returnValue('stubbedvalue');
Now,whenobjectUnderTest.someFunctioniscalled,stubbedvaluewillbereturned.Hereishowtheprecedingstubbedvaluecanbeconfirmedusingconsole.log:
varobjectUnderTest={
www.it-ebooks.info
someFunction:function(){return'stubme!';}
};
//beforethereturnvalueisstubbed
Console.log(objectUnderTest.someFunction());
//displays'stubme'
jasmine.spyOn(objectUnderTest,'someFunction')
.and
.returnValue('stubbedvalue');
//Afterthereturnvalueisstubbed
Console.log(objectUnderTest.someFunction());
//displays'stubbedvalue'
TestingargumentsAtestdoubleprovidesinsightsintohowamethodisusedinanapplication.Asanexample,atestmightwanttoassertwhatargumentsamethodwascalledwithorthenumberoftimesamethodwascalled.Hereisanexamplefunction:
varobjectUnderTest={
someFunction:function(arg1,arg2){}
};
Herearethestepstotesttheargumentstheprecedingfunctioniscalledwith:
1. Createaspysothattheargumentscalledcanbecaptured:
jasmine.spyOn(objectUnderTest,'someFunction');
2. Thentoaccessthearguments,dothefollowing:
//Gettheargumentsforthefirstcallofthefunction
varcallArgs=objectUnderTest.someFunction.call.argsFor(0);
console.log(callArgs);
//displays['param1','param2']
3. Hereishowtheargumentscanbedisplayedusingconsole.log:
varobjectUnderTest={
someFunction:function(arg1,arg2){}
};
//createthespy
jasmine.spyOn(objectUnderTest,'someFunction');
//Callthemethodwithspecificarguments
objectUnderTest.someFunction('param1','param2');
//Gettheargumentsforthefirstcallofthefunction
varcallArgs=objectUnderTest.someFunction.call.argsFor(0);
console.log(callArgs);
//displays['param1','param2']
www.it-ebooks.info
RefactoringRefactoringistheactofrestructuring,rewriting,renaming,andremovingcodeinordertoimprovethedesign,readability,maintainability,andoverallaestheticofapieceofcode.TheTDDlifecyclestepof“makingitbetter”isprimarilyconcernedwithrefactoring.Thissectionwillwalkyouthrougharefactoringexample.Hereisanexampleofafunctionthatneedstoberefactored:
varabc=function(z){
varx=false;
if(z>10)
returntrue;
returnx;
}
Thisfunctionworksfineanddoesnotcontainanysyntacticalorlogicalissues.Theproblemisthatthefunctionisdifficulttoreadandunderstand.Refactoringthisfunctionwillimprovethenaming,structure,anddefinition.Theexercisewillremovethemasqueradingcomplexityandrevealthefunction’struemeaningandintention.Herearethesteps:
1. Renamethefunctionandvariablenamestobemoremeaningful,thatis,renamexandzsothattheymakesense:
varisTenOrGreater=function(value){
varfalseValue=false;
if(value>10)
returntrue;
returnfalseValue;
}
2. Now,thefunctioncaneasilybereadandthenamingmakessense.3. Removeunnecessarycomplexity.Inthiscase,theifconditionalstatementcanbe
removedcompletely:
varisTenOrGreater=function(value){
returnvalue>10;
};
4. Reflectontheresult.
Atthispoint,therefactoriscomplete,andthefunction’spurposeshouldjumpoutatyou.Theremainingquestionthatshouldbeaskedis“whydoesthismethodexistinthefirstplace?”.
Thisexampleonlyprovidedabriefwalk-throughofthestepsthatcanbetakentoidentifyissuesincodeandhowtoimprovethem.Otherexampleswillbeusedthroughoutthisbook.
www.it-ebooks.info
BuildingwithabuilderThebuilderpatternusesabuilderobjecttocreateanotherobject.Imagineanobjectwithtenproperties.Howwilltestdatabecreatedforeveryproperty?Willtheobjecthavetoberecreatedineverytest?
Abuilderobjectdefinesanobjecttobereusedacrossmultipletests.Thefollowingcodesnippetprovidesanexampleoftheuseofthispattern.Thisexamplewillusebuilderobjectinthevalidatemethod:
varbook={
id:null,
author:null,
dateTime:null
};
Thebookobjecthasthreeproperties:id,author,anddateTime.Fromatestingperspective,youwouldwanttheabilitytocreateavalidobject,thatis,onethathasallthefieldsdefined.Youmayalsowanttocreateaninvalidobjectwithmissingproperties,oryoumaywanttosetcertainvaluesintheobjecttotestthevalidationlogic,thatis,dateTimeisanactualdate.
HerearethestepstocreateabuilderforthedateTimeobject:
1. Createabuilderfunction:
varbookBuilder=function();
2. Createavalidobjectwithinthebuilder:
varbookBuilder=function(){
var_resultBook={
id:1,
author:'AnyAuthor',
dateTime:newDateTime()
};
}
3. Createafunctiontoreturnthebuiltobject:
varbookBuilder=function(){
var_resultBook={
id:1,
author:"AnyAuthor",
dateTime:newDateTime()
};
this.build=function(){
return_resultBook;
}
}
4. Createanotherfunctiontosetthe_resultBookauthorfield:
varbookBuilder=function(){
www.it-ebooks.info
var_resultBook={
id:1,
author:'AnyAuthor',
dateTime:newDateTime()
};
this.build=function(){
return_resultBook;
};
this.setAuthor=function(author){
_resultBook.author=author;
};
};
5. Makethefunctionfluentsothatcallscanbechained:
this.setAuthor=function(author){
_resultBook.author=author;
returnthis;
};
6. AsetterfunctionwillalsobecreatedfordateTime:
this.setDateTime=function(dateTime){
_resultBook.dateTime=dateTime;
returnthis;
};
Now,bookBuildercanbeusedtocreateanewbookasfollows:
varbuiltBook=bookBuilder.setAuthor('TimChaplin')
.setDateTime(newDate())
.build();
Theprecedingbuildercannowbeusedthroughoutyourteststocreateasingleconsistentobject.Hereisthecompletebuilderforyourreference:
varbookBuilder=function(){
var_resultBook={
id:1,
author:'AnyAuthor',
dateTime:newDateTime()
};
this.build=function(){
return_resultBook;
};
this.setAuthor=function(author){
_resultBook.author=author;
returnthis;
};
this.setDateTime=function(dateTime){
_resultBook.dateTime=dateTime;
returnthis;
};
www.it-ebooks.info
};
TipDownloadingtheexamplecode
Youcandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
www.it-ebooks.info
Self-testquestionsQ1.Atestdoubleisanothernameforaduplicatetest.
1. True2. False
Q2.TDDstandsfortest-drivendevelopment.
1. True2. False
Q3.Thepurposeofrefactoringistoimprovecodequality.
1. True2. False
Q4.Atestobjectbuilderconsolidatesthecreationofobjectsfortesting.
1. True2. False
Q5.The3A’sareasportsteam.
1. True2. False
www.it-ebooks.info
SummaryThischapterprovidedanintroductiontoTDD.ItdiscussedtheTDDlifecycle(testfirst,makeitrun,makeitbetter)andshowedhowthesamestepsareusedbyatailor.Finally,itlookedoversomeofthetestingtechniquesthatwillbediscussedthroughoutthisbookincluding:
TestdoublesRefactoringBuildingpatterns
AlthoughTDDisahugetopic,thisbookissolelyfocusedontheTDDprinciplesandpracticestobeusedwithAngularJS.Inthenextchapter,youwillstartthejourneyandseehowtosetuptheKarmatestrunner.
www.it-ebooks.info
Chapter2.TheKarmaWayJavaScripttestinghashitthemainstream,thankstoKarma.KarmamakesitseamlesstotestJavaScript.AngularJSwascreatedaroundtesting.ThischapterexplorestheoriginsofKarmaandwhyithastobeusedinanyAngularJSproject.Bytheendofthischapter,youwillnotonlyunderstandtheproblemthatKarmasolves,butalsowalkthroughacompleteexampleusingit.
www.it-ebooks.info
JavaScripttestingtoolsKnowingwhatthedifferenttestingtoolsareishalfthebattle.Inthissection,youwilllearnaboutthetwoprimarytoolsthatwillbediscussedandusedthroughoutthebook.Theyare:
Karma:ThisisatestrunnerProtractor:Thisisanend-to-endtestingframework
www.it-ebooks.info
KarmaBeforediscussingwhatKarmais,itisbesttodiscusswhatitisn’t.Itisn’taframeworktowritetests.Itisatestrunner.WhatthismeansisthatKarmagivesyoutheabilitytoruntestsinseveraldifferentbrowsersinanautomatedway.Inthepast,developershadtoperformmanualstepstodothis,including:
1. Openingupabrowser2. PointingthebrowsertotheprojectURL3. Runningthetests4. Confirmingthatalltestshavepassed5. Makingchanges6. Refreshingthepage
WithKarma,automationgivesthedevelopertheabilitytorunasinglecommandanddeterminewhetheranentiretestsuitehaspassedorfailed.FromaTDDperspective,thisgivesyoutheabilitytofindandfixfailingtestsquickly.SomeoftheprosandconsofusingKarmacomparedtoamanualprocessareasfollows:
Pros Cons
Abilitytoautomatetestsinmultiplebrowsersanddevices. Additionaltooltolearn,configure,andmaintain.
Abilitytowatchfiles.
Onlinedocumentationandsupport.
Doesonething—runsJavaScripttests—anddoesitwell.
Easytointegratewithacontinuousintegrationserver.
AutomatingtheprocessoftestingandusingKarmaisextremelyadvantageous.IntheTDDjourneythroughthisbook,Karmawillbeoneofyourprimarytools.
www.it-ebooks.info
ProtractorProtractorisanend-to-endtestingtool.Itallowsdeveloperstomimicuserinteractions.Itautomatesthetestingoffunctionalityandfeaturesthroughtheinteractionofawebbrowser.ProtractorhasspecificmethodstoassistwithtestingAngularJS,buttheyarenotexclusivetoAngularJS.SomeoftheprosandconsofusingProtractorareasfollows:
Pros Cons
Configurabletotestmultipleenvironments Documentationandexamplesarelimited
EasyintegrationwithAngularJS
Syntaxandtestingcanbesimilartothetestingframeworkchosenforunittesting
www.it-ebooks.info
JavaScripttestingframeworksInthissection,youwilllearnaboutthetestingframeworksthatwillsupportyouinyourTDDpractices.Theseinclude:
JasmineSeleniumMocha
www.it-ebooks.info
JasmineJasmineisaJavaScripttestingframework.ItcanbeeasilyintegratedandrunforwebsitesandisagnostictoAngularJS.Itprovidesspiesandotherfeatures.ItcanalsoberunonitsownwithoutKarma.Someoftheprosandconsareasfollows:
Pros Cons
DefaultintegrationwithKarma. Nofile-watchingfeatureavailablewhenrunningtests.Thismeansthattestshavetobererunbytheuserastheychange.
Providesadditionalfunctionstoassistwithtesting,suchastestspies,fakes,andthepass-throughfunctionality. ThelearningcurvecanbesteepforalltheProtractormethodsandfeatures.
Cleansreadablesyntaxthatallowsteststobeformattedinawaythatrelatestothebehaviorbeingtested.
Integrationwithseveraloutputreporters.
www.it-ebooks.info
SeleniumSelenium(http://www.seleniumhq.org/)definesitselfas:
“Seleniumautomatesbrowsers.That’sit!”
Automationofbrowsersmeansthatdeveloperscaninteractwithbrowserseasily.Theycanclickonbuttonsorlinks,enterdata,andsoon.Seleniumisapowerfultoolsetthat,whenusedandsetupproperly,haslotsofbenefits;however,itcanbeconfusingandcumbersometosetitup.SomeoftheprosandconsofSeleniumareasfollows:
Pros Cons
Largefeatureset Hastoberunasaseparateprocess
Distributedtesting Severalstepstoconfigure
SaaSsupportthroughservicessuchasSauceLabs
Documentationandresourcesavailable
AsProtractorisawrapperaroundSelenium,itwon’tbediscussedindetail.ProtractorwillbefurtherintroducedinChapter3,End-to-endTestingwithProtractor.
www.it-ebooks.info
MochaMochaisatestingframeworkoriginallywrittenforNode.jsapplicationsbutsupportsbrowsertestingaswell.ItisverysimilartoJasmineandmirrorsmuchofitssyntax.Let’sdiscusssomeoftheprosandconsofMocha:
Pros Cons
Easytoinstall Separateplugins/modulesrequiredforassertions,spies,andsoon
Gooddocumentationavailable AdditionalconfigurationrequiredtouseitwithKarma
Hasseveralreporters
Plugsinwithseveralnodeprojects
TheapproachofbeingjustatestrunnerandnotworryingaboutassertionsandmockingfitsintotheNode.jsmantra—smallindividualmodulesthatdoonething.ForNode.jsprojects,IprefertogowithMocha.ThereasonisthatyoucanaddnewNodePackageManager(npm)modulesforthespecificpluginsneeded.Whenworkingwithawebsite,andspecificallyAngularJS,IprefertouseJasmine.Itprovidesthefeaturesneededwithouthavingtoinstalladditionalnpmmodulestoanon-Node.jsproject.
www.it-ebooks.info
BirthofKarmaWhenpickingupanewtool,itisimportanttounderstandwhereitcamefromandwhyitwasbuilt.ThissectiongivesyousomebackgroundoftheoriginsofKarma.
www.it-ebooks.info
TheKarmadifferenceKarmawascreatedbyVojtechJína.Theprojectwasoriginallycalledtestacular.InVojtechJína’sthesis,hediscussesthedesign,purpose,andimplementationofKarma.Inhisthesis(JavasScriptTestRunner,page6,https://github.com/karma-runner/karma/raw/master/thesis.pdf),hedescribesKarmaas:
“…atestrunner,thathelpswebapplicationdeveloperstobemoreproductiveandeffectivebymakingautomatedtestingsimplerandfaster.Infact,Ihaveamuchhigherambitionandthisthesisisonlyapartofit-IwanttopromoteTestDrivenDevelopment(TDD)as“the”waytodevelopwebapplications,becauseIbelieveitisthemosteffectivewaytodevelophighqualitysoftware.”
KarmahastheabilitytoeasilyandautomaticallyrunJavaScriptunittestsonrealbrowsers.Traditionally,testswererunbyhavingtomanuallylaunchabrowserandcheckforresultsbycontinuallyhittingtheRefreshbutton.Thismethodwasawkwardandoftenresultedindeveloperslimitingtheamountofteststhatwerewritten.
WithKarma,adevelopercanwriteatestinalmostanystandardtestframework,chooseabrowsertorunagainst,setthefilestowatchforchanges,andbam!Continuousautomatedtesting.Simplychecktheoutputwindowforfailedorpassedtests.
www.it-ebooks.info
ImportanceofcombiningKarmawithAngularJSKarmawasbuiltforAngularJS.PriortoKarma,therewasalackofautomatedtestingtoolsforweb-basedJavaScriptdevelopers.
Remember,Karmaisatestrunner,notatestframework.Itsjobistoruntestsandreportwhichtestswillpassorfail.Whyisthishelpful?Atestframeworkiswhereyouwillwriteyourtests.Apartfromdoingthis,youwillneedtobefocusedonrunningthetestseasilyandseeingresults.Karmaeasilyrunstestsacrossseveraldifferentbrowsers.Karmaalsohassomeotherfeatures,suchasfilewatching,whichwillbediscussedfurtherindetaillaterinthebook.
www.it-ebooks.info
InstallingKarmaTimetostartusingKarma.Installationsandapplicationsareconstantlychanging.ThefollowingguideisintendedtobebriefinthehopethatyouwillgototheKarmawebsite,http://karma-runner.github.io/,andfindthelatestinstructions.
Themainfocusofthissectionwillbeonthespecificconfigurationusedinthisbookandnotanin-depthinstallationguide.
www.it-ebooks.info
InstallationprerequisitesToinstallKarma,youneedtohaveNode.jsonyourcomputer.Node.jsrunsonGoogle’sV8engineandallowsJavaScripttoberunonseveraloperatingsystems.
Developerscanpublishnodeapplicationsandmodulesusingnpm.Thisallowsdeveloperstoquicklyintegrateapplicationsandmodulesintotheirapplications.
Karmarunsandisinstalledthroughthenpmpackage,andthereforeyouneedNode.jsbeforeyouuseorinstallKarma.ToinstallNode.js,gotohttp://nodejs.org/andfollowtheinstallationinstructions.
AssumingyouhaveNode.jsinstalled,typethefollowingcommandinthecommandprompttoinstallKarma:
$npminstallkarma-g
TheprecedingcommandusesnpmtoinstallKarmagloballyusing-g.WhatthismeansisthatyoucanuseKarmaonthecommandpromptbysimplytypingthefollowing:
$karma-–version
Bydefault,installingKarmawillinstallkarma-chrome-launcherandkarma-jasmineasdependencies.Ensurethatthesemodulesareinstalledgloballyaswell.
www.it-ebooks.info
ConfiguringKarmaKarmacomesequippedwithanautomatedwaytocreateaconfigurationfile.Tousetheautomatedway,typethefollowingcommand:
$karmainit
Hereisasampleoftheoptionschosen:
CustomizingKarma’sconfigurationThefollowinginstructionsdescribethespecificconfigurationrequiredtogetKarmarunningfortheproject.Customizationincludesthetestframework(Jasmine),browser(Chrome)totestwith,andfilestotest.Tocustomizetheconfiguration,openupkarma.confandperformthefollowingsteps:
1. Ensurethattheenabledframeworksaysjasmineusingthefollowingcode:
frameworks:['jasmine'],
2. Configurethetestdirectory.Notethatthefollowingdefinitionneedstoincludethetestsrequiredtorunalongwithanypotentialdependencies.Thedirectorythatwillholdourtestsis/test/unit/:
files:[
'test/unit/**/*.js'
],
3. SetthetestbrowsertoChrome.Itwillthenbeinitializedandwillrunapopupaftereverytest:
www.it-ebooks.info
browsers:['Chrome'],
ConfirmingKarma’sinstallationandconfigurationToconfirmKarma’sinstallationandconfiguration,performthefollowingsteps:
1. RunthefollowingcommandtoconfirmthatKarmastartswithnoerrors:
$karmastart
2. Theoutputshouldbesomethinglikethis:
$INFO[karma]:Karmav0.12.16serverstartedathttp://localhost:9876/
3. Inaddition,theoutputshouldstatethatnotestfileswerefound:
$WARN[watcher]:Pattern"test/unit/**/*.js"doesnotmatchanyfile.
4. Theoutputshoulddothisalongwithafailedtestmessage:
$Chrome35.0.1916(Windows7):Executed0of0ERROR(0.016secs/0
secs)
Thisisexpectedasnotestshavebeencreatedyet.ContinuetothenextstepifKarmaisstartedandyouwillseeyourChromebrowserwiththefollowingoutput:
Commoninstallation/configurationissuesIfJasmineorChromeLauncheraremissing,performthefollowingsteps:
Whenrunningthetest,anerrormightoccursayingmissingJasmineorChromeLauncher.Ifyougetthiserror,typethefollowingcommandtoinstallthemissingdependencies:
$npminstallkarma-jasmine-g
$npminstallkarma-chrome-launcher-g
Retrythetestandconfirmthattheerrorshavebeenresolved.
Thefollowingiswhatyouneedtodotoprovidepermissions(sudo/administrator):
Insomecases,youmightnotbeabletoinstallnpm_modulesgloballyusingthe–gcommand.Thisisgenerallyduetopermissionissuesonyourcomputer.TheresolutionistoinstallKarmadirectlyinyourprojectfolder.Usethesamecommandwithout–gtodothis:
$npminstallkarma
RunKarmausingtherelativepath:
www.it-ebooks.info
$./node_modules/karma/bin/karma--version
NowthatKarmaisinstalledandrunning,it’stimetoputittouse.
www.it-ebooks.info
TestingwithKarmaInthissection,youwillcreateatesttoconfirmKarmaisworkingasexpected.Todothis,performthefollowingsteps:
1. Createthetestdirectory.IntheKarmaconfiguration,testsweredefinedinthefollowingdirectory:
files:[
'test/unit/**/*.js'
],
Goaheadandcreatethetest/unitdirectory.
2. CreateanewfilenamedfirstTest.jsinthetest/unitdirectory.3. Writethefirsttestasfollows:
describe('whentestingkarma',function(){
it('shouldreportasuccessfultest',function(){
expect(true).toBeTruthy();
});
});
4. TheprecedingtestusesJasminefunctionsandhasthefollowingproperties:
describe:Thisprovidesabriefstringdescriptionofthethingsthatwillbetestedit:Thisprovidesabriefstringofthespecificassertionexpect:ThisprovidesawaytoassertvaluestoBeTruthy:Thisisoneofseveralpropertiesonanexpectationthatcanbeusedtomakeassertions
Thistesthasnorealvalueotherthantoconfirmtheoutputofapassingtest.
5. Bam!CheckyourconsolewindowandseethatKarmahasexecutedyourtest.Yourcommandlineshouldsaysomethinglikethis:
$INFO[watcher]:Addedfile"./test/unit/firstTest.js"
ThisoutputmeansthatKarmaautomagicallyrecognizedthatanewfilewasadded.Thenextoutputshouldsaysomethinglikethis:
$Chrome35.0.1916(Windows7):Executed1of1SUCCESS(0.02secs/
0.015secs)
Thismeansyourtesthaspassed!
www.it-ebooks.info
ConfirmingtheKarmainstallationNowtheinitialsetupandconfigurationofKarmaiscomplete.Hereisareviewofthesteps:
InstalledKarmathroughthenpmcommandInitializedadefaultconfigurationthroughthekarmainitcommandConfiguredKarmawithJasmineandatest/unittestdirectoryStartedKarmaandconfirmeditcouldbeopenedwithChromeAddedaJasminetest,firstTest.js,toourtest/unittestdirectoryKarmarecognizedthatfirstTest.jshadbeenaddedtothetestdirectoryKarmaexecutedourfirstTest.jsandreportedouroutput
Withacoupleofsteps,youwereabletoseeKarmarunningandexecutingtestsautomatically.FromaTDDperspective,youcanfocusonmovingtestsfromfailingtopassingwithoutmucheffort.Noneedtorefreshthebrowser;justcheckthecommandoutputwindow.KeepKarmarunningandallyourtestsandfileswillautomaticallybeaddedandrun.
Inthenextsections,youwillseehowtoapplyKarmawithaTDDapproach.Ifyou’reOKwithKarmasofarandwanttomoveontoProtractor,continuetothenextchapter.
www.it-ebooks.info
UsingKarmawithAngularJSHere,youwillwalkthroughaTDDapproachtoanAngularJScomponent.Bytheendofthischapter,youshouldbeableto:
FeelconfidentaboutusingKarmaanditsconfigurationUnderstandthebasiccomponentsofaJasminetestStarttounderstandhowtointegrateaTDDapproachinanAngularJSapplication
www.it-ebooks.info
GettingAngularJSAneasymethodforinstallingAngularJSintoprojectsistouseBower.FeelfreetoinstallAngularJSintoyourprojectinanywayyouprefer.FollowingisabriefdescriptiononhowtoinstallanduseBower.
BowerBowerisapackagemanagerforJavaScriptcomponents.Bowerallowsclient-sideJavaScriptcomponentstobeversionedandautomaticallydownloadedintoyourprojects.Thisallowsyoutoupgradethird-partytoolsandcomponentsandprovideaneasy,standardwaytousetoolssuchasAngularJS,Bootstrap,andmanymore.
Bowerinstallation
Bowerisannpmmodule,justlikeKarma.EnsureyouhaveNode.jsinstalledbeforeyoutrytoinstallBowerusingthefollowingsteps:
1. EnsureyouhaveBowerinstalledusingthiscode:
$npminstallbower-g
2. Initializethebower.jsonconfigurationintherootoftheproject:
$bowerinit
//Thiswillcreateabower.jsonfilewhichcontainsthedependent
packages
//Answerdefaulttoallthequestions.
Theoutputshouldbesomethinglikewhatisshowninthefollowingscreenshot:
Thatisit.NowBowerisinstalledandreadytodownloadJavaScriptpackagesintoyourproject.
InstallingAngularJSUsethefollowingcommandtoinstallAngularJSusingBower:
www.it-ebooks.info
$bowerinstallangular
Typethepreviouscommandinyourcommandpromptforthedirectoryyouwillbeworkingin.Aftertheinstallationiscomplete,lookatyourdirectoryandconfirmthatabower_componetsdirectorywascreated.Insidethis,thereshouldbeafolderforAngularJS:
InstallingAngularmocksAngularmocksallowsyoutotestAngularJScomponents.Theofficialdefinition,whichisfoundathttps://docs.angularjs.org/api/ngMock,isasfollows:
“ThengMockmoduleprovidessupporttoinjectandmockAngularservicesintounittests.Inaddition,ngMockalsoextendsvariouscorengservicessuchthattheycanbeinspectedandcontrolledinasynchronousmannerwithintestcode.”
ToinstallAngularmocks,simplyuseBower:
$bowerinstallangular-mocks
InitializingKarmaAkarma.conffileisrequiredtotellKarmahowitshouldrunfortheapplicationinquestion.Thebestwaytoinitializeitistorunthefollowingcommandinthecommandprompt:
$karmainit
Usethedefaultanswers.Afterkarma.confhasbeencreatedinthecurrentdirectory,openuptheconfiguration.TheoneconfigurationthatneedstochangeisthedefinitionofthefilesforKarmatouse.Usethefollowingdefinitioninthefilessection,whichdefinesthefilesrequiredtorunthetest:
files:[
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'app/**/*.js',
'spec/**/*.js'
],
Theprecedingconfigurationloadsangular.js,JavaScriptfilesintheappdirectory,andyourtestsinthespecfolder.
EnsurethatKarmacanrunyourconfiguration:
$karmastart
Thecommandoutputshouldstatesomethinglikethis:
www.it-ebooks.info
$Chrome35.0.1916(Windows7):Executed0of0ERROR(0.01secs/0secs)
Thatisit.KarmaisnowrunningforthefirstAngularJSapplication.
www.it-ebooks.info
TestingwithAngularJSandKarmaThepurposeofthisfirsttestusingKarmaistocreateadynamicto-dolist.ThiswalkthroughwillfollowtheTDDstepswediscussedinChapter1,IntroductiontoTest-drivenDevelopment:testfirst,makeitrun,andmakeitbetter.ThiswillallowyoutogainmoreexperienceinusingTDDwithAngularJS.
www.it-ebooks.info
Adevelopmentto-dolistBeforeyoustartthetest,setyourfocusonwhatneedstobedevelopedusingadevelopmentto-dolist.Thiswillallowyoutoorganizeyourthoughts.Hereistheto-dolist:
Maintainalistofitems:
Theexamplelistconsistsoftest,execute,andrefactor
Addanitemtothelist:
Theexamplelistafteryouaddtheitemistest,execute,refactor,andrepeat
Removeanitemfromthelist:
Theexamplelistafteryouaddandremovetheitemistest,execute,andrefactor
www.it-ebooks.info
TestingalistofitemsThefirstdevelopmentitemistoprovideyouwiththeabilitytohavealistofitemsonacontroller.ThenextcoupleofstepswillwalkyouthroughtheTDDprocessofaddingthefirstfeatureusingtheTDDlifecyclethatistestfirst,makeitrun,makeitbetter.
TestfirstDeterminingwheretostartisoftenthehardestpart.Thebestwayistorememberthe3A’s(Assemble,Act,andAssert)andstartwiththebaseJasminetemplateformat.Thecodetodothisisasfollows:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
describe:Thisdefinesthemainfeaturewearetesting.Thestringwillexplainthefeatureinreadabletermsandthenthefunctionwillfollowwiththetest.beforeEach:Thisistheassemblestep.ThefunctiondefinedinbeforeEachwillgetexecutedbeforeeveryassert.Itisbesttoputthetestsetuprequiredbeforeeachtestinthisfunction.it:Thisistheactandassertstep.Intheitsection,youwillperformtheactionbeingtested,followedbysomeassertion.Theactstepdoesn’thavetogointotheitfunction.Dependingonthetest,itmightbemoresuitedinthebeforeEachfunction.
Assemble,Act,andAssert(3A’s)Nowthatthetemplateisthere,wecanstartfillinginthepieces.Wewillagainfollowthe3A’smantra.
Thefollowingarethetwopartsoftheassemblesection.
Inthefirstpart,weinitializethemoduleusingthefollowingcode:
...
beforeEach(function(){
module('todo');
});
...
ThiscodewillusetheAngularmocksJavaScriptlibrarytoinitializetheAngularJSmodulebeingtested.Wehaven’tdefinedthetodomodule,butwewilldothisafterwegetafailingtest.
ThesecondparttalksaboutthescopeofTodoController.TheTodoControllerscopewillcontainthelistofitemsonitsscopevariable.ItisrequiredthatthetesthasaccesstothescopeofTodoController.Angularmockswillbeusedtogetthis.Addthefollowing
www.it-ebooks.info
codetobeforeEachtogetthecontroller’sscope:
//scope–holditemsonthecontroller
varscope={};
beforeEach(function(){
//...
//inject–accessangularcontrollerinject(function($controller){
//$controller–initializecontrollerwithtestscope
$controller('TodoController',{$scope:scope});
});
//...
});
Thefollowingisabriefexplanationofeachofthecodeelements:
scope:Thisvariableisusedtoholdandtestthelistitemsonthecontroller.inject:TheAngularmocksfunctionisusedtoaccessAngularJS’s$controller.ThisessentiallyallowsyoutogetaccessandinjectdependenciesintoAngularJSobjects.$controller:ThisinitializesthescopeofTodoController.Thetest’sscopevariablewillnowcontainthecontroller’sscope.
Inthecaseof“act”,thereisnomethodtoacton.Thescopeobjecthasalreadybeenretrievedaspartoftheassemblestep.
Inassert,therearetwopartsagain:
ThefirstassertionistoensuretheTodoControllerscopehasalistvariabledefinedwiththreeitems.Thelistvariablewillbeusedtoholdthelistofalltheitems:
it('shoulddefinealistobject',function(){
expect(scope.list).toBeDefined();
});
Thesecond,third,andfourthassertionswillbeusedtoconfirmwhetherthedatainthelistisinthecorrectorder,thatis,firstistest,secondisexecute,andthirdisrefactor:
//Secondtest
it('shoulddefinealistobject',function(){
expect(scope.list[0]).toEqual('test');
});
//Thirdtest
it('shoulddefinealistobject',function(){
expect(scope.list[1]).toEqual('execute');
});
//Fourthtest
it('shoulddefinealistobject',function(){
expect(scope.list[2]).toEqual('refactor');
});
MakeitrunThenextstepintheTDDlifecycleistomaketheapplicationrunandfixthecodesothat
www.it-ebooks.info
thetestspass.Remember,thinkaboutthesmallestcomponentsthatcanbeaddedtomakethetestpassbyproceedingwiththefollowingsteps:
1. RunKarmabytypingthefollowingcommand:
$karmastart
2. Ifyouencounter[$injector:moduler]Failedtoinstantiatemoduletododuetoerror,thenitcanbeduetothefollowing:
Theprecedingerrormessageissayingthatthetodomodulehasn’tbeendefined.Sincetheerrormessageistellingyouwhatisrequired,thisistheperfectplacetostart.Createanewfileintheappdirectorynamedtodo.Theworkingdirectoryshouldnowlooksomethinglikethis:
Addthetodomoduletothebeginningofyournewfileasfollows:
angular.module('todo',[]);
ReviewtheconsolewindowwhereKarmaisrunning.Youshouldnowseeanewerror.
3. Error:The[ng:areq]argumentTodoControllerisnotafunction,gotundefined:
Thiserrormessageisdescribingexactlywhatneedstobedone.Thereisnoneedtodeciphererrormessagesorstacktraces.Simplyupdatethetodo.jsfilesoitcontainsanAngularJScontrollerasfollows:
angular.module('todo',[])
.controller('TodoController',[])
Inthepreviouscode,wedidn’ttryanddefinethelogicrequired;weonlyaddedthesmallestcomponenttomeettheerrormessage.Reviewtheconsolewindowforthenexterror.
4. Error:Theexpectedundefinedtobedefinedasfollows:
Thenewerrormessageisagainclear.Wecanalsoseethatthecodehasnowpasseduptothepointofourassertionatthefollowingpoint:
expect(scope.list).toBeDefined();
Asthereisnolistonthescope,youneedtoaddone.Updatetheapp/todo.jsfileasfollows:
www.it-ebooks.info
.controller('TodoController',['$scope',function($scope){
$scope.list=[];
}])
Reviewtheconsolewindow.
5. Youshouldnowseeoneofthefourtestspass!ThismeansyouhavesuccessfullyusedTDDandKarmatogetyourfirsttesttopass.Nowyouneedtofixtheotherthree.ThenexterrorisExpectedundefinedtoequal'test':
Theerroroutputagaindescribesexactlywhatneedstohappen.Youjustneedtoinitializethearraywiththeelementstest,execute,andrun.Gotoapp/todo.jsandaddthedatatothearrayinitialization:
angular.module('todo',[])
.controller('TodoController',['$scope',function($scope){
$scope.list=['test','execute','refactor'];
}]);
ReviewtheoutputintheKarmawindow.
6. Excellent!Theoutputisingreenandstatesthatallthetestshavepassed.
Theresultmoduleandcontrollercodefromthisstepisasfollows:
//Amodulefortheapplication
angular.module('todo',[])
//Acontrollertomanagetheto-doitems.controller('TodoController',
['$scope',function($scope){
//theinitializationofitemsonthecontrollerscope
$scope.list=['test','execute','refactor'];
}]);
Nowthatthe“makeitrun”stepiscomplete,youcanmoveontothenextstepandmakeitbetter.
MakeitbetterUntilthispoint,therewasnothingrequiredtodirectlyrefactororthathadbeenidentifiedinthedevelopmentto-dolist.Areviewofthedevelopmentto-dolistshowsthatanitemcanbecrossedout:
Viewalistofto-dolistitems:
Theexamplelistconsistsoftest,execute,andrefactor
Addanitemtoato-do-list:
Theexamplelistafteryouaddtheitemwillconsistoftest,execute,refactor,andrepeat
Removeanitemfromato-do-list:
Theexamplelistafteryouaddandthenremovetheitemwillconsistoftest,execute,andrefactor
www.it-ebooks.info
Nextupistherequirementtoaddanewitemtothelist.TheTDDrhythmwillbefollowedagain:testfirst,makeitrun,andmakeitbetter.
www.it-ebooks.info
AddingafunctiontothecontrollerThenexttaskistogivethecontrollertheabilitytoadditemstothescopelist.Thiswillrequiretheadditionofamethodtothescope.Thiswalk-throughwillfollowthesameTDDstepsasdonepreviously.
TestfirstInsteadofcreatinganewfileandduplicatingsomeoftheassemblesteps,thefollowingtestwillbeinsertedunderthelastitmethod.Thereasonisbecausethesamemoduleandcontrollerwillbeused:
describe('whenusingato-dolist',function(){
varscope=null;
beforeEach(function(){
//...
});
//...
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
});
Assemble,Act,andAssert(3A’s)Nowthatthetemplateisthere,wecanstartfillinginthegapsusingthe3A’smantra:
1. Assemble:Thereisnoinitializationorsetuprequired,asthemoduleandcontrollerscopewillbeinherited.
2. Act:Here,youneedtoactontheaddmethodwithanewitem.Weplacetheactfunctionintothebeforeeachfunction.Thisallowsustorepeatthesamestepif/whenmoretestsareadded:
beforeEach(function(){
scope.add('repeat');
});
3. Assert:Here,anitemshouldbeaddedtothelist,andthenyouneedtoconfirmthatthelastiteminthearrayisasexpected:
it('shouldadditemtolastiteminlist',function(){
varlastIndexOfList=scope.list.length-1;
expect(scope.list[lastIndexOfList]).toEqual('repeat');
});
www.it-ebooks.info
MakeitrunThenextstepintheTDDlifecycleistomakeitrun.Remember,thinkaboutthesmallestcomponentsthatcanbeaddedtomakethetestpass,asfollows:
1. EnsureKarmaisrunninginyourconsolebytypinginthefollowingcommand:
$karmastart
2. ThefirsterrorwillstateTypeError:undefinedisnotafunction:
Theerrorreferstothefollowinglineofcode:
scope.add('repeat');
Theerroristellingyouthattheaddmethodhasn’tbeendefined.Theaddfunctionwillneedtobeaddedtotheapp/todo.jscode.Thecontrollerhasalreadybeendefined,sotheaddfunctionneedstobeplacedonthecontroller’sscope:
angular.module('to-do',[])
.controller('TodoController',['$scope',function($scope){
//...
$scope.add=function(){};
}]);
Noticehowtheaddfunctiondoesn’tcontainanylogic.Thesmallestcomponenthasbeenaddedtogetthetesttosatisfytheerrormessage.Reviewtheconsolewindowforthenexterror.
3. Error:Expected'refactor'toequal'repeat':
Havealookatthefollowingexpectation:
it('shouldadditemtolastiteminlist',function(){
varlastIndexOfList=scope.list.length-1;
expect(scope.list[lastIndexOfList]).toEqual('repeat');
});
Thefailedassertioninstep2istellingusthatbasedontheprecedingexpectation,theexpectedresultofrepeatisnotwhatthelastiteminthelisthas.Thesmallestpossiblethingthatcanbeaddedtomakethisassertionpassistopushrepeattotheendofthelistintheaddfunction.Hereishowtodothis:
//...
$scope.add=function(){
$scope.list.push('repeat');
};
//...
Reviewtheconsoletoseewhatthenextoutputsays.
4. Success!Allfivetestshavenowpassed.
Theresultingcodeaddedtogettheteststopassisasfollows:
www.it-ebooks.info
//Amodulefortheapplication
angular.module('todo',[])
//Acontrollertomanagetheto-doitems
.controller('TodoController',['$scope',function($scope){
//theinitializationofitemsonthecontrollerscope
$scope.list=['test','execute','refactor'];
$scope.add=function(){
$scope.list.push('repeat');
};
}]);
MakeitbetterThemainthingthatweneedtorefactoristhattheaddfunctionstillhasn’tbeenfullyimplemented.Itcontainsahardcodedvalue,andtheminutewesendinadifferentitemintotheaddfunction,thetestwillfail.
KeepKarmarunningsowecankeeppassingthetestsaschangesaremade.Themainissuewiththecurrentaddmethodisasfollows:
Itdoesn’tacceptanyparameterItdoesn’tpushaparameterontothelistbutusesahardcodedvalue
Theresultantaddfunctionshouldnowlookasfollows:
$scope.add=function(item){
$scope.list.push(item);
};
ConfirmthattheKarmaoutputstilldisplayssuccess:
$Chrome35.0.1916(Windows7):Executed5of5SUCCESS(0.165secs/0.153
secs)
www.it-ebooks.info
Self-testquestionsSelf-testquestionswillhelpyoufurthertestyourknowledgeofusingTDDwithAngularJSandKarma.
Q1.HowdoyouuseKarmatocreateaconfigurationfile?
1. karmaconfig2. karmainit3. karma–configkarma.conf.js
Q2.TheJasminetestmethodnamedbeforegetsexecutedbeforeeverytest.
1. True2. False
Q3.BowerisusedtoinstallKarma.
1. True2. False
Q4.The3A’sstandforwhichoneofthese?
1. Agroupofsuperheroes2. Assemble,Act,andAssert3. Accept,approve,andact
www.it-ebooks.info
SummaryInthischapter,wereviewedJavaScripttestingframeworksandtoolsanddiscussedhowVojtechJínacreatedKarma.Wesawhowtoinstall,configure,andrunKarma.Finally,youhavewalkedthroughanexampleofusingKarmawithTDD.Inthenextchapter,youwilllearnaboutend-to-endtestingwithProtractor.
www.it-ebooks.info
Chapter3.End-to-endTestingwithProtractorUnittestingisonlyoneaspectoftesting.Inthischapter,wewilllookatend-to-endtestingapplications,throughalllayersofanapplication.YouwillbeintroducedtoProtractor,theend-to-endtestingtoolfromtheAngularJSteam.Wewilllookintowhyitwascreatedandtheproblemsitsolves.Finally,wewillseehowtoinstall,configure,anduseProtractorwithTDD.
www.it-ebooks.info
AnoverviewofProtractorProtractorisanend-to-endtestingtoolthatrunsusingNode.jsandisavailableasannpmpackage.BeforetalkingaboutProtractorspecifically,youneedtounderstandwhatend-to-endtestingis.End-to-endtestingistestinganapplicationagainstalltheinterconnectedmovingpartsandlayersofanapplication.Thisdiffersfromunittests,wherethefocusisonindividualcomponentssuchascontrollers,services,directives,andsoon.Withend-to-endtesting,thefocusisonhowtheapplicationoramodule,asawhole,works,suchasconfirmingtheclickofabuttondoesx,y,andz.
Protractorallowstheend-to-endtestingofanapplication.Thisincludestheabilitytosimulatetheclickofabuttonandinteractwithanapplicationinthesamewayauserwould.Itthenallowsexpectationstobesetbasedonwhattheuserwouldexpect.Toputthisintocontext,thinkaboutthefollowinguserspecification:
AssumingIinputabcintothesearchbox,thefollowingshouldoccur:
ThesearchbuttonishitAtleastoneresultshouldbereceived
Theprecedingspecificationdescribesabasicsearchfeature.Nothingintheprecedingspecificationdescribesacontroller,directive,orservice;itonlydescribestheexpectedapplicationbehavior.Ifauserweretotestthespecification,theymayperformthefollowingsteps:
1. Pointthebrowsertothewebsite2. Selecttheinputfield3. Typeabcintheinputfield4. ClickontheSearchbutton5. Confirmthatthesearchoutputdisplaysatleastoneresult.
ThestructureandsyntaxofProtractormirrorsthatofJasmineandthetestsyouwroteinChapter2,TheKarmaWay.YoucanthinkofProtractorasawrapperaroundJasmine,withaddedfeaturestosupportend-to-endtesting.Towriteanend-to-endtestwithProtractor,wecanfollowthesamestepsasdescribedintheprecedingsteps,butwithcode.Herearethestepsincode:
1. Pointthebrowsertothewebsite:
browser.get('/');
2. Selecttheinputfield:
varinputField=element.all(by.css('input'));
3. Typeabcintheinputfield:
inputField.setText('abc');
4. ClickontheSearchbutton:
www.it-ebooks.info
inputField.click();
5. Findthesearchresultdetailsonthepage:
varsearchResults=element.all(by.css('#searchResult');
6. Finally,theassertionneedstobemadethatatleastoneormoresearchresultsareavailableonthescreen:
expect(searchResults).count()>=1);
Asacompletetest,thecodewillbeasfollows:
describe('GivenIinput'abc'intothesearchbox',function(){
//1–Pointbrowsertowebsite
browser.get('/');
//2–Selectinputfield
varinputField=element.all(by.css('input'));
//3-Typeabcintoinputfield
inputField.setText('abc');
//4-Pushsearchbutton
inputField.click();
it('shoulddisplaysearchresults',function(){
//5-Findthesearchresultdetails
varsearchResults=element.all(by.css('#searchResult');
//6-Assert
expect(searchResults).count()>=1);
});
});
That’sit!WhenProtractorruns,itwillopenabrowser,gotothewebsite,followtheinstructions,andfinallychecktheexpectations.Thetrickwithend-to-endtestingishavingaclearvisiononwhattheuserspecificationis,andthentranslatingthatspecificationtocode.
Thepreviousexampleisahigh-levelviewofwhatwillbedescribedthroughoutthischapter.NowthatyouhavebeenintroducedtoProtractor,therestofthechapterwillshowhowProtractorworksbehindthescenes,howtoinstallit,andfinally,walkyouthroughacompleteexampleusingTDD.
www.it-ebooks.info
OriginsofProtractorProtractorisnotthefirstend-to-endtestingtoolthattheAngularJSteambuilt.ThefirsttoolwascalledScenarioRunner.InordertounderstandwhyProtractorwasbuilt,weneedtofirstlookatitspredecessor:ScenarioRunner.
www.it-ebooks.info
EndoflifeScenarioRunnerisinmaintenancemodeandhasreacheditsendoflife.IthasbeendeprecatedinplaceofProtractor.Inthissection,wewilllookatwhatScenarioRunnerwasandwhatgapsthetoolhad.
www.it-ebooks.info
ThebirthofProtractorJulieRalphistheprimarycontributortoProtractor.AccordingtoJulieRalph,themotivationforProtractorwasbasedonthefollowingexperiencewithAngularScenarioRunner,onanotherprojectwithinGoogle(http://javascriptjabber.com/106-jsj-protractor-with-julie-ralph/):
WetriedusingtheScenarioRunner.Andwefoundthatitreallyjustcouldn’tdothethingsthatweneededtotest.Weneededtotestthingslikeloggingin.Andyourloginpageisn’tanAngularpage.AndtheScenarioRunnercouldn’tdealwiththat.Anditcouldn’tdealwiththingslikepopupsandmultiplewindows,navigatingthebrowserhistory,stufflikethat.
BasedonherexperiencewithScenarioRunner,JulieRalphdecidedtocreateProtractortofillthegaps.
ProtractortakesadvantageofthematurityoftheSeleniumproject,andwrapsupitsmethodssothatitcanbeeasilyusedforAngularJSprojects.Remember,Protractorisabouttestingthroughtheeyesoftheuser.Itwasdesignedtotestalllayersofanapplication:WebUI,backendservices,persistencelayer,andsoon.
www.it-ebooks.info
LifewithoutProtractorUnittestingisnottheonlytestingthatneedstobewrittenandmaintained.Unittestsfocusonsmallindividualcomponentsofanapplication.Bytestingsmallcomponents,theconfidenceinthecodeandlogicgrows.Unittestsdon’tfocusonhowthecompletesystemworkswheninterconnected.
End-to-endtestingwithProtractorallowsthedevelopertofocusonthecompletebehaviorofafeatureormodule.Goingbacktothesearchexample,thetestshouldonlypassifthewholeuserspecificationpasses;enterdataintothesearchbox,clickontheSearchbutton,andseetheresults.
Protractorisnottheonlyend-to-endtestingframeworkoutthere,butitisthebestchoiceforAngularJSapplications.HereareafewreasonswhyyoushouldchooseProtractor:
ItisdocumentedthroughouttheAngularJStutorialsandexamples.ItcanbewrittenusingmultipleJavaScripttestingframeworks,includingJasmineandMocha.ItprovidesconveniencemethodsforAngularJScomponents,includingwaitingforapagetoload,expectationsonpromises,andsoon.ItwrapsSeleniummethodsthatautomaticallywaitforpromisestobefulfilled.ItissupportedbySaaS(SoftwareasaService)providerssuchasSauceLabs,whichisavailableathttps://saucelabs.com/.ItissupportedandmaintainedbythesamecompanythatmaintainsAngularJSandGoogle.
www.it-ebooks.info
ProtractorinstallationIt’stimetostartgettingourhandsdirty,andinstallandconfigureProtractor.Installationsandapplicationsareconstantlychanging.Themainfocuswillbeonthespecificconfigurationusedinthisbook,andnotanin-depthinstallationguide.Thereareseveralvaryingdifferentconfigurations,sopleasereviewtheProtractorsiteforadditionaldetails.Pleasevisitthefollowingwebsitetofindthelatestinstallationandconfigurationguide:
http://angular.github.io/protractor/
Forthisbook,wewillonlybeusingthechromeOnlyconfiguration.ThechromeOnlyconfigurationdoesn’trequireseveralmovingparts,andallowsyoutogetuptospeedquickly.Asyourtestsgrowandyouarerequiredtosupportmultiplebrowsers,runningtestswithaSeleniumserverorusingsomethinglikeSauceLabsshouldbereviewed.AppendixA,IntegratingSeleniumServerwithProtractordescribeshowtosetupastandaloneSeleniumserver.
www.it-ebooks.info
InstallationprerequisitesProtractorhasthefollowingprerequisites:
Node.js:ProtractorisaNode.jsmoduleavailableusingnpm.ThebestwaytoinstallNode.jsistofollowtheinstructionsontheofficialsiteathttp://nodejs.org/download/.Chrome:ThisisawebbrowserbuiltbyGoogle.Itwillbeusedtorunend-to-endtestsinProtractorwithouttheneedforaSeleniumserver.Followtheinstallationinstructionsontheofficialsiteathttp://www.google.com/chrome/browser/.SeleniumWebDriverforChrome:Thisisatoolthatallowsyoutointeractwithwebapplications.SeleniumWebDriverisprovidedwiththeProtractornpmmodule.WewillwalkthroughtheinstructionsasweinstallProtractor.
www.it-ebooks.info
InstallingProtractorHerearethestepstoinstallProtractor:
1. OnceNode.jsisinstalledandavailableinthecommandprompt,typethefollowingcommandtoinstallProtractorinthecurrentdirectory:
$npminstallprotractor
ThepreviouscommandusesNode’snpmcommandtoinstallProtractorinthecurrentlocaldirectory.
2. Confirmthecurrentdirectorystructure:
TouseProtractorinthecommandprompt,usetherelativepathtotheProtractorbindirectory.
3. TestthattheProtractorversioncanbedeterminedasfollows:
$./node_modules/protractor/bin/protractor--version
InstallingWebDriverforChromeHerearethestepstoinstallWebDriverforChrome:
1. ToinstallSeleniumWebDriverforChrome,gotothewebdriver-managerexecutableintheProtractorbindirectorythatcanbefoundat./node_modules/protractor/bin/andtypethefollowing:
$./node_modules/protractor/bin/webdriver-managerupdate
2. Confirmthedirectorystructure.
ThepreviouscommandwillcreateaSeleniumdirectorycontainingtherequiredChromedriverusedintheproject.Thenode_modulesdirectoryshouldnowlooklikethefollowing:
www.it-ebooks.info
Theinstallationisnowcomplete.BothProtractorandSeleniumWebDriverforChromehavebeeninstalled.Wecannowmoveontotheconfiguration.
www.it-ebooks.info
CustomizingconfigurationInthissection,wewillbeconfiguringProtractorusingthefollowingsteps:
1. Startwithastandardtemplateconfiguration.
Fortunately,theProtractorinstallationcomeswithsomebaseconfigurationsinitsinstallationdirectory.Goingbacktothelocalnode_modulesdirectory,youshouldfindtheexampleChromeconfigurationintheexamplefolder:
Theexampledirectorycontainsexampleconfigurations.TheonethatwewilluseiscalledchromeOnlyConf.js.ThechromeOnlyconfigurationwillallowustorunend-to-endtestsinChromewithouttheneedforaSeleniumserver.Asdiscussedearlier,runningaSeleniumserverisanotheroptionthatwillnotbediscussedinthisbook.
2. Reviewtheexampleconfigurationfile:
ThechromeOnlyparametershouldbesettotrue,asfollows:
exports.config={
//...
chromeOnly:true,
//...
};
ThechromeDriverparameterwillhavetobemodifiedtopointtothedriverweinstalled,asfollows:
exports.config={
//...
chromeDriver:'../selenium/chromedriver',
//...
};
Thecapabilitiesparametershouldonlyspecifythenameofthebrowser:
exports.config={
//...
capabilities:{
'browserName':'chrome'
},
//...
www.it-ebooks.info
};
Thefinalimportantconfigurationisthesourcefiledeclaration:
exports.config={
//...
specs:['example_spec.js'],
//...
};
Excellent!NowwehaveProtractorinstalledandconfigured.
ConfirminginstallationandconfigurationToconfirminstallation,Protractorrequiresatleastonefiledefinedinthespecsconfigurationsection.Beforeaddingarealtestandcomplicatingthings,createanemptyfileintherootdirectorycalledconfirmConfigTest.js.Then,addthetesttothespecssectionsoitlookslikethis:
specs:['confirmConfigTest.js'],
ToconfirmthatProtractorhasbeeninstalled,runProtractorbygoingtotherootofyourprojectdirectoryandtype:
$./node_modules/protractor/bin/protractorchromeOnlyConf.js
Ifeverythingwassetupcorrectlyandinstalled,youshouldseesomethingsimilartothisinyourcommandprompt:
Finishedin0.0002seconds
0tests,0assertions,0failures
Commoninstallation/configurationissuesThefollowingaresomecommonissuesthatyoumightcomeacrosswhileinstallingWebDriverforChrome:
Seleniumnotinstalledcorrectly:IfthetestshaveerrorsrelatedtotheSeleniumWebDriverlocation,youneedtoensurethatyoufollowedthestepstoupdateWebDriver.TheupdatestepdownloadstheWebDrivercomponentsintothelocalProtractorinstallationfolder.UntilWebDriverhasbeenupdated,youwon’tbeabletoreferenceitintheProtractorconfiguration.AneasywaytoconfirmtheupdateistolookintheProtractordirectoryandensurethataSeleniumfolderexists.Unabletofindtests:WhennotestsareexecutedbyProtractor,itcanbefrustrating.Thebestplacetostartisintheconfigurationfile.Makesuretherelativepathandanyfilenamesorextensionsarecorrect.
Foramorecompletelist,pleaserefertotheofficialProtractorsiteathttp://angular.github.io/protractor/.
www.it-ebooks.info
HelloProtractorWiththeProtractorinstallationandconfigurationcomplete,youcanlookatwritingarealtest.ThissectionwillwalkyouthroughusingTDDwithProtractor.Attheendofthischapter,youshouldbeableto:
FeelconfidentinusingandconfiguringProtractorUnderstandthebasiccomponentsofaProtractortestStarttounderstandhowtointegrateaTDDapproachtoend-to-endtesting
www.it-ebooks.info
TDDend-to-endTest-drivendevelopmentisnotasilverbullet.Itisafoundationofprinciplesandtechniquesusedtoimproveefficiency,quality,andmuchmore.KnowinghowtoapplyTDDisthefirststep,butknowingwhentoapplyitisjustasimportant.
WhenapplyingTDD,youarecouplingteststoyourlogicandcode.Asadeveloper,youhavetomakedecisionsonwhenthatcouplingmakessenseandwillbeadvantageoustoyourproject.Asyouworkthroughtheexamples,beawarethattheyshowyouhowtoapplyTDDtechniques.Asyouusethesepracticesinyourownprojects,youwillneedtodeterminethedepthandcouplingoftheteststhatyourprojectandspecificationsrequire.
Thepre-setupThecodeinthistestwillleveragetheunittestedcodefromChapter2,TheKarmaWay.Youwillneedtocopythecodetoanewdirectory.
Asareminder,theapplicationwasato-doapplicationthataddsanddeletesitemsfromalist.Ithasasinglecontroller,TodoController,thathasalistofitemsandanaddmethod.Theapplicationdidn’thaveanyHTMLorusercomponents.WewilluseaTDDapproachtoaddtheUIelements.Thecurrentcodedirectoryshouldbestructuredasfollows:
www.it-ebooks.info
ThesetupThesetupwillmirrortheinstallationandconfigurationstepsfromearlier:
1. InstallProtractor.2. UpdateSeleniumWebDriver.3. ConfigureProtractorbasedontheexampleconfiguration.
FollowtheProtractorinstallationandconfigurationstepsyoulearnedintheprevioussectioninanewprojectdirectory.TheonlydifferenceisthattheProtractortestsshouldbeplacedinaspec/e2edirectory.Thiswillallowyoutoeasilyidentifythetestsinyourprojectstructure.Aftercreatingaspec/e2edirectoryupdate,theProtractorconfigurationspecsectionshouldbeasfollows:
exports.config={
//...
specs:['spec/e2e/**/*.js'],
//...
};
AfterconfirmingthatProtractorhasbeeninstalledandconfiguredproperly,youcanstartthefirsttest.
www.it-ebooks.info
TestfirstNowthatProtractorhasbeensetup,thetestingcanbegin.End-to-endtestsareslowandtouchmultiplelayersoftheapplication.Theyalsorequirethefullapplicationtobesetupandrunninginordertotest.Thereareseveraltechniquesthatwecanleveragetomockalocalenvironment.MockingdataandAPIswillbediscussedinChapter7,GiveMeSomeData.Thisfirstend-to-endtestwillonlyhaveaWebUIlayer.Noadditionalmockingwillberequired.
Asmentionedearlier,Protractorrequiresarunningapplication.Thismeansthewebsiteneedstobeavailableforyoutopointyourbrowsertoit.AsimpleapproachtoservingstaticHTTPcontentistousethehttp-servernpmmodule.Thehttp-servermoduleisperfectforalocaldevelopmentenvironment,butprobablynotsuitedforthefinalapplicationinfrastructure.YourproductionwebsitemightbedevelopedinsomethinglikeExpress,IIS,orApache.
InstallingthetestwebserverToinstallourtestwebserver,wewillusethehttp-servernodemodule.Theadvantageofawebserversuchashttp-serveristhatitrequiresverylittleconfigurationandcanjuststartandrunthewebsite.Herearethestepstoinstallthewebserver:
1. Typethefollowingcommandinthecommandline:
$npminstallhttp-server
2. Nowcreateastubindex.htmlpageattherootoftheprojectwiththebasicHTMLcomponents:
<!DOCTYPEhtml>
<html>
<head>
<title></title>
</head>
<body>
</body>
</html>
3. NowruntheHTTPserverandensurethepageisloaded:
./node_modules/http-server/bin/http-server-p8080
4. Gotohttp://localhost:8080.Youshouldseeablankpagegetloaded,withnoerrorsinthecommandoronthewebpage.Ifyouseeerrors,ensurethatthedirectoryhastherequiredindex.htmlfile.Nowthatyouhaveaworkingwebsite,itistimetoconfigureProtractortouseit.
ConfiguringProtractorProtractorcanbeconfiguredwithabaseURLforanapplication.Byspecifyingabase
www.it-ebooks.info
URL,testswilllookcleanerandcanbeeasilyconfiguredtousedifferentURLsforthesameapplication.Imagineadev,qa,andproductionURLthatusethesametests,buthavedifferentURLsthatneedtobetested.
Aswewillberunningthislocally,wewillneedtousehttp://localhost:8000asourbaseURL.UpdatetheProtractorconfigurationfileasfollows:
baseUrl:'http://localhost:8080/'
GettingdowntobusinessEnd-to-endtestingisdifferentthanunittesting.Testswillinteractwithdifferentlayersofanapplicationthroughoutasinglescenario.YoumayhaveanotherteamdesigningtheHTMLelements,CSS,andsoon.ThedevelopmentteamwillthenhavetointegratetheUIHTMLintothepage.TheTDDapproachwillallowyoutocreatetestsforseparatecomponentsindependently.Theideaisyouwanttobeabletestthefeaturesoftheapplicationthatmakesensetotest.Testingeverythingblindlycanbeawasteoftimeandarefactoringnightmare.
Inthiscase,westartwithablankcanvasofapageandwanttotestthebehavioroftheprimarycomponents.WewillfollowtheTDDlifecycle(test,execute,refactor).Intheupcomingsections,wewillcoverthefollowingsteps:
1. Reviewtheuserspecification.2. Writedownthemaintasksthatneedtobedeveloped.3. Writethetestforwhatwillbedeveloped.
Specification
Thepurposeofthisfirsttestistomanageadynamicto-dolist.
Thedevelopmentto-dolist
Wewillneedadevelopmentto-dolisttosetourfocusandorganizeourdevelopmenttasks.Performthefollowingsteps:
1. Viewtheto-dolistitems
Examplelist:test,execute,refactor
2. Addanitemtotheto-dolist
Examplelist:test,execute,refactor,repeat
3. Removeanitemfromtheto-dolist
Examplelist:test,execute,refactor
Ifyourecall,inourpreviousexample,wesetupthebackendmodulefortheto-dolistapplication.Inthiscase,wewillfocusonmanagingthelistfromtheuser’sperspective.
Testfirst
www.it-ebooks.info
JustaswediscussedwiththeKarmatest,startwiththe3A’s(Assemble,Act,Assert).ProtractortestsarewritteninthesameJasminestyleandsetup,soyoudon’thavetolearnanynewsyntax.StartwiththebasicJasminetemplateformat:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
describe:Thisdefinesthemainfeaturewetest.Thefirstparameterisastringtoexplainthefeatureandthesecondparameteristhefunctionthatcontainstheteststeps.beforeEach:ThisisthetestsetupandAssemblesection.ThefunctiondefinedinbeforeEachwillbeexecutedbeforeeveryAssert.Thisiswhereweperformanysetupmocks,spies,andothercomponentsneededtotest.it:ThisistheActandAssertsection.Inthissection,youwillperformtheactualactionbeingtested,followedbyanassertion.
Assemble,Act,Assert(3A’s)
Followthe3A’smantra:
Assemble:Asthisisanend-to-endtest,wewillinitializebydirectingthetesttogotothepageundertest.Inthiscase,thepageis/.ThisisbecausewesetthebaseURLtobehttp://localhost:8080/intheconfigurationfile.Sothecodewilllooklikethefollowing:
beforeEach(function(){
browser.get('/');
});
Act:Inthefirsttest,toviewalistofto-doitems,thereisnobuttontobeclickedoractiontobedoneinordertogetthelist.Weshouldjustbrowsetothepageandseethelistofto-doitems.Assert:Thisisourfirstfailingtest,whichwewillwriteusingProtractor.Thetestneedstodeterminewhetherthelistofto-doitems,thatistest,execute,andrefactor,isavailableonthepage.InAngularJS,thiswillbedoneusingng-repeat,meaningeachiteminalistwillberepeatedwithsomespecialHTMLtodisplayanindividualitem.
AsProtractoristestingtheactualUI,youwillneedtohavetheabilitytoselectHTMLelements.OneofthebenefitsofProtractoristhatitwrapsupAngularJScomponentssothattheycanbeeasilytested.
Intheprecedingtest,wewillusetheelementselectorwiththeby.repeaterselection.Inourcase,thefirstassertionwilllooklikethis:
it('',function(){
vartodoListItems=element.all(by.repeater('iteminlist'));
expect(todoListItems.count()).toBe(3);
www.it-ebooks.info
});
Thefirstlinewillselecttheto-dolistitemsavailableonthepage.ThesecondwillAssertthattheitemcountis3.Whenrunningthetest,ensurethewebserverisstillrunningusingthefollowingcommand:
$./node_modules/http-server/bin/http-server-p8080
Thecompletedtestlooksasfollows:
describe('',function(){
//ASSEMBLE
beforeEach(function(){
//ACT
browser.get('/');
});
it('',function(){
vartodoListItems=element.all(by.repeater('iteminlist'));
//ASSERT
expect(todoListItems.count()).toBe(3);
});
});
Runningthetest
Thestepstorunatestareasfollows:
1. RuntheProtractortestinadifferentcommandprompt,usingthefollowingcommand:
$protractorchromeOnlyConf.js
2. TheoutputshouldsaythatAngularJScouldnotbefound:
$Error:Angularcouldnotbefoundonthepagehttp://localhost:8080/
:retrieslookingforangularexceeded
Thiserrorindicatesthattheassertionsfailed.
3. Whenrunningthetest,youshouldseeaChromepop-upwiththepage.Youshouldalsoseethattheoutputfromthewebserversayssomethinglikethefollowing:
GET/”“Mozilla/5.0(WindowsNT6.1;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/36.0.1985.125Safari/537.36
Excellent!Nowyou’vegotafailingProtractortest,itistimetomakeitrun.
Makeitrun
ThenextstepintheTDDlifecycleistoexecuteandfixthecodesothatthetestspass.Asyouwalkthroughthetest,remembertousethesmallestcomponentsthatcanbeaddedtomakethetestpass:
1. Asthefirsterrorsays,Angularcan'tbefound.AddAngularJStothepagejustbeforetheclosingtagforthebodyasfollows:
//...
www.it-ebooks.info
<scriptsrc="bower_components/angular/angular.js"></script>
</body>
//...
2. Rerunthetestusingthefollowingcommand:
$protractorchromeOnlyConf.js
Theoutputshouldnowdisplaythefollowing:
$Error:Angularcouldnotbefoundonthepagehttp://localhost:8080/
:angularneverprovidedresumeBootstrap
3. Sinceyouhaven’tspecifiedtheapplicationoraddedthetodo.jspage,let’saddthesecomponentstoitaftertheAngularJSscript:
//...
<bodyng-app="todo">
<scriptsrc="bower_components/angular/angular.js"></script>
<scriptsrc="app/todo.js"></script>
//...
4. Rerunthetestusingthefollowingcommand:
$protractorchromeOnlyConf.js
Theoutputshouldnowdisplaythatourexpectationsfailed:
$Expected0tobe3.
Great!Nowtherearenomoreexecutionerrorsinourpage,onlythefailedexpectationsonthenumberoflistitems.
5. Inordertoaddtheitemstothepage,wewillneedtoaddareferencetoTodoController,andthenaddng-repeatforeachitem.Thecodeintheindex.htmlpageshouldbeasfollows:
<divng-controller="TodoController">
<ulng-repeat="iteminlist">
<li>{{item}}</li>
</ul>
</div>
6. Rerunthetestasfollows:
$protractorchromeOnlyConf.js
Theoutputshouldnowdisplaythatourassertionandtestpassed:
$1test,1assertion,0failures
Thecompletedpagebodytagwillnowlookasfollows:
<bodyng-app="todo">
<divng-controller="TodoController">
<ulng-repeat="iteminlist">
<li>{{item}}</li>
www.it-ebooks.info
</ul>
</div>
<scriptsrc="bower_components/angular/angular.js"></script>
<scriptsrc="app/todo.js"></script>
</body>
Makeitbetter
Thereisnothingthatwascalledouttorefactor.Lookingatourto-dolist,wetackledthefirsttwoitemsfromanend-to-endperspective.
1. Viewtheto-do-listitems:
Examplelist:test,execute,refactor
2. Addanitemtoato-do-list:
Examplelist:test,execute,refactor,repeat
3. Removeanitemfromato-do-list:
Examplelist:test,execute,refactor
Iwillleavethesecondandthirditemsasanexercise,sothatyoucanfurtherexploreandpracticeTDDwithProtractor.
www.it-ebooks.info
CleaningupthegapsThereareacoupleofthingsthatwerediscussedinthischapterthatneedsomefurtherclarification.Thisincludesthefollowing:
Whereistheasynchronouslogic?HowtoreallyimplementTDDwithend-to-endtests.
www.it-ebooks.info
AsyncmagicIntheprecedingtests,wesawsomemagicthatyoumightbequestioning.Herearesomeofthemagiccomponentsthatweglancedover:
LoadingapagebeforetestexecutionAssertiononelementsthatgetloadedinpromises
LoadingapagebeforetestexecutionIntheprevioustest,weusedthefollowingcodetospecifythatthebrowsershouldpointtothehomepage:
browser.get('/');
TheprecedingcommandwilllaunchthebrowserandnavigatetothebaseUrllocation.Oncethebrowserreachesthepage,itwillhavetoloadAngularJSandthenimplementtheAngularJS-specificfunctions.Ourtestsdon’thaveanywaitlogic,andthisispartofthebeautyofProtractorwithAngularJS.Thewaitingforpageloadingisalreadybuiltintheframeworkforyou.Yourtestscanthenbewrittenverycleanly.
AssertiononelementsthatgetloadedinpromisesTheassertionsandexpectationsalreadyhavepromisefulfillmentwritteninthem.Inthecaseofourtest,wewrotetheassertionsothatitexpectsthecounttobethree:
expect(todoListItems.count()).toBe(3);
However,inreality,wemayhavethoughtthatweneededtoaddasynchronoustestingtotheassertioninordertowaitforthepromisetobefulfilled,somethingmorecomplicatedlikethefollowing:
it('',function(done){
vartodoListItems=element.all(by.repeater('iteminlist'));
todoListItems.count().then(function(count){
expect(count).toBe(3);
done();
});
})
Theprecedingcodeislonger,moregranular,andhardertoread.Protractorhastheabilityforcertainelementsbuiltintoexpectationstomaketestsmoreconcise.
www.it-ebooks.info
TDDwithProtractorWithourfirsttest,thereisacleardistinctionofend-to-endtestsandunittests.Withtheunittest,wefocusedonstrongcouplingthetesttothecode.Asanexample,ourunittestspiedonthescopeforaspecificcontroller,TodoController.WeusedAngularmockstoinitializethescopewithavariablewecouldthenevaluate:
inject(function($controller){
$controller('TodoController',{$scope:scope});
});
IntheProtractortest,wedon’tcareaboutwhichcontrollerwearetestingandourfocusisontheuserperspectiveofthetest.WefirststartwiththeselectionofaparticularelementwithintheDocumentObjectModel(DOM);inourcase,thatelementistiedtoAngularJS,ng-repeat.TheAssertisthatthenumberofelementsforaspecificrepeaterisequaltotheexpectedcount.
Withtheloosecouplingoftheend-to-endtest,wecanwriteatestthatfocusesontheuserspecification,whichinitiallydisplaysthreeelements,andthenhavethefreedomtowritethatinthepage,controllers,andsoon,inanywaywewant.
www.it-ebooks.info
Self-testquestionsUseTDDwithProtractortodevelopthethirddevelopmentto-dolistitem:
Q1.Protractoruseswhichofthefollowingframeworks?
1. Selenium2. Unobtanium3. Karma
Q2.YoucaninstallAngularmocksbyrunningbowerinstallangular-mocks.
1. True2. False
Q3.WhatstepsdoestheTDDlifecycle,discussedinthisbook,consistof?
1. Testfirst,makeitrun,makeitbetter(refactor)2. Test,makeitbetter(refactor),makeitrun3. Makeitrun,test,makeitbetter
Additionally,ifyouwantmorepractice,addafunctionalitytotheapplicationtoremoveanitemfromtheto-dolist.
www.it-ebooks.info
SummaryThischapterhasgivenyoutheskillsnecessarytoinstall,configure,andapplyTDDprinciplestoend-to-endtesting.WehaveseenhowwecanleveragetheexistingTDDlifecycle(test,makeitrun,makeitbetter)andtechniqueswithProtractor.ProtractorisanimportantpartoftestinganyAngularJSapplication.Itbridgesthegaptoensuretheuser’sspecificationsworkasexpected.Whenend-to-endtestsarewrittentotheuserspecifications,theconfidenceoftheapplicationandabilitytorefactorgrows.Intheupcomingchapters,wewillseehowtoapplyKarmaandProtractorinmoredepthwithsimplestraightforwardexamples.Thenextchapterwillwalkyouthroughtestingcontrollers,usingAngularmocks,andusingProtractortoenterkeystrokes.
www.it-ebooks.info
Chapter4.TheFirstStepThefirststepisalwaysthehardest.Thischapterprovidesaninitialintroductorywalk-throughofhowtouseTDDtobuildanAngularJSapplicationwithacontroller,model,andscope.YouwillbeabletobegintheTDDjourneyandseethefundamentalsinaction.Uptothispoint,thisbookhasfocusedonafoundationofTDDandthetools.Now,wewillswitchgearsanddiveintoTDDwithAngularJS.ThischapterwillbethefirststepofTDD.WehavealreadyseenhowtoinstallKarmaandProtractor,inadditiontosmallexamplesandawalk-throughonhowtoapplyit.Thischapterwillfocusonthecreationofsocialmediacomments.ItwillalsofocusonthetestingassociatedwithcontrollersandtheuseofAngularmockstoAngularJScomponentsinatest.
www.it-ebooks.info
Preparingtheapplication’sspecificationCreateanapplicationtoentercomments.Thespecificationoftheapplicationisasfollows:
GivenIampostinganewcomment,whenIclickonthesubmitbutton,thecommentshouldbeaddedtotheto-dolistGivenacomment,whenIclickonthelikebutton,thenumberoflikesforthecommentshouldbeincreased
Nowthatwehavethespecificationofapplication,wecancreateourdevelopmentto-dolist.Itwon’tbeeasytocreateanentireto-dolistofthewholeapplication.Basedontheuserspecifications,wehaveanideaofwhatneedstobedeveloped.HereisaroughsketchoftheUI:
Holdyourselfbackfromjumpingintotheimplementationandthinkingabouthowyouwilluseacontrollerwithaservice,ng-repeat,andsoon.Resist,resist,resist!Althoughyoucanthinkofhowthiswillbedevelopedinthefuture,itisneverclearuntilyoudelveintothecode,andthatiswhereyoustartgettingintotrouble.TDDanditsprinciplesareheretohelpyougetyourmindandfocusintherightplace.
www.it-ebooks.info
SettinguptheprojectInpreviouschapters,wediscussedindetailhowaprojectshouldbesetup,explainedthedifferentcomponentsinvolved,andwalkedthroughtheentireprocessoftesting.Iwillskipthesedetailsandprovidealistinthefollowingsectionfortheinitialactionstogettheprojectsetup.
www.it-ebooks.info
SettingupthedirectoryThefollowinginstructionsarespecifictosettinguptheprojectdirectory:
1. Createanewprojectdirectory.2. GetangularintotheprojectusingBower:
bowerinstallangular
3. Getangular-mocksfortestingusingBower:
bowerinstallangular-mocks
4. Initializetheapplication’ssourcedirectory:
mkdirapp
5. Initializethetestdirectory:
mkdirspec
6. Initializetheunittestdirectory:
mkdirspec/unit
7. Initializetheend-to-endtestdirectory:
mkdirspec/e2e
Oncetheinitializationiscomplete,yourfolderstructureshouldlookasfollows:
www.it-ebooks.info
SettingupProtractorInChapter3,End-to-endTestingwithProtractor,wediscussedthefullinstallationandsetupofProtractor.Inthischapter,wewilljustdiscussthestepsatahigherlevel:
1. InstallProtractorintheproject:
$npminstallprotractor
2. UpdateSeleniumWebDriver:
$./node_modules/protractor/bin/webdriver-managerupdate
MakesurethatSeleniumhasbeeninstalled.
3. CopytheexamplechromeOnlyconfigurationintotherootoftheproject:
$cp./node_modules/protractor/example/chromeOnlyConf.js.
4. ConfiguretheProtractorconfigurationusingthefollowingsteps:
1. OpentheProtractorconfiguration.2. EdittheSeleniumWebDriverlocationtoreflecttherelativedirectoryto
chromeDriver:
chromeDriver:'./node_modules/protractor/selenium/chromedriver',
3. Editthefilessectiontoreflectthetestdirectory:
specs:['spec/e2e/**/*.js'],
5. SetthedefaultbaseURL:
baseUrl:'http://localhost:8080/',
Excellent!Protractorshouldnowbeinstalledandsetup.Hereisthecompleteconfiguration:
exports.config={
chromeOnly:true,
chromeDriver:'./node_modules/protractor/selenium/chromedriver',
capabilities:{
'browserName':'chrome'
},
baseUrl:'http://localhost:8080/',
specs:['spec/e2e/**/*.js'],
};
www.it-ebooks.info
SettingupKarmaThedetailsforKarmacanbefoundinChapter2,TheKarmaWay.Hereisabriefsummaryofthestepsrequiredtoinstallandgetyournewprojectsetup:
1. InstallKarmausingthefollowingcommand:
npminstallkarma-g
2. InitializetheKarmaconfiguration:
karmainit
3. UpdatetheKarmaconfiguration:
files:[
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'spec/unit/**/*.js'
],
NowthatwehavesetuptheprojectdirectoryandinitializedProtractorandKarma,wecandiveintothecode.Hereisthecompletekarma.conf.jsfile:
module.exports=function(config){
config.set({
basePath:'',
frameworks:['jasmine'],
files:[
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'spec/unit/**/*.js'
],
reporters:['progress'],
port:9876,
autoWatch:true,
browsers:['Chrome'],
singleRun:false
});
};
www.it-ebooks.info
Settinguphttp-serverAwebserverwillbeusedtohosttheapplication.Asthiswilljustbeforlocaldevelopmentonly,youcanusehttp-server.Thehttp-servermoduleisasimpleHTTPserverthatservesstaticcontent.Itisavailableasannpmmodule.Toinstallhttp-serverinyourproject,typethefollowingcommand:
$npminstallhttp-server
Oncehttp-serverisinstalled,youcanruntheserverbyprovidingitwiththerootdirectoryofthewebpage.Hereisanexample:
$./node_modules/http-server/bin/http-server
Nowthatyouhavehttp-serverinstalled,youcanmoveontothenextstep.
www.it-ebooks.info
Top-downorbottom-upapproachFromourdevelopmentperspective,wehavetodeterminewheretostart.Theapproachesthatwewilldiscussinthisbookareasfollows:
Thebottom-upapproach:Withthisapproach,wethinkaboutthedifferentcomponentswewillneed(controller,service,module,andsoon)andthenpickthemostlogicaloneandstartcoding.Thetop-downapproach:Withthisapproach,weworkfromtheuserscenarioandUI.Wethencreatetheapplicationaroundthecomponentsintheapplication.
Therearemeritstobothtypesofapproachesandthechoicecanbebasedonyourteam,existingcomponents,requirements,andsoon.Inmostcases,itisbestforyoutomakethechoicebasedontheleastresistance.Inthischapter,theapproachofspecificationistop-down,everythingislaidoutforusfromtheuserscenarioandwillallowyoutoorganicallybuildtheapplicationaroundtheUI.
www.it-ebooks.info
TestingacontrollerBeforegettingintothespecification,andthemind-setofthefeaturebeingdelivered,itisimportanttoseethefundamentalsoftestingacontroller.AnAngularJScontrollerisakeycomponentusedinmostapplications.
www.it-ebooks.info
AsimplecontrollertestsetupWhentestingacontroller,testsarecenteredonthecontroller’sscope.Thetestsconfirmeithertheobjectsormethodsinthescope.Angularmocksprovideinject,whichfindsaparticularreferenceandreturnsitforyoutouse.Wheninjectisusedforthecontroller,thecontrollersscopecanbeassignedtoanouterreferencefortheentiretesttouse.Hereisanexampleofwhatthiswouldlooklike:
describe('',function(){
varscope={};
beforeEach(function(){
module('anyModule');
inject(function($controller){
$controller('AnyController',{$scope:scope});
});
});
});
Intheprecedingcase,thetest’sscopeobjectisassignedtotheactualscopeofthecontrollerwithintheinjectfunction.Thescopeobjectcannowbeusedthroughoutthetest,andisalsoreinitializedbeforeeachtest.
www.it-ebooks.info
InitializingthescopeIntheprecedingexample,scopeisinitializedtoanobject{}.Thisisnotthebestapproach;justlikeapage,acontrollermightbenestedwithinanothercontroller.Thiswillcauseinheritanceofaparentscopeasfollows:
<bodyng-app='anyModule'>
<divng-controller='ParentController'>
<divng-controller='ChildController'>
</div>
</div>
</body>
Asseenintheprecedingcode,wehavethishierarchyofscopesthattheChildControllerfunctionhasaccessto.Inordertotestthis,wehavetoinitializethescopeobjectproperlyintheinjectfunction.Hereishowtheprecedingscopehierarchycanberecreated:
inject(function($controller,$rootScope){
varparentScope=$rootScope.$new();
$controller('ParentController',{$scope:parentScope});
varchildScope=parentScope.$new();
$controller('AnyController',{$scope:childScope});
});
Therearetwomainthingsthattheprecedingcodedoes:
The$rootScopescopeisinjectedintothetest.The$rootScopescopeisthehighestlevelofscopethatexists.Eachlevelofscopeiscreatedwiththe$new()method.Thismethodcreatesthechildscope.
Inthischapter,wewillusethesimplifiedversionandinitializethescopetoanemptyobject;however,itisimportanttounderstandhowtocreatethescopewhenrequired.
www.it-ebooks.info
BringonthecommentsNowthatthesetupandapproachhavebeendecided,wecanstartourfirsttest.Fromatestingpointofview,aswewillbeusingatop-downapproach,wewillwriteourProtractortestsfirstandthenbuildtheapplication.WewillfollowthesameTDDlifecyclewehavealreadyreviewed,thatis,testfirst,makeitrun,andmakeitbetter.
www.it-ebooks.info
TestfirstThescenariogivenisinawell-specifiedformatalreadyandfitsourProtractortestingtemplate:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
Placingthescenariointhetemplate,wegetthefollowingcode:
describe('GivenIampostinganewcomment',function(){
describe('WhenIpushthesubmitbutton',function(){
beforeEach(function(){
});
it('Shouldthenaddthecomment',function(){
});
});
});
Followingthe3A’s(Assemble,Act,Assert),wewillfittheuserscenariointhetemplate.
AssembleThebrowserwillneedtopointtothefirstpageoftheapplication.AsthebaseURLhasalreadybeendefined,wecanaddthefollowingtothetest:
beforeEach(function(){
browser.get('/');
});
Nowthatthetestisprepared,wecanmoveontothenextstep,Act.
ActThenextthingweneedtodo,basedontheuserspecification,isaddanactualcomment.Theeasiestthingistojustputsometextintoaninputbox.Thetestforthis,againwithoutknowingwhattheelementwillbecalledorwhatitwilldo,istowriteitbasedonwhatitshouldbe.
Hereisthecodetoaddthecommentsectionfortheapplication:
beforeEach(function(){
...
varcommentInput=$('input');
commentInput.sendKeys('acomment');
});
Thelastassemblecomponent,aspartofthetest,istopushtheSubmitbutton.ThiscanbeeasilyachievedinProtractorusingtheclickfunction.Eventhoughwedon’thaveapageyet,oranyattributes,wecanstillnamethebuttonthatwillbecreated:
www.it-ebooks.info
beforeEach(function(){
...
varsubmitButton=element.all(by.buttonText('Submit')).click();
});
Finally,wewillhitthecruxofthetestandasserttheusers’expectations.
AssertTheuserexpectationisthatoncetheSubmitbuttonisclicked,thecommentisadded.Thisisalittleambiguous,butwecandeterminethatsomehowtheuserneedstogetnotifiedthatthecommentwasadded.Thesimplestapproachistodisplayallcommentsonthepage.InAngularJS,theeasiestwaytodothisistoaddanng-repeatobjectthatdisplaysallcomments.Totestthis,wewilladdthefollowing:
it('Shouldthenaddthecomment',function(){
varcomments=element(by.repeater('commentincomments')).first();
expect(comment.getText()).toBe('acomment');
});
Now,thetesthasbeenconstructedandmeetstheuserspecifications.Itissmallandconcise.Hereisthecompletedtest:
describe('GivenIampostinganewcomment',function(){
describe('WhenIpushthesubmitbutton',function(){
beforeEach(function(){
//Assemble
browser.get('/');
varcommentInput=$('input');
commentInput.sendKeys('acomment');
//Act
//Act
varsubmitButton=element.all(by.buttonText('Submit')).
click();
});
//Assert
it('Shouldthenaddthecomment',function(){
varcomments=element(by.repeater('commentin
comments')).first();
expect(comment.getText()).toBe('acomment');
});
});
});
www.it-ebooks.info
MakeitrunBasedontheerrorsandoutputofthetest,wewillbuildourapplicationaswego.
1. Thefirststeptomakethecoderunistoidentifytheerrors.Beforestartingoffthesite,let’screateabarebonesindex.htmlpage:
<!DOCTYPEhtml>
<html>
<head>
<title></title>
</head>
<body>
</body>
</html>
Alreadyanticipatingthefirsterror,addAngularJSasadependencyinthepage:
<scripttype='text/javascript'
src='bower_components/angular/angular.js'></script>
</body>
2. Now,startingthewebserverusingthefollowingcommand:
$./node_modules/http-server/bin/http-server-p8080
3. RunProtractortoseethefirsterror:
$./node_modules/.bin/protractorchromeOnlyConf.js
4. OurfirsterrorstatesthatAngularJScouldnotbefound:
Error:Angularcouldnotbefoundonthepagehttp://localhost:8080/:
angularneverprovidedresumeBootstrap
Thisisbecauseweneedtoaddng-apptothepage.Let’screateamoduleandaddittothepage.
ThecompleteHTMLpagenowlooksasfollows:
<!DOCTYPEhtml>
<html>
<head>
<title></title>
</head>
<body>
<scriptsrc="bower_components/angular/angular.js"></script>
</body>
</html>
AddingthemoduleThefirstcomponentthatyouneedtodefineisanng-appattributeintheindex.htmlpage.
www.it-ebooks.info
Usethefollowingstepstoaddthemodule:
1. Addng-appasanattributetothebodytag:
<bodyng-app='comments'>
2. Now,wecangoaheadandcreateasimplecommentsmoduleandaddittoafilenamedcomments.js:
angular.module('comments',[]);
3. Addthisnewfiletoindex.html:
<scriptsrc='app/commentController.js'></script>
4. ReruntheProtractortesttogetthenexterror:
$Error:Noelementfoundusinglocator:By.cssSelector('input')
Thetestcouldn’tfindourinputlocator.Youneedtoaddtheinputtothepage.
AddingtheinputHerearethestepsyouneedtofollowtoaddtheinputtothepage:
1. Allwehavetodoisaddasimpleinputtagtothepage:
<inputtype='text'/>
2. Runthetestandseewhatthenewoutputis:
$Error:Noelementfoundusinglocator:by.buttonText('Submit')
3. Justlikethepreviouserror,weneedtoaddabuttonwiththeappropriatetext:
<buttontype='button'>Submit</button>
4. Runthetestagainandthenexterrorisasfollows:
$Error:Noelementfoundusinglocator:by.repeater('commentin
comments')
Thisappearstobefromourexpectationthatasubmittedcommentwillbeavailableonthepagethroughng-repeat.Toaddthistothepage,wewilluseacontrollertoprovidethedatafortherepeater.
ControllerAswementionedintheprecedingsection,theerrorisbecausethereisnocommentsobject.Inordertoaddthecommentsobject,wewilluseacontrollerthathasanarrayofcommentsinitsscope.Usethefollowingstepstoaddacommentsobjectinthescope:
1. CreateanewfileintheappdirectorynamedcommentController.js:
angular.module('comments')
.controller('CommentController',['$scope',function($scope){
www.it-ebooks.info
$scope.comments=[];
}])
2. AddittothewebpageaftertheAngularJSscript:
<scriptsrc='app/commentController.js'></script>
3. Now,wecanaddcommentControllertothepage:
<divng-controller='CommentController'>
4. Then,addarepeaterforthecommentsasfollows:
<ulng-repeat='commentincomments'>
<li>{{comment}}</li>
</ul>
5. RuntheProtractortestandlet’sseewhereweare:
$Error:Noelementfoundusinglocator:by.repeater('commentin
comments')
Hmmm!Wegetthesameerror.
6. Let’slookattheactualpagethatgetsrenderedandseewhat’sgoingon.InChrome,gotohttp://localhost:8080andopentheconsoletoseethepagesource(Ctrl+Shift+J).Youshouldseesomethinglikewhat’sshowninthefollowingscreenshot:
Noticethattherepeaterandcontrollerareboththere;however,therepeateriscommentedout.SinceProtractorisonlylookingatvisibleelements,itwon’tfindtherepeater.
7. Great!Nowweknowwhytherepeaterisn’tvisible,butwehavetofixit.Inorderforacommenttoshowup,ithastoexistonthecontroller’scommentsscope.Thesmallestchangeistoaddsomethingtothearraytoinitializeitasshowninthefollowingcodesnippet:
.controller('CommentController',['$scope',function($scope){
$scope.comments=['anything'];
www.it-ebooks.info
}]);
8. Nowrunthetestandwegetthefollowing:
$Expected'anything'tobe'acomment'.
Wow!Wefinallytackledalltheerrorsandreachedtheexpectation.HereiswhattheHTMLcodelookslikesofar:
<!DOCTYPEhtml>
<html>
<head>
<title></title>
</head>
<bodyng-app='comments'>
<divng-controller='CommentController'>
<inputtype='text'/>
<ul>
<ling-repeat='commentincomments'>
{{comment.value}}
</li>
</ul>
</div>
<scriptsrc='bower_components/angular/angular.js'></script>
<scriptsrc='app/comments.js'></script>
<scriptsrc='app/commentController.js'></script>
</body>
</html>
Thecomments.jsmodulelooksasfollows:
angular.module('comments',[]);
HereiscommentController.js:
angular.module('comments')
.controller('CommentController',['$scope',function($scope){
$scope.comments=[];
}])
MakeitpassWithTDD,youwanttoaddthesmallestpossiblecomponenttomakethetestpass.Sincewehavehardcoded,forthemoment,thecommentstobeinitializedtoanything,changeanythingtoacomment;thisshouldmakethetestpass.Hereisthecodetomakethetestpass:
angular.module('comments')
.controller('CommentController',['$scope',function($scope){
$scope.comments=['acomment'];
}]);
…
Runthetest,andbam!Wegetapassingtest:
www.it-ebooks.info
$1test,1assertion,0failures
Waitasecond!Westillhavesomeworktodo.Althoughwegotthetesttopass,itisnotdone.Weaddedsomehacksjusttogetthetestpassing.Thetwothingsthatstandoutare:
ClickingontheSubmitbutton,whichreallydoesn’thaveanyfunctionalityHardcodedinitializationoftheexpectedvalueforacomment
Theprecedingchangesarecriticalstepsweneedtoperformbeforewemoveforward.TheywillbetackledinthenextphaseoftheTDDlifecycle,thatis,makeitbetter(refactor).
www.it-ebooks.info
MakeitbetterThetwocomponentsthatneedtobereworkedare:
AddingbehaviortotheSubmitbuttonRemovinghardcodedvalueofthecomments
www.it-ebooks.info
ImplementingtheSubmitbuttonTheSubmitbuttonneedstoactuallydosomething.Wewereabletosidesteptheimplementationbyjusthardcodingthevalue.UsingourtriedandtrustedTDDtechniques,switchtoanapproachfocusedonunittesting.Sofar,thefocushasbeenontheUIandpushingchangestothecode.Wehaven’twrittenasingleunittest.
Forthisnextbitofwork,wewillswitchgearsandfocusondrivingthedevelopmentoftheSubmitbuttonthroughtests.WewillbefollowingtheTDDlifecycle(testfirst,makeitrun,makeitbetter).
ConfiguringKarmaWedidsomethingverysimilarfortheto-dolistapplicationinChapter2,TheKarmaWay.Iwon’tspendasmuchtimedivingintothecode,sopleasereviewthepreviouschaptersforadeeperdiscussiononsomeoftheattributes.HerearethestepsyouneedtofollowtoconfigureKarma:
1. Updatethefilessectionwiththeaddedfiles:
files:[
...
'app/comments.js',
'app/commentController.js',
...
],
2. StartKarma:
$karmastart
3. ConfirmthatKarmaisrunning:
$Chrome36.0.1985(Windows7):Executed1of1SUCCESS(0.018secs/
0.015secs)
TestfirstLet’sfirststartwithanewfileinthespec/unitfoldercalledcomments.js.Wewillusethebasetemplate:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
Accordingtothespecification,whentheSubmitbuttonisclicked,itneedstoaddacomment.Wewillneedtofillintheblanksofthethreecomponentsofatest(Assemble,Act,Assert).
www.it-ebooks.info
Assemble
Thebehaviorwillneedtobepartofacontrollerforthefrontendtouseit.Theobjectundertestinthiscaseisthecontroller’sscopeobject;wewillneedtoaddthistotheassembleofthistest.TowireuptheAngularJScontrollerweneedtoinitializethemoduleandtheninjecttheCommentControllerscopeintothetest.AswedidinChapter2,TheKarmaWay,wewilldothesameinthefollowingcode:
varscope={};
beforeEach(function(){
module('comments');
inject(function($controller){
$controller('CommentController',{$scope:scope});
});
...
})
Now,thecontroller’sscopeobject,whichisundertest,isavailabletothetest.
Act
Thespecificationdeterminesthatweneedtocallaaddmethodinthescopeobject.AddthefollowingcodetothebeforeEachsectionofthetest:
beforeEach(function(){
…
scope.add('anyComment');
});
Nowfortheassertion.
Assert
Assertthatthecommentitemsinthescopeobjectnowcontainanycommentasthefirstelement.Addthefollowingcodetothetest:
it('',function(){
expect(scope.comments[0]).toBe('anycomment');
});
Savethefileandlet’smoveontothenextstepofthelifecycleandmakeitrun(execute).
MakeitrunNowthatwehavemostofthetestprepared,weneedtomakethetestpass.LookingattheoutputoftheconsolewhereKarmaisrunning,weseethefollowing:
$TypeError:undefinedisnotafunction…unit/comments.js:4:9
Lookingatthelinenumber,thatis4:9,ofourunittest,weseethatthisistheaddfunction.Let’sgoaheadandputinanaddfunctionintothecontroller’sscopeobjectusingthefollowingsteps:
1. Openthecontrollerscopeandcreateafunctionnamedadd:
$scope.add=function(){}
www.it-ebooks.info
2. CheckKarma’soutputandlet’sseewhereweare:
$Expected'acomment'tobe'anycomment'.
3. Now,wehavehittheexpectation.Remembertothinkofthesmallestchangetogetthistowork.Modifytheaddfunctiontosetthe$scope.commentsarraytoanycommentwhencalled:
$scope.add=function(){
$scope.comments.unshift('anycomment');
};
TipUnshiftisastandardJavaScriptfunctionthataddsanitemtothefrontofanarray.
4. WhenwecheckKarma’soutput,weseethefollowing:
$Chrome36.0.1985(Windows7):Executed1of1SUCCESS
Success!Thetestpasses,butagainneedssomework.Let’smoveontothenextstageandmakeitbetter(refactor).
MakeitbetterThemainpointthatneedstoberefactoredistheaddfunction.Itdoesn’ttakeanyarguments!Thisshouldbestraightforwardtoadd,andsimplyconfirmthattheteststillruns.UpdatetheaddfunctionofCommentController.jstotakeanargumentandusethatargumenttoaddtothecommentsarray:
$scope.add=function(commentToAdd){
$scope.comments.unshift(commentToAdd);
};
ChecktheoutputwindowofKarmaandensurethattheteststillpasses.Thecompleteunittestlooksasfollows:
describe('',function(){
varscope={};
beforeEach(function(){
module('comments');
inject(function($controller){
$controller('CommentController',{$scope:scope});
});
scope.add('anycomment');
});
it('',function(){
expect(scope.comments[0]).toBe('anycomment');
})
});
TheCommentControllerfilenowlooksasfollows:
www.it-ebooks.info
angular.module('comments')
.controller('CommentController',['$scope',function($scope){
$scope.comments=[];
$scope.add=function(commentToAdd){
$scope.comments.unshift(newComment);
};
}]);
BackupthetestchainWecompletedtheunittestandadditionoftheaddfunction.NowwecanaddthefunctiontospecifythebehavioroftheSubmitbutton.Thewaytolinktheaddmethodtothebuttonistotousetheng-clickattribute.ThestepstoaddbehaviortotheSubmitbuttonareasfollows:
1. Opentheindex.htmlpageandlinkitasfollows:
<buttontype="button"ng-click="add('acomment')">Submit</button>
Warning!Isthevaluehardcoded?Well,again,wewanttodothesmallestchangeandensurethattheteststillpasses.Wewillworkthroughourrefactorsuntilthecodeishowwewantit,butinsteadofabigbangapproach,wewanttomakesmallincrementalchanges.
2. Nowlet’sreruntheProtractortestandensurethatitstillpasses.Theoutputsaysitpasses,andweareokay.Thehardcodedvaluewasn’tremovedfromthecomments.Let’sgoaheadandremovethatnow.TheCommentsControllerfileshouldnowlookasfollows:
$scope.comments=[];
3. Runthetestandseethatwestillgetapassingtest.
Nowthelastthingweneedtomopupisthehardcodedvalueinng-click.Thecommentbeingaddedshouldbedeterminedbytheinputinthecommentinputtext.
BindtheinputHerearethestepsyouneedtofollowtobindtheinput:
1. Tobeabletobindtheinputintosomethingmeaningful,addanng-modelattributetotheinputtag:
<inputtype='text'ng-model='newComment'/>
2. Then,intheng-clickattribute,simplyusethenewCommentmodelastheinput:
<buttontype='button'ng-click='add(newComment)'>Submit</button>
RuntheProtractortestandconfirmthateverythinghaspassedandisgoodtogo.
www.it-ebooks.info
OnwardsandupwardsNowthatwehavethefirstspecificationworkingend-to-endandunittested,wecanstartthenextspecification.Thenextspecificationstatesthattheuserswanttheabilitytolikeacomment.
Wewillusethesametop-downapproachandstartourtestfromaProtractortest.WewillcontinuetofollowtheTDDlifecycle,thatis,testfirst,makeitrun,makeitbetter.
www.it-ebooks.info
TestfirstFollowingthepattern,wewillstartwithabasicProtractortesttemplate:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
Whenwefillinthespecification,wegetthefollowing:
describe('WhenIlikeacomment',function(){
beforeEach(function(){
});
it('shouldthenbeliked',function(){
});
});
Withthetemplateinplace,wearereadytoconstructthetest.
AssembleTheassemblyofthistestwillrequireacommenttoexist.Placethecommentwithintheexistingpostedcommenttest.Itshouldlooksimilartothis:
describe(''GivenIampostinganewcomment',function(){
describe('WhenIlikeacomment',function(){
…
});
});
ActTheuserspecificationwetestisthatthelikebuttonperformsanactionforaspecificcomment.Herearethestepsthatwillberequiredandthecoderequiredtodothem(notethatthefollowingstepswillbeaddedtothebeforeEachtext):
1. Storethefirstcommentsothatitcanbeusedinthetest:
varfirstComment=null;
beforeEach(function(){
…
2. Findthefirstcomment’slikebutton:
varfirstComment=element.all(by.repeater('commentin
comments').first();
varlikeButton=firstComment.element(by.buttonText('like'));
3. Thecodeforthelikebuttonwhenitisclickedisasfollows:
likeButton.click();
www.it-ebooks.info
AssertThespecificationexpectationisthatoncethecommenthasbeenliked,itisliked.Thisisbestdonebyputtinganindicatorofthenumberoflikes,andensuringthecountis1.Thecodewillthenbeasfollows:
it('Shouldincreasethenumberoflikestoone',function(){
varcommentLikes=firstComment.element(by.binding('likes'));
expect(commentLikes.getText()).toBe(1);
});
Thecreatedtestnowlooksasfollows:
describe('WhenIlikeacomment',function(){
varfirstComment=null;
beforeEach(function(){
//Assemble
firstComment=element.all(by.repeater('commentincomments').first();
varlikeButton=firstComment.element(by.buttonText('like'));
//Act
likeButton.click();
});
//Assert
it('Shouldincreasethenumberoflikestoone',function(){
varcommentLikes=firstComment.element(by.binding('likes'));
expect(commentLikes.getText()).toBe(1);
});});
www.it-ebooks.info
MakeitrunThetesthasbeenpreparedandisitchingtorun.Wewillnowrunthetestandfixthecodeuntilthetestpasses.Thefollowingstepswilldetailtheerrorandthefixcyclerequiredtomakethetestpath:
1. RunProtractor.2. Viewtheerrormessageinthecommandline:
$Error:Noelementfoundusinglocator:by.buttonText("like")
3. Astheerrorstates,thereisnolikebutton.Goaheadandaddthebutton:
<ling-repeat='commentincomments'>
{{comment}}
<buttontype="button">like</button>
</li>
4. RunProtractor.5. Viewthenexterrormessage:
$Expected'acommentlike'tobe'acomment'.
6. Byaddingthelikebutton,wecausedourothertesttofail.ThereasonisouruseofthegetText()method.Protractor’sgetText()methodgetstheinnertextincludinginnerelements.Tofixthis,wewillneedtoupdatetheprevioustesttoincludelikeaspartofthetest:
it('Shouldthenaddthecomment',function(){
varcomments=element.all(by.repeater('commentincomments')).first();
expect(comments.getText()).toBe('acommentlike');
});
7. RunProtractor.8. Viewthenexterrormessage:
$Error:Noelementfoundusinglocator:by.binding("likes")
9. Timetoaddalikesbinding.Thisoneisalittlemoreinvolved.Likesneedstobeboundtoacomment.Weneedtochangethewaythecommentsareheldinthecontroller.Commentsneedtoholdthecommentvalueandthenumberoflikes.Acommentshouldbeanobjectlikethis:{value:'',likes:0}.Again,thefocusofthisstepisjusttogetthetesttopass.Thenextstepistoupdatethecontroller’saddfunctiontocreatecommentsbasedontheobjectwedescribedintheprecedingsteps.OpencommentController.jsandedittheaddfunctionasfollows:
$scope.add=function(commentToAdd){
varnewComment={value:commentToAdd,likes:0};
$scope.comments.unshift(newComment);
};
10. Updatethepagetousethevalueforthecomment:
www.it-ebooks.info
<ling-repeat='commentincomments'>
{{comment.value}}
11. BeforererunningtheProtractortest,weneedtoaddthenewcomment.likesbindingtotheHTMLpage:
<ling-repeat='commentincomments'>
…
{{comment.likes}}
12. NowreruntheProtractortestsandlet’sseewheretheerrorsare:
$Expected'acommentlike0'tobe'acommentlike'
13. Becausetheinnertextofthecommenthaschanged,weneedtochangetheexpectationofthetest:
it('Shouldthenaddthecomment',function(){
…
expect(comments.getText()).toBe('acommentlike0');
});
14. RunProtractor:
$Expected'0'tobe'1'.
15. Now,wearefinallydowntotheexpectationofthetest.Inordertomakethistestpass,thesmallestchangewillbetomakethelikebuttonupdatethelikesonthecommentarray.Thefirststepistoaddalikemethodonthecontroller,whichwillupdatethenumberoflikes:
$scope.like=function(comment){
comment.likes++;
};
16. LinkthelikemethodtotheHTMLpageusinganng-clickattributeonthebuttonasfollows:
<buttontype="button"ng-click='like(comment)'>like</button>
17. RunProtractorandconfirmthatthetestspass!
Thepagenowlooksasfollows:
Comparedtothedrawingatthebeginningofthischapter,allthefeatureshavebeencreated.NowthatwemadethetestpassinProtractor,weneedtochecktheunitteststo
www.it-ebooks.info
ensurethatourchangesdidn’tbreaktheunittests.
FixingtheunittestsOneoftheprimarychangesrequiredwastomakethecommentanobject,consistingofavalueandnumberoflikes.Beforethinkingtoomuchabouthowtheunittestscouldhavebeenaffected,let’skickthemoff.Executethefollowingcommand:
$karmastart
Asexpected,theerrorisrelatedtothenewcommentobject:
$Expected{value:'anycomment',likes:0}tobe'anycomment'.
Reviewingtheexpectation,itseemsliketheonlythingrequiredisforcomment.valuetobeusedintheexpectationasopposedtothecommentobjectitself.Changetheexpectationasfollows:
it('',function(){
varfirstComment=scope.comments[0];
expect(firstComment.value).toBe('anycomment');
})
SavethefileandchecktheKarmaoutput.Confirmthatthetestpasses.BoththeKarmaandProtractortestspassandwehavecompletedtheprimaryuserbehaviorsofaddingacommentandlikingit.Youarefreenowtomoveontothenextstepandmakethingsbetter.
www.it-ebooks.info
MakeitbetterAllinall,theapproachendedupwiththeresultwewanted.UsersarenowabletolikeacommentintheUIandseethenumberoflikes.Themajorcalloutfromarefactorstandpointisthatwehavenotunittestedthelikemethod.Reviewingourdevelopmentto-dolist,weseethattheto-dolistisanactionwewrotedown.Beforecompletelywrappingupthefeature,let’sdiscusstheoptionofaddingaunittestforthelikefunctionality.
CouplingofthetestAsalreadydiscussedinthisbook,testsaretightlycoupledtotheimplementation.Thisisagoodthingwhenthereisacomplicatedlogicinvolvedoryouneedtoensurethatcertainaspectsoftheapplicationbehaveincertainways.Itisimportanttobeawareofthecouplingandknowwhenitisimportanttobringitintotheapplicationandwhenitisnot.Thelikefunctionwecreatedsimplyincrementsacounteronanobject.Thiscanbeeasilytested;however,thecouplingwewillbringinwithaunittestwillnotgiveustheextravalue.Inthiscase,wewillnotaddanadditionalunittestforthelikemethod.Astheapplicationprogresses,wemayfindtheneedtoaddaunittestinordertodevelopandextendthefunction.HereareacoupleofthingsIconsiderwhenaddingatest:
Doesaddingatestoutweighthecostofmaintainingatest?Isthetestaddingvaluetothecode?
Doesithelpotherdevelopersbetterunderstandthecode?
Isthefunctionalitybeingtestedinsomeotherway?
Basedonourdecision,thereisnomorerefactoringortestingrequired.Inthenextsection,wewilltakeastepbackandreviewthemainpointsofthischapter.
www.it-ebooks.info
Self-testquestionsQ1.The$newfunctionisusedtocreateachildscope:$scope.$new.
1. True2. False
Q2.Giventhefollowingcodesegment,howwouldyouselecttheitemsinthelist?
<ul>
<ling-repeat="iteminmyItems">
{{item.value}}
</li
</ul>
1. element.all(by.repeater('iteminitems')).2. element.all(by.repeater('iteminmyItems')).3. element.all('iteminitems').
Q3.TheAngularmocksinjectfunctionisusedto:
1. Resolveapplicationdependencies/references.2. Injectdependenciesintotheapplication.3. Noneoftheabove.
www.it-ebooks.info
SummaryInthischapter,wewalkedthroughtheTDDtechniquesofusingProtractorandKarmatogether.Astheapplicationwasdeveloped,youwereabletoseewhere,why,andhowtoapplytheTDDtestingtoolsandtechniques.Theapproach,top-down,wasdifferentthanthebottom-upapproachdiscussedinChapter2,TheKarmaWayandChapter3,End-to-endTestingwithProtractor.Withthebottom-upapproach,thespecificationsareusedtobuildunittestsandthenbuildtheUIlayerontopofthat.Inthischapter,atop-downapproachwasshowntofocusontheuser’sbehavior.Thetop-downapproachteststheUIandthenfiltersthedevelopmentthroughtheotherlayers.Bothapproacheshavetheirmerit.WhenapplyingTDD,itisessentialtoknowhowtouseboth.InadditiontowalkingthroughadifferentTDDapproach,yousawsomeofthecoretestingcomponentsofAngularJSsuchas:
Testingacontrollerfromend-to-endandunitperspectivesUsingAngularmockstotestthescopeobjectofacontrollerProtractor’sabilityto:
Bindtong-repeaterandng-modelSendkeystrokestoinputcolumnsGetanelement’stextbyitsinnerHTMLcodeandallsubelements
Thenextchapterwillbuildonthetechniquesusedhereandlookintoheadlessbrowsertesting,advancedtechniquesforProtractor,andhowtotestAngularJSroutes.
www.it-ebooks.info
Chapter5.FlipFlopAtthispoint,youshouldbefeelingconfidentintheinitialimplementationofanAngularJSapplicationusingTDD.Youshouldbefamiliarwithusingatest-firstapproach.Inthischapter,youwillcontinuetoexpandyourknowledgeofapplyingTDDwithAngularJSbylookingatthefollowing:
AngularJSroutesPartialviewsProtractorlocationreferenceswithCSS(CascadingStyleSheets)andHTMLelementsHeadlessbrowsertestingwithKarma
www.it-ebooks.info
FundamentalsThischapterwillwalkyouthroughapplyingTDDtoroutesandpartialviewsforasearchapplication.Beforegettingintothewalk-through,youneedtobeawareofsomeofthetechniques,configurations,andfunctionsthatwillbeusedthroughoutthischapter,whichinclude:
ProtractorlocatorsHeadlessbrowsertesting
Afteryouhavereviewedtheseconcepts,youcanmoveontothewalk-through.
www.it-ebooks.info
ProtractorlocatorsProtractorlocatorsarekeycomponentsthatyoumusttaketimetolearn.Thisbookwillnotbeabletoshowexamplesofallthedifferentlocators,butitwillprovideexamplesofthemostcommonones.
ProtractorlocatorsallowyoutofindelementswithinanHTMLpage.Inthischapter,youwillseethefollowinginaction:CSS,HTML,andAngularJS-specificlocators.Locatorsarepassedtotheelementfunction.Theelementfunctionwillfindandreturnelementsinapage.Thegenericlocatorsyntaxisasfollows:
element(by.<LOCATOR>);
Intheprecedingcode,<LOCATOR>isaplaceholder.Thefollowingsectionsdescribeacoupleoftheselocators.
CSSlocatorsCSSisusedtoaddlayout,color,formatting,andstyletoanHTMLpage.Fromanend-to-endtestingperspective,thelookandstyleofanelementmaybepartofaspecification.AsanexampleconsiderthefollowingHTMLsnippet:
<divclass="anyClass"id="anyId"></div>
//...
vare1=element(by.css('.anyClass'));
vare2=element(by.css('#anyId'));
vare3=element(by.css('div'));
vare4=$('div');
Allfourselectionswillselectthedivelement.
ButtonandlinklocatorsBesidesbeingabletoselectandinterpretthewaysomethinglooks,itisalsoimportanttobeabletofindbuttonsandlinkswithinapage.Thiswillallowatesttointeractwiththesiteeasily.Hereareacoupleofexamples:
Buttontextlocator:
<button>anyButton</button>
//...
varb1=element(by.buttonText('anyButton'));
Linktextlocator:
<ahref="#">anyLink</a>
//...
vara1=element(by.linkText('anyLink'));
AngularlocatorsOneofProtractor’skeystrengthsisthatitprovidestestingfunctionalityspecifictoAngularJS.Therepeaterlocatorwillselecttheelementswithintheapplicationwhereng-
www.it-ebooks.info
repeatwasused.Thisisespeciallyusefulwhenlookingatthenumberofreturnedresultsandthevaluesofindividualresults.Onekeytousingthislocatoristhatthestringoftherepeaterlocatormustmatchtheng-repeatstringusedintheAngularJSapplication.Hereisanexampleofusingtherepeaterlocator:
//TheListintheapplicationtouseng-repeaton
<ling-repeat="iteminlist">
<div>
<ahref="#">link</a>
</div>
</li>
//...
varfirstItem=element.all(by.repeater('iteminlist')).first();
Theprecedingcodehighlightshowtofindthefirstelementinarepeater.Itshouldbeclearthatinthiscase,theelement.allfunctionfindsalltheelementsmatchingtheselector.Then,thefirst()methodisusedtoreturnthefirstelementfound.
URLlocationreferencesWhentestingAngularJSroutes,youneedtobeabletotesttheURLofyourtest.ByaddingtestsaroundtheURLandlocation,youensurethattheapplicationfollowsspecificroutes.Thisisimportantbecauseroutesprovideaninterfaceintoyourapplication.HereishowtogettheURLreferenceinaProtractortest:
varlocation=browser.getLocationAbsUrl();
Nowthatyouhaveseenhowtousethedifferentlocatorsitistimetoputtheknowledgetouse.
www.it-ebooks.info
CreatinganewprojectItisimportanttogetaprocessandmethodtosetupyourprojectsquickly.Thelesstimeyou’rethinkingofthestructureofthedirectoryandtherequiredtools,themoretimeyou’redeveloping!
Somepeopleusetheangular-seed(https://github.com/angular/angular-seed)project,Yeoman,orcreateacustomtemplate.Althoughthesetechniquesareusefulandhavetheirmerit,whenstartingoutinAngularJS,itisessentialtounderstandwhatittakestobuildanapplicationfromthegroundup.Bybuildingthedirectorystructureandinstallingtoolsyourself,youwillunderstandAngularJSbetter.Youwillbeabletomakelayoutdecisionsbasedonyourspecificapplicationandneeds,asopposedtofittingintosomeothermold.AsyougrowandbecomeabetterAngularJSdeveloper,thisstepmaynotbeneededandwillbecomesecondnaturetoyou.
Inpreviouschapters,wediscussedhowtogettheprojectsetup,explainedthedifferentcomponentsinvolved,andwalkedthroughtheentireprocess.Iwillskipthesedetailsandexpectthatyoucanrecallhowtoperformthenecessaryinstallation.Toconfirmtheinstallation,hereisascreenshotoftheexpectedoutput:
www.it-ebooks.info
SettingupheadlessbrowsertestingforKarmaInpreviouschapters,youwererunningKarmausingthedefaultconfiguration.ThedefaultChromeconfigurationlaunchesChromeoneverytest.Testingagainsttheactualcodeandbrowser,whichtheapplicationwillrunin,isapowerfultool.However,whenlaunching,abrowsermaynotbehowyoualwayswantedit.Fromaunittestperspective,youmaynotwantthebrowsertobelaunchedinawindow.Someofthereasonsaretestsmaytakealongtimetorunoryoumaynotalwayshaveabrowserinstalled.
Luckily,KarmacomesequippedwiththeabilitytoeasilyconfigurePhantomJS,aheadlessbrowser.AheadlessbrowserrunsinthebackgroundandwillnotdisplaywebpagesinaUI.ThePhantomJSheadlessbrowserisareallygreattooltousefortesting.Itcanevenbesetuptotakescreenshotsofyourtests!ReadmoreabouthowthisisdoneandtheWebKitusedonthePhantomJSsiteathttp://phantomjs.org/.ThesucceedingsetupconfigurationwillshowyouhowtosetupPhantomJSwithKarmatogetheadlessbrowsertesting.
PreconfigurationWhenKarmaisinstalled,itautomaticallyincludesthePhantomJSbrowserplugin.Foryourreference,thepluginislocatedathttps://github.com/karma-runner/karma-phantomjs-launcher.Thereshouldn’tbeanyadditionalinstallationorconfigurationrequired.However,ifyoursetupstatesthatitismissingkarma-phantomjs-launcher,youcaneasilyinstallitusingnpm:
$npminstallkarma-phantomjs-launcher
ConfigurationPhantomJSisconfiguredinthebrowsersectionoftheKarmaconfiguration.Openthekarma.conffileandupdateitwiththefollowingdetails:
browsers:['PhantomJS'],
Nowthattheprojecthasbeeninitializedandconfiguredwithheadlessbrowsertesting,youcanseeitinactionthroughthefollowingwalk-throughs.
www.it-ebooks.info
Walk-throughofAngularroutesThiswalk-throughwillleverageAngularJSroutes.RoutesareanextremelyusefulfeatureofAngularJS.Theyallowyoutocontrolcertainaspectsoftheapplicationusingdifferentviews.Thiswalk-throughwillflipbetweenviewstoshowyouhowtouseTDDtobuildroutes.Thefollowingarethespecifications:
GivenaviewAthathasasinglebutton;thefollowingactionswilltakeplace:
ThebuttonispushedTheviewisswitchedtoviewB
GivenaviewBthathasasinglebutton;thefollowingactionswilltakeplace:
ThebuttonispushedTheviewisswitchedtoviewA
Essentially,thiswillbeanapplicationthatdoesaflipflopbetweenviews.
www.it-ebooks.info
SettingupAngularJSroutesBeforeyouuseAngularJSroutes,youneedtoinstalltheAngularJSroutecomponent.YoucaninstallAngularJSroutesusingbowerasfollows:
$bowerinstallangular-route
AngularroutesrequiresAngular,asyoucanimagine.InordertouseitanHTMLpagewouldlookasfollows:
<!DOCTYPEhtml>
<html>
<head>
<title></title>
</head>
<body>
<scriptsrc="bower_components/angular/angular.js">
</script>
<script
src="bower_components/angular-route/angular-route.js"></script>
</body>
</html>
DefiningdirectionsAroutespecifiesaspecificlocationandexpectsaresult.FromanAngularJSperspective,theroutesmustfirstbespecifiedandthenassociatedtocertainelementswithinthem.
ConfiguringngRoute
InordertouseAngularJSroutes,wefirstneedtobringngRouteinasadependencyintotheapplication.Inapp/flipFlop.js,modifythecodetobringinngRouteasadependencyandreturnthemodule:
varflipFlop=angular.module('flipFlop',['ngRoute']);
Now,thesecondthingrequiredisweneedtoconfiguretheroutesthatweneed.Inourcase,weneedtworoutes:oneforviewAandoneforviewB.Therouteconfigurationwillthenlookasfollows:
flipFlop.config(['$routeProvider',function($routeProvider){
$routeProvider
.when('/view/a',{
templateUrl:'app/viewA.html',
controller:'ViewAController'
})
.when('/view/b',{
templateUrl:'app/viewB.html',
controller:'ViewBController'
})
.otherwise({
redirectTo:'/view/a'
});
www.it-ebooks.info
}]);
Arouteisdefinedusingwhen,whichhasafirstargumentasastringforthefullroute.Thesecondargumentisanobject,whichtakestheHTMLpagefortheroute(templateURL)andthecontrollerfortheroute(controller).
Definingtheroutecontrollers
Forbothroutes,createanemptycontrollersothatitcanbeaplaceholderforthefuturecontroller.Herearethestepsyouneedtofollowtodefineroutecontrollers:
1. CreateanewfilefortheViewAcontroller(/app/ViewAController.js):
angular.module('flipFlop')
.controller('ViewAController',['$scope',function($scope){
}]);
2. CreateanothernewfilefortheViewBcontroller(/app/ViewBController.js):
angular.module('flipFlop')
.controller('ViewBController',['$scope',function($scope){
}]);
3. Addthetwocontrollerstotheindex.htmlpage:
<scriptsrc="app/viewAController.js"></script>
<scriptsrc="app/viewBController.js"></script>
Definingtherouteviews
RouteviewsarepartialHTMLelementsthatcanbedynamicallyplacedintoanapplication.Forthetwoviewswerequire,wewillputabasicdivtagforeachview,asshowninthefollowingsteps:
1. Createanewfileforapp/viewA.html:
<divid="viewA"></div>
2. Createanewfileforapp/viewB.html:
<divid="viewB"></div>
Thelastthingrequiredistoputaplaceholderwheretherouteviewwillbeplacedintheindex.htmlpage:
<divng-view></div>
Now,theroutesaresetupwiththeinitialviewsandcontrollers.WecancontinuewiththeProtractortest.
AssemblingtheflipfloptestFollowingthefirstofthe3A’s,Assemble,thefollowingstepswillshowyouhowtoassemblethetest.
www.it-ebooks.info
1. StartwiththeProtractorbasetemplate:
describe('GivenaviewAthathasasinglebutton',function(){
describe('Whenthebuttonispushed',function(){
beforeEach(function(){
})
it(''shouldbeswitchedtoviewB'',function(){
})
})
})
2. Navigatetotherootoftheapplicationusingthefollowingcode:
browser.get('/index.html');
3. ThebeforeEachmethodneedstoconfirmthatthecorrectviewisbeingdisplayed.ThiscanbedoneusingaCSSlocatortolookforthedivtagofviewA.Theexpectationwilllookasfollows:
varviewA=element(by.css('#viewA'));
expect(viewA.isPresent()).toBeTruthy();
4. Then,addanexpectationthatviewBisnotvisible:
varviewB=element(by.css('#viewB'));
expect(viewB.isPresent()).toBeFalsy();
YouwillnoticehowtheselectionofviewAandviewBisdoneoutsideofthebeforeEachmethod,soitcanbeusedforotherexpectations.
Makingtheviewsflip
Theprecedingtestneedstoconfirmthatwhentheflipbuttonispushed,theviewshouldswitch.Inordertotestthis,youcanusetheby.buttonTextlocator.Hereiswhatitwilllooklike:
varbuttonToPush=element(by.linkText('flip'));
buttonToPush.click();
ThebeforeEachfunctionisnowcompleteandlooksasfollows:
varviewA=element(by.css('#viewA'));
varviewB=element(by.css('#viewB'));
beforeEach(function(){
browser.get('/index.htm');
expect(viewA.isPresent()).toBeTruthy();
varbuttonToPush=element(by.linkText('flip'));
buttonToPush.click();
})
Now,youcanaddtheassertion.
Assertingaflip
TheassertionwillagainuseProtractor’sCSSlocatortofindthatviewBisavailable:
www.it-ebooks.info
it('shouldbeswitchedtoviewB',function(){
expect(viewB.isPresent()).toBeTruthy();
})
YoualsoneedtoconfirmthatviewAisnolongeravailable.AddtheexpectationthatviewAshouldnotexist:
it('shouldnotdisplayviewA',function(){
expect(viewA.isPresent()).toBeFalsy();
})
Thetesthasnowbeenassembled.
MakingflipfloprunNow,youwillseethestepsrequiredtomaketheflipfloprun:
1. Inanewconsolewindow,starthttp-server:
$./node_modules/http-server/bin/http-server-p8080
2. RunProtractor:
$./node_modules/protractor/bin/protractorprotractorConf.js
3. ThefirsterrorstatesError:Angularcouldnotbefoundonthepagehttp://localhost:8080/:angularneverprovidedresumeBootstrap.Whenyougetthiserror,proceedwiththefollowingsteps:
1. ThiserrormeansthatnoAngularJSapplicationhasbeenassociatedwiththeapplication.It’snowtimetocreatetheapplicationmoduleandaddittothepage.
2. Createanewfilenamed/app/flipFlop.js:
angular.module('flipFlop',[]);
3. Addthenewmoduletotheindex.htmlpage:
<scriptsrc="app/flipFlop.js"></script>
4. AddtheAngularJSapplicationidentifiertothepage:
<bodyng-app='flipFlop'>
5. ReruntheProtractortest.
4. TheerrorisError:Noelementfoundusinglocator:by.linkText("flip").Torectifythisperformthefollowingsteps:
1. Openuptheapp/viewA.htmlfileandaddalinktotheViewBroutewiththefliptext:
<divid="viewA">
<ahref="#/view/b">flip</button>
</div>
www.it-ebooks.info
2. Rerunthetest.
5. TheProtractortestsnowpass.
MakingflipflopbetterForpractice,youshouldaddalinktoswitchbacktoviewAfromviewB.Thereisnothingthathasbeencalledoutthatneedstobechangedorrefactored.Themaintakeawayfromthiswalk-throughishowtouseProtractortotestroutes.Herearesomescreenshotsoftheapplication:
Theinitialindexpageisshowninthefollowingscreenshot:
Thefollowingiswhatyou’llseeaftertheviewhasbeenswitched:
www.it-ebooks.info
SearchingtheTDDwayThiswalk-throughwillshowyouhowtobuildasimplesearchapplication.Thewalk-throughhastwocomponents.Thefirstdiscussesasearchquerycomponent.Thesecondusesroutestodisplaysearchresultdetails.
www.it-ebooks.info
DecidingontheapproachThiswalk-throughusesthetop-downTDDapproach.Itstartswithwritingfailingtests,fromtheUIpointofviewusingProtractor,andthenworkingthroughtheapplicationwithacombinationofunitandend-to-endtests.
www.it-ebooks.info
Walk-throughofsearchqueryTheapplicationbeingbuiltisasearchapplication.Thefirststepistosetupthesearchareawithsearchresults.ImagineIamperformingasearch.Thefollowingactionswilloccur:
AsearchqueryistypedinResultsaredisplayedontheleftsidebar
Thispieceoftheapplicationisverysimilartothetest,layout,andapproachyousawinChapter4,FirstSteps.Theapplicationwillneedtouseaninput,respondtoaclick,andconfirmtheresultingdata.Sincethetestsandcodeusethesamefunctionalityasthepreviousexample,itisnotworthprovidingacompletewalk-throughofthesearchfunctionality.Instead,thefollowingsectionwillshowtheresultingcodewithafewexplanations.
www.it-ebooks.info
ThesearchquerytestThefollowingcoderepresentsthetestforthesearchqueryfunctionality:
describe('',function(){
//StorethesearchResultforuseinthetest
varsearchResult=null;
beforeEach(function(){
//ASSEMBLE
browser.get('/index.html');
varsearchResult=element.all(by.repeater('resultinresults'));
expect(searchResult.count()).toBe(0);
//ACT
varsearchQueryInput=$('input');
searchQueryInput.sendKeys('anyvalue');
varsearchButton=element(by.buttonText('search'));
searchButton.click();
});
//Assert
it('',function(){
expect(searchResult.count()).toBe(1);
});
});
Youshouldnoticeaparalleltoprevioustests.Thefunctionalityiswrittentomirrorthebehaviorofausertypinginthesearchbox.Thetestfindstheinputfield,typesavalue,andthenselectsthebuttonthatsaysSearch.Theassertionconfirmsthattheresultcontainsasinglevalue.ThenextsectionwilllookattheapplicationfromtheHTMLpage.
www.it-ebooks.info
ThesearchqueryHTMLpageThefollowingcodeshowstheresultingbodyofthesearchqueryHTMLpage:
<bodyng-app="search">
<divng-controller="SearchController">
<inputtype="text"ng-model="searchQuery"></input>
<buttonng-click="search(searchQuery)">search</button>
<ul>
<ling-repeat="resultinresults">{{result}}</li>
</ul>
</div>
<scriptsrc="bower_components/angular/angular.js"></script>
<scriptsrc="app/search.js"></script>
<scriptsrc="app/searchController.js"></script>
</body>
ThemainhighlightsoftheHTMLpageare:
TheuseofthesearchControllerclass’modeltostorethesearchQueryclassintheinput:
<inputtype="text"ng-model="searchQuery"></input>
AssociatingthebuttonclickeventtothesearchController'ssearchfunction:
<buttonng-click="search(searchQuery)">search</button>
ThenextsectionwillshowtheresultingsearchmoduleandsearchController.
www.it-ebooks.info
ThesearchapplicationHereistheresultofthesearchModulecode:
varsearchModule=angular.module('search',[]);
HereistheresultofthesearchControllercode:
angular.module('search')
.controller('SearchController',['$scope',function($scope){
$scope.results=[];
$scope.search=function(){
$scope.results=['AnyValue'];
};
}]);
TheprecedingAngularJScomponentsaresimilartowhathasalreadybeenshowninpreviouschapters.Nowthatyouhavereviewedtheexistingsearchpieceoftheapplication,youcanwalkthroughthestepstodisplaysearchresultdetailviews.Hereiswhatthesearchapplicationlookslikesofar:
www.it-ebooks.info
Showmesomeresults!NowthattheSearchbuttonissetwiththerequiredfeatures,theresultingdetailsneedtobedisplayedwhenasearchresultisselected.Hereistheuserspecification.Giventhefollowingsearchresults:
IselectanitemfromthesearchresultsIwillseethedetailsinthemainpagecomponent
Followingthetop-downapproach,thefirststepwillbetheProtractortestsfollowedbythenecessarystepstogettheapplicationfullyfunctional.
www.it-ebooks.info
CreatingthesearchresultroutesThisapplicationwilluseroutestoswitchbetweenviews.Asthisstepisprimarilyaboutconfiguration,itdoesn’tmakesensetowaituntilatestfails.Thefollowingstepswillbrieflyrecapthenecessarysteps,asyouhavealreadywalkedthroughthestepswiththeflipflopapplication:
1. Installangular-routesusingBower:
$bowerinstallangular-route
2. Addangularandangular-routetotheindex.htmlpage:
<scriptsrc="bower_components/angular/angular.js"></script>
<scriptsrc="bower_components/angular-route/angular-route.js"></script>
3. CreateangRoutemoduleasadependencyintheapplication(app/search.js):
varsearchModule=angular.module('search',['ngRoute']);
4. Configuretheroutesintheapp/search.jsfile.Addthefollowingrouteconfiguration:
searchModule.config(['$routeProvider',function($routeProvider){
$routeProvider
.when('/splash',{
templateUrl:'app/splash.html',
controller:'SplashController'
})
.when('/detail/:id',{
templateUrl:'app/searchDetail.html',
controller:'SearchDetailController'
})
.otherwise({
redirectTo:'/splash'
});
}]);
Theprecedingconfigurationcontainstworoutes.Oneforasplashscreen/landingpagethatwillbedisplayedwhentheuserfirstcomestothepage.Thesecondistheroutetogetthesearchdetails.
5. Addtheroutestubcontrollers:
1. CreateanewfileforSplashController(app/splashController.js):
angular.module('search')
.controller('SplashController',['$scope',function($scope){
}]);
2. CreateanewfileforSearchDetailController(app/searchDetailController.js):
angular.module('search')
.controller('SearchDetailController',['$scope',function($scope){
www.it-ebooks.info
}]);
6. Addthedetailcontrollertotheindex.htmlpage:
<scriptsrc="app/searchDetailController.js"></script>
7. CreatethepartialviewHTMLfilesbyfollowingthesesteps:
1. Createanewfileforsplash.html:
<divid="splash"></div>
2. CreateanewfileforsearchDetail.html:
<divid="searchResultDetail"></div>
Theroutesforthetesthavenowbeencreated.Youcancontinuetothenextstepandbeginaddingthefunctionalitytolinksearchresultstotheresultdetails.
www.it-ebooks.info
TestingthesearchresultsAsthespecificationstates,youwillneedtoleveragetheexistingsearchresults.Insteadofcreatingatestfromscratch,youcanaddtotheexistingsearchquerytest.Startwithabasetestembeddedinthesearchquerytestasfollows:
describe('GivenIamsearching',function(){
describe(''whenItypeinasearchquery'',function(){
...
describe('Givensearchresults',function(){
describe('WhenIselectanitemfromthesearchresults',function(){
beforeEach(function(){
});
it('shouldseethedetailsinthemainpagecomponent',function(){
});
});
});
})
})
Nowmoveontothenextstepandbuildthetest.
AssemblingthesearchresulttestInthiscase,thesearchresultsarealreadyavailablefromthesearchquerytest.Youdon’thavetoaddanymoresetupstepforthetest.
SelectingasearchresultTheobjectundertestistheresult.Thetestiswhentheresultisselectedandthentheapplicationmustdosomething.ThestepstowritethisinProtractorareasfollows:
1. Findaresultitemusingthefollowingcode:
varresultItem=element(by.repeater('resultinresults')).first();
2. Selecttheresultitem.Asyouwillberepresentingthedetailsusingaroute,youwillcreatealinktothedetailspageandclickonthelink.Herearethestepstocreatealink:
1. Selectthelinkwithintheresultitem.Thisusestheelementcurrentlyselectedandthenfindsanysubelementsthatmeetthecriteria.Thecodeforthisisasfollows:
varresultLink=resultItem.element(by.css('a'));
2. Nowtoselectthelinkaddthefollowingcode:
resultLink.click();
Confirmingasearchresult
www.it-ebooks.info
Nowthatthesearchitemhasbeenselected,youwillneedtoverifythattheresultdetailspageisvisible.Thesimplestsolutionatthispointistoensurethatthedetailsviewisvisible.ThiscanbedoneusingProtractor’sCSSlocatortolookforthesearchdetailview.Thefollowingisthecodetobeaddedforconfirmingasearchresult:
it('Shouldseethedetailsinthemainpagecomponent',function(){
varresultDetail=element(by.css('#searchResultDetail'))
expect(resultDetail.isDisplayed()).toBeTruthy();
})
Hereisthecompletetest:
...
describe('WhenIselectanitemfromthesearchresults',function(){
beforeEach(function(){
varresultItem=element.all(by.repeater('resultinresults')).first();
varresultLink=resultItem.element(by.css('a'));
resultLink.click();
});
it('Shouldseethedetailsinthemainpagecomponent',function(){
varresultDetail=element(by.css('#searchResultDetail'))
expect(resultDetail.isDisplayed()).toBeTruthy();
});
});
Nowthatthetestissetup,youcancontinuetothenextphaseofthelifecycleandmakeitrun.
www.it-ebooks.info
MakingthesearchresulttestrunForthisstepofthelifecycle,wewillexecuteProtractorandmakefixesintheapplicationinordertomakethetestrunsuccessfully.Herearethestepsyouneedtofollow:
1. Thefirsterror:Error:Noelementfoundusinglocator:by.cssSelector('a')
Weneedtoaddalinktotheresultitemlist,whichwillpointtothedetailsoftheresult.IntermsofAngularroutes,wewilladd#/detail/:resultIdasaprefix:
<ling-repeat="resultinresults"><ahref="#/detail/{{result.id}}">
{{result.name}}</a></li>
2. NowrerunthetestandwegetUnknownError:unknownerror:Elementisnotclickableatpoint(48,57).Otherelementwouldreceivetheclick:....
Thiserrorisnotasclear.Whenthishappens,andtheerrorisnotasspecificasrequired,youcanjumptothesiteitselfandlookattheJavaScriptconsoleforerrors.Gotohttp://localhost:8080.Hereisascreenshotofwhatyoushouldsee:
Themainproblemisthatthelinkisnotonthepage.Lookingbackatthecode,youcanseethatthesearchresultobjectisanarrayofstringsbutitneedstobeanarrayofobjectsthathaveanIDandname.Updatetheapp/searchController.jssearchfunctionasfollows:
$scope.search=function(){
$scope.results=[{id:1,name:'AnyValue'}];
};
Nowrerunthetest.
3. Therouteshavenowbeenconfiguredtothenewroute(#/detail/{{result.id}})andthetestsnowpass.
www.it-ebooks.info
Creatingalocation-awaretestAstheapplicationusesroutes,theroutedetailviewwillneedtobetested.Inthiscase,youwillneedtoensuretheURLhastheIDofthesearchresult.Followthesestepstoaddthetest:
1. InthebeforeEachmethod,retrievetheIDofthesearchresultbasedonhrefofthelinkattribute:
varresultId=null;
beforeEach(function(){
…
resultId=resultLink.getAttribute('href').then(function(attr){
returnattr.match(/#\/detail\/(\d+)/)[1];
});
});
2. ResolvetheresultIdpromisecontainingtheIDoftheresult:
it('Shouldsettheurltotheselecteddetailview',function(){
resultId.then(function(id){
3. Withinthepromise,createexpectedUrl:
varexpectedUrl='/detail/'+id;
4. GetthelocationoftheURL:
browser.getLocationAbsUrl()
5. UsethepromisetochecktheexpectationontheURL:
.then(function(url){
expect(url.split('#')[1]).toBe(expectedUrl);
});
});
Location-awaretestscanbeveryhelpfulwhendealingwithroutes.Thetestscanbesimpleorcomplex,buthelpaligntherouteinterfacetoclearspecifications.
www.it-ebooks.info
MakingthesearchresultbetterNowthatthereisapassingtest,somecleanupandrefactoringisneeded.Therearetwoprimarycallouts:
Nounittests.HowdoyouknowsearchResultDetailisspecifictothesearchresultweselect?
Uptothispoint,therehasn’tbeenaneedtocreateunitteststobuildtheapplication.ThefocushasbeenontheUIintheapplication.Therehasn’tbeenlogicoractionsneededtobuildonthebackend.Mostofthedevelopmenthasbeenfocusedonwiringupthefrontendandmakingsurethecomponentsinthespecificationareavailabletotheuser.
Theotheractionthatyouneedtolookatisthefactthatthereisnotawaytotestthataloadedviewactuallyreflectsdatafromtheselectedresult.Thiscanbetackledintwoparts.ThefirstpartistoensurethattheURLforthewindowpointstothecorrectroute.ThesecondpartwillbetodisplaytheIDnumberofthesearchresultontheview.
ConfirmingtherouteIDTheIDwillnotbedisplayedtotheusers;however,itisstillanintegralpartoftheapplication.Astheapplicationgrowsinthefollowingchapters,youwillbeleveragingtheIDtoextractfurtherdata.Thiswalk-throughwillfollowtheTDDlifecycleanduseKarmatobuildthefeature.
SettinguptherouteIDunittest
Toinjectthescopeintoacontroller,theinitialtestwilllookasfollows:
describe('',function(){
varscope={};
beforeEach(function(){
module('search');
inject(function($controller){
$controller('SearchController',{$scope:scope});
});
});
it('',function(){});
});
Inordertotesttheroutes,thetestwillleveragethe$routeParamsobject.The$routeParamsobjectgivesanobjectaccesstoinformationrelatingtotheroutethatbroughttheapplicationtothelocation.Forexample,the/detail/:idroutedefinitionandthe/detail/123,$routeParamsroutewillgiveyouthe{id:123}object.Forthetest,afake$routeParamsobjectcontainingtheIDofthedetailobjectwillbeused.Updatethetestsothatithasthefollowingfake$routeParamsobject,whichwillreturnanIDof1:
beforeEach(function(){
//...
varrouteParams={id:1};
$controller('SearchDetailController',{$scope:scope,$routeParams:
routeParams});
www.it-ebooks.info
Nowthatthefake$routeParamsobjecthasbeeninjectedintothecontroller,youcancontinuetothenextphaseandmaketheassertion.
ConfirmingtheID
TheassertionisthatthescopehasadetailobjectwiththesameIDthat$routeParamsspecified.ThecodeforconfirmingtheIDisasfollow:
it('Shouldreturnresults',function(){
expect(scope.detail.id).toBe(1);
});
Makingtherouteparameter’stestrun
NowthatKarmaisrunningusingaheadlessbrowser,wecanstartKarmaintheconsoleandletitrunaswewalkthroughtheissues,asshowninthefollowingsteps:
1. StartKarma:
$karmastart
2. ThefirstissuewegetisthatngRoutecan’tbefound.Thisisbecauseweaddedangular-routetotheproject,buthaven’taddedittokarma.conf.Updatethekarma.confupdatethefilessectionwiththefollowingcode:
files:[
//...
'bower_components/angular-route/angular-route.js',
3. Afterrerunningthetest,weareleftwithTypeError:''undefined''isnotanobject(evaluatingscope.detail.id).Torectifythis,performthefollowingsteps:
1. Thiserrorinformsusthatthescope.detail.idobjectdoesn’texistinthecontroller.Wewillnowupdatethecontrollertoincludeit.Thefirststeptofixingthisistoadd$routeParamstosearchDetailController:
.controller('SearchDetailController',
['$scope','$routeParams',function($scope,$routeParams){
2. Now,inthecontroller,createthedetailobjectwiththe$routeParamsID:
$scope.detail={id:$routeParams.id};
3. ThedetailobjecthasnowbeencreatedusingtheIDoftheroute.Goaheadandrerunthetest.
Thetestpasses!
Theapplicationnowlookslikewhatisshowninthefollowingscreenshotwhenyoufirstopenit:
www.it-ebooks.info
Afterasearchquery,theapplicationlookslikewhatisshowninthefollowingscreenshot:
Fordetailsoftheapplicationlooksasshowninthefollowingscreenshot(noticethattheURLcontainsthedetailroute):
www.it-ebooks.info
Self-testquestionsQ1.GiventhefollowingHTMLcode,howwouldyouselectthesecondlistitem?
<ul>
<li>item1</li>
<li>item2</li>
</ul>
1. element.all(by.css('li')).second();.2. element(by.repeater('iteminlist'))[1];.3. element.all(by.css('li')).get(1);.
Q2.GiventhefollowingAngularJScomponent,howwouldyouselecttheelementandsimulateaclick?
<ahref="#">SomeLink</a>
1. $('a').click();.2. element(by.css('li)).click();.3. element(by.linkText('SomeLink')).click();.
Q3.WhenusingrouteswithAngularJSyouneedtoinstallangular-route.
1. True.2. False.
www.it-ebooks.info
SummaryThischapterhasshownyouhowtouseTDDtobuildanAngularJSapplication.Theapproach,uptothispoint,hasfocusedonthespecificationfromauserperspectiveandusingTDDfromtop-downapproach.Thistechniquehelpsyougetusable,smallcomponentstestedandcompletedfortheusers.Asapplicationsgrow,sodoestheircomplexity.Aswemoveontothenextchapter,wewillexplorethebottom-upapproachandseewhentousethattechniqueoveratop-downapproach.
ThischapterhasshownyouhowTDDcanbeusedtodeveloproute-basedviews.Thisincludesutilizingmultiplecontrollersandviews.Routesallowyoutogetaniceseparationofyourcomponentsandviews.WehaveshowntheusageofseveralProtractorlocators,fromCSS,torepeaters,tolinktext,toinnerlocators.BesidesusingProtractor,wehavealsolearnedhowtoconfigureKarmawithaheadlessbrowser,andwegottoseeitinaction.
www.it-ebooks.info
Chapter6.TellingtheWorldThebuildupofTDDfocusedonfundamentalcomponents,namelylifecycleandprocess,usingstep-by-stepwalk-throughs.Youhavetakenseveralapplicationsfromthegroundup,understandinghowtobuildAngularJSapplicationsandusetoolstotestthem.ItistimetoexpandfurtherintothedepthsofAngularJSandintegrateservices,broadcasting,androutes.
Thischapterwillbeslightlydifferentthantheothersintwoways:
Insteadofbuildingabrandnewapplication,wewillusethesearchapplicationfromChapter5,FlipFlop.Also,abottom-upapproachwillbeused.ThisconsistsofcreatingunittestsfirstandthenmovingtotheUI.
www.it-ebooks.info
BeforetheplungeBeforethewalk-through,thecoreconceptsofthechapterwillbereviewedfirst.Itisimportantthatyouunderstandtheseconceptsbeforeyoumoveontothewalk-through.
www.it-ebooks.info
KarmaconfigurationSofar,thedefaultKarmaconfigurationhasbeenused,butnoexplanationonthedefaultconfigurationhasbeengivenyet.Filewatchingisausefuldefaultbehaviorthatwillnowbereviewed.
FilewatchingFilewatchingisenabledbydefaultwhenthekarmainitcommandisused.FilewatchinginKarmaisconfiguredwiththefollowingdefinitioninthekarma.conf.jsfile:
autoWatch:true,
Thefilewatchingfeatureworksasexpectedandwatchesthefilesdefinedintheconfiguration’sfilesarray.Whenafileisupdated,changed,ordeleted,Karmawillrespondbyrerunningthetests.FromaTDDperspective,thisisagreatfeatureastestswillcontinuetorunwithoutanymanualintervention.
Themainpointtowatchoutforistheadditionoffiles.Ifthefilebeingaddeddoesn’tmatchthecriteriainthefilesarray,theautoWatchparameterwon’trespondtothechange.Asanexample,let’sconsiderthatthefilesaredefinedasfollows:
files:['dir1/**/*.js']
Ifthisisthecase,thewatcherwillfindallthefilesandsubdirectoryfilesendingin.js.Ifanewfileisinadifferentdirectory,notindir1,thenthewatcherwillnotbeabletorespondtothenewfilebecauseitisinadifferentdirectorythanwhatitwasconfiguredin.
www.it-ebooks.info
Usingabottom-upapproachThetop-downapproachofTDDcanbeveryuseful.Ithelpsfocusonuser-facingcomponentsfirstandthenfillsupthebackendlayer.Oneofthecaveatstothisapproachisthatthespecificationbeingbuiltismoreuserfacingasopposedtoitbeingbasedonlogic.Thebottom-upapproachbuildsfromtheinnercomponentsouttotheUIandtheuser.Thiskindofapproachisextremelyimportantwhenworkingwithcomplicatedlogicandrequirements.Withthebottom-upapproach,youwillfirstbuildservices,controllers,anddirectiveswithallthecomplexitiesusingunittestsandKarma.Afterthis,youwillexpandtocreateend-to-endtestswithProtractor.
www.it-ebooks.info
ServicesAngularJSservices,factories,andresourcesareallimportantcomponents.Servicesareusedtoabstractapplicationlogic.Theyareusedtoprovidesingleresponsibilityforaparticularaction.Singleresponsibilityallowscomponentstobeeasilytestedandchanged.Thisisbecausethefocusisononecomponentandnotalltheinnerdependencies.
HereisasummaryofsomeoftheotherAngularJScomponentsthathavebeenlookedatsofar:
Attributesanddirectives:ThesedriveactionsandflowfromtheUIControllers:ThisprovidesthegluebetweentheUIandlogicServices:Thisisolatesthelogic
www.it-ebooks.info
PublishingandsubscribingmessagesOneofthegreatfeaturesofAngularJSisitsabilitytopublishandsubscribemessageswithinapage.Publishingandsubscribingmessagesisapowerfulcomponent,butlikewithanything,whenusedthewrongway,itcanleadtoamess.
Oneareawherethispatternisusefuliswhencommunicatingacrossboundariesinanapplication.ApplicationboundariesareimportantastheyallowtheUItohaveisolatedcode.ComplexityoccurswhenseparateUIcomponentsneedtobeawareofchangesinotherareasoftheUI.Withapublishingandsubscriptionmodel,applicationscancommunicateseamlesslyusingmessages.Thischapterwillfocusonpublishingandsubscribing.Youwillbeabletotakeacloserlookatwhatboundariesareanddeterminegoodplacestoleveragethisfeatureinyourownapplications.
Therearetwowaysinwhichmessagescanbepublished.Youcaneitheremitorbroadcast.Itisimportanttoknowthedifferenceasbothworkslightlydifferently,andtheymayaffecttheperformanceofyourapplication.
EmittingOnewaytopublisheventsistoemitthem.Thedocumentationathttps://docs.angularjs.org/api/ng/type/$rootScope.Scopegivesthefunctionalityofthe$emit()methodasfollows:
Dispatchesaneventnameupwardsthroughthescopehierarchynotifyingtheregistered$rootScope.Scopelisteners.
Theimportantthingtonoteis$emit()notifiesupthroughthescopesallthewaytothetopofthehierarchy.Thisisimportantbecauseifyouhaveanembeddedcontrollerscope,itisgoingtohavetopropagateallthewayuptoeverycontrollerandscope.Thiscancauseaperformanceissue.Hereisanexampleofhowtoemitanevent:
$scope.someAction=function(){
$scope.$emit('ANYEVENT');
};
Thebestwaytoseetheupwardpropagationoftheeventisthroughatest.Thenextsectionwillshowyouhowtounittesttheupwardeffectof$emit().
Testingemit
Thefollowingtestshavethreecontrollers:TopController,MiddleController,andBottomController.MiddleControllerwillemittheevent.Fromthis,anexpectationcanbemadethatTopControllerwillreceivetheeventandBottomControllerwon’t,astheemissionpropagatesinanupwardfashion.Herearethestepstotestthe$emit()method:
1. Createspiestotesttheemissionofevents:
vartopEventSpy=jasmine.createSpy();
varbottomEventSpy=jasmine.createSpy();
www.it-ebooks.info
2. Thetestsetupfirstsetsthehierarchyofscopes:
inject(function($controller,$rootscope){
vartopScope=$rootscope.$new();
varmiddleScope=topScope.$new();
varbottomScope=middleScope.$new();
3. Thenthecontrollersaresetwiththeirrespectivescopes:
$controller('TopController',{$scope:topScope});
$controller('MiddleController',{$scope:middleScope});
$controller('BottomController',{$scope:bottomScope});
4. Setthespytocapturetheevents:
topScope.$on('MIDDLEEMIT',topEventSpy);
bottomScope.$on('MIDDLEEMIT',bottomEventSpy);
5. Emittheeventfromthemiddlescope:
middleScope.$emit('MIDDLEEMIT');
6. Addtheexpectationthatthetopspywascalledontheevents:
it('Shouldnotifytopcontroller',function(){
expect(topEventSpy.wasCalled).toBe(true);
});
7. Addtheexpectationthatthebottomspywasnotcalled:
it('Shouldnotnotifybottomcontroller',function(){
expect(bottomEventSpy.wasCalled).toBe(false);
});
Hereareacoupleofthingstonotefromtheprecedingtest:
ThisisaunittestthatwewillruninKarma.Theinjectmethodprovidesareferencetothe$controllerand$rootscopescopes.The$rootscopescopeisthetopmostscopeofanAngularJSapplication.Ifyou’reusing$rootscopetoemitevents,theywouldn’tneedtopropagateanymoreas$rootscopeisatthehighestlevel.Inthelaterexamples,$rootscopewillbeinjectedintothecontrollerandusedtolistentoandsendevents.Ascopecancreateanewchildscope.Achildscopeiscreatedusingthe$newmethod.Youcanimaginethistobeequivalenttoapagethathasembeddedcontainers:
<divng-controller="topController"
<divng-controller="middleController">
<divng-controller="bottomController">
</div>
</div>
</div>
Testingbroadcast
www.it-ebooks.info
Thedocumentationathttps://docs.angularjs.org/api/ng/type/$rootScope.Scopestatesgivesthefunctionalityofthe$broadcast()methodasfollows:
Dispatchesaneventnamedownwardstoallchildscopes(andtheirchildren)notifyingtheregistered$rootScope.Scopelisteners.
Asopposedtothe$emitmethod,whichpusheseventsupthroughthescopechain,$broadcastpusheseventsdownthechain.Theotherimportantdistinctiontomakeisthatthe$broadcasteventcan’tbecancelled,but$emitcanbe.Thesearesmallintricaciesthatifnotunderstoodproperlycanhaveanegativeeffectontheapplication.Likewiththe$emitevent,thefollowingexampleshowsthewaybroadcastingworksthroughatest.
Testingbroadcast
Utilizingsimilartechniquesfromtheemissiontest,herearethestepstotestthebroadcastingofevents:
1. Createthespies:
vartopEventSpy=jasmine.createSpy();
varbottomEventSpy=jasmine.createSpy();
2. Initializethescopes:
vartopScope=$rootScope.$new();
varmiddleScope=topScope.$new();
varbottomScope=middleScope.$new();
3. Settherespectivecontrollerscopes:
$controller('TopController',{$scope:topScope});
$controller('MiddleController',{$scope:middleScope});
$controller('BottomController',{$scope:bottomScope});
4. Setthespiestolistenfortheevents:
topScope.$on('MIDDLEEMIT',topEventSpy);
bottomScope.$on('MIDDLEEMIT',bottomEventSpy);
5. BroadcasttheeventfrommiddleScope:
middleScope.$broadcast('MIDDLEEMIT');
6. Havetheexpectationthatthetopscopewasnottouched:
it('Shouldnotnotifytopcontroller',function(){
expect(topEventSpy.wasCalled).toBe(false);
});
7. Havetheexpectationthatthebottomscopereceivedthemessage:
it('Shouldnotifybottomcontroller',function(){
expect(bottomEventSpy.wasCalled).toBe(true);
});
TheprecedingexplanationshaveshowedhowtointegrateandtesttwotypesofAngularJS
www.it-ebooks.info
events.Asyouprogressthroughtherestoftheeventtests,youwillfindthatthesetupandtechniquesusedherewillbeusedthroughouttherestofthechapter.
www.it-ebooks.info
Publishingandsubscribing–thegoodandbadKnowingwhentousepublishingandsubscribingisonething,butknowingwhennottousethemisthedifficultpart.
ThegoodBeforelookingattheproblemsthatpublishingandsubscribingcanleadto,herearesomeofthebestscenarioswhereyoucanusethistechnique:
CommunicatingimportanteventstodifferentcomponentsoftheapplicationReducingcoupling
Communicatingthroughevents
Whenthinkingabouteventsthatneedtobecoupled,itisimportanttothinkaboutwhatactionsaredrivingtheapplication.Givenabankapplication,eventsmightbeassimpleasDEPOSITEDandWITHDREW.Thesetwosimpleeventsmaybeusedinmanyotherplaces.Thinkaboutyouwantingtosendane-mailtothecustomereverytimetheywithdreworautomaticallyupdatedsomereal-timereport.Insteadofpollingthepersistencelayer,areal-timenotificationmessagecanbeused.InAngularJS,thismeansthattheUIcanbemadeupofdifferentcomponentsthatcanrespondtochangesinonearea,forexample,UInotifications,updatingworkflows,enablingfeatures,oranythingyoucanthinkof.
Communicatingeventssothatothercomponentscanrespondtothemiskey.Whenyouwanttoeasilyrespondtoeventsandchanges,publishingandsubmittingisthewaytogo.Thefollowingisanothertesttoshowhowcommunicationcanbeused:
1. Createscopesforthecontrollers:
recentTransactionScope=$rootScope.$new();
atmScope=recentTransactionScope.$new();
2. Assignthescopestothecontrollers:
$controller('AtmController',{$scope:atmScope});
$controller('RecentTransactionsController',
{$scope:recentTransactionScope});
3. Setthespies:
spyOn(atmScope,'$emit').and.callThrough();
spyOn(recentTransactionScope.recent,'push');
4. Callthemethodbeingtested:
atmScope.withdraw(3.33);
5. Settheexpectationthattheeventwasemitted:
it('shouldemitanevent',function(){
expect(atmScope.$emit).toHaveBeenCalled();
});
www.it-ebooks.info
6. Settheexpectationthattherecenttransactionsreceivedtheevent:
it('shouldsendeventtorecenttransactions',function(){
expect(recentTransactionScope.recent.push).toHaveBeenCalled();
});
Herearethecontrollerstofurtherclarifythecode:
1. TheAtmControllerproperty(publisher):
bankModule.controller('AtmController',['$scope',function($scope){
$scope.withdraw=function(amount){
$scope.$emit('WITHDREW',amount);
}
}]);
2. TheRecentTransactionsControllerproperty(subscriber):
bankModule.controller('RecentTransactionsController',['$scope',
function($scope){
$scope.recent=[];
$scope.$on('WITHDREW',function(amount){
$scope.recent.push(amount);
})
}]);
Asdiscussedwiththetests,AtmControlleremitstheWITHDREWeventafterawithdrawaloccurs.
Theprecedingstepsarejustasimpleexampleofhowpublishingandsubscribingcanhelpcommunicateimportantactivitiesacrossyourapplication.
Reducingcoupling
Communicationisoneaspectofthebenefitsofpublishingmessages.Messaginggivesyoudecreasedcoupling.Thinkabouttheprecedingbankapplicationthatcommunicateswhenawithdrawaloccurs.Themessagesmaybeusedformanydifferentaspectsoftheapplication,andsinceitisdecoupled,wedon’tneedtoworry.Ifwethinkaboutitanotherway,thewithdrawfunctiondoesn’tcareabouttherestoftheapplication.Itonlyfocusesonthefactthatitwillperformawithdrawalandthensendamessageuponitscompletion.Fromthesubscriptionperspective,therecenttransactionsdon’tcarewherethewithdrawalhappens.Itonlyhastofocusonwhatitneedstodowhenthishappens.
Decouplingtheapplicationcanbeextremelybeneficialfromatestingperspective.Takeanotherlookatthebankapplicationifyouwanttorefactorandseparateoutthetests.YoucouldcreateanewtestthatisspecifictotheRecentTransactionsproperty.Sincetheapplicationisdecoupled,itdoesn’tcareaboutAtmController.Thetestcanbeseparatedoutasfollows:
1. ThebeforeEachfunctioncanbereducedtoonlydealwiththescopeofrecentTransactionsControllerand$rootScope:
www.it-ebooks.info
varrecentTransactionScope={};
varrootScope={};
beforeEach(function(){
module('bank');
inject(function($controller,$rootScope){
rootScope=$rootScope.$new();
recentTransactionScope=$rootScope.$new();
$controller('RecentTransactionsController',
{$scope:recentTransactionScope});
});
spyOn(recentTransactionScope.recent,'push');
rootScope.$emit('WITHDREW',3);
});
2. InthebeforeEachfunction,addaspytohelpwithtesting:
spyOn(recentTransactionScope.recent,'push');
3. InsteadofcallingtheAtmControllerclass’swithdrawfunction,wecancall$emiton$rootScope:
rootScope.$emit('WITHDREW',3);
4. TheafterEachfunctionandtheexpectationarethesameasshownpreviously:
afterEach(function(){
recentTransactionScope.recent.push.calls.reset();
});
it('shouldsendeventtorecenttransactions',function(){
expect(recentTransactionScope.recent.push).toHaveBeenCalled();
});
Thisexamplehasshownthatusingmessaging,youcandecoupletests.Decouplingapplicationtestsallowstheapplicationtogrowwithouthavingtonegativelyrefactortheentireapplication.Intheprecedingcase,ifAtmControllerischanged,therecentTransactionstestandtherecentTransactionscontrollerwon’tneedtobechanged.AslongastheWITHDREWeventispublished,recentTransactionswillnothavetobeupdated.
www.it-ebooks.info
HarnessingthepowerofeventsPublishingandsubscribingeventscanleadtosomeuglyandhard-to-understandspaghetticode.Nowthatthefoundationsforthechapterhavebeenreviewed,youcandiveintoimplementingeventsintothesearchapplication.
www.it-ebooks.info
TheplanThesearchapplicationfromChapter5,FlipFlop,isquitebasic.Atthispoint,itwillreturnasetofresults,andthenwhentheuserclicksonaresult,detailswillappear.Theapplicationprovidesafoundationforfuturedevelopment.Inthischapter,thefunctionalitywillbeexpandedtoincludepublishingandsubscribing.Hereistheplantoexpandthesearchapplication:
Thesearchapplicationwillberebrandedasastoreapplication,andthesearchresultswilldisplayalistofproducts.Whenaproductisselected,detailswillbedisplayed.Allselectedproductsfromthesearchwillbeavailableinanewviewfor“recentlyviewed”items.Thedetailedviewoftheproductwillhavetheoptionto“addtocart”,andtheproductwillthenbeavailableinthecartview.
Theplanissomewhatambitious,butwithalltheknowledgewehaveonTDDandAngularJS,thedevelopmentshouldflownicely.
www.it-ebooks.info
RebrandingThesearchapplicationwillberebrandedintoastoreapplicationinsteadofrewritingthesearchfunctionalitythathasalreadybeenwritten.Inordertoleveragetheexistingsearchproject,itwillbecopiedintoanewprojectfile.Then,thenewprojectwillusetheteststodrivethedevelopmentchangesandrefactoring.Therefactorstepshavebeenleftout,butareviewofthecodewillshowhowthecodeandtestsweremodifiedtocreatetheproductapplication.
Therefactorstepsupdatedtheunittestsandapplicationtosupportthecorrectnamingfortheapplication.Itisimportanttotakeawaytwothingsfromthis:
Refactorsmalltointroducebigchanges.Smallincrementalchangeshelptoprogressivelygettothenextstageoftheapplication.Whenbigchangesoccur,itcanbeconfusingtoknowwhereandwhattochange.Withsmallchanges,eventhoughthesamecodeisrevisitedseveraltimes,youcanensurethetestspassateachstageinsteadofrippingtheapplicationapartcompletelyandthentryingtoputitallbacktogetheragain.TDDappliesduringrefactoringjustasmuchaswhendoingcoredevelopment.TherefactorstepsfollowedwerethesameastheTDDsteps.Startwithchangingthetesttomeetourspecificationandthenmakethecoderuntomeetthespecification.Applyingtheseprincipleshelpskeepproductivityandfocus.
Boththeunittestsandend-to-endtestspassfromtherefactorsteps.Itistimetoturntothefirstfeatureoftheapplication.
www.it-ebooks.info
SeeingrecentlyvieweditemsNowthattheinitialrefactoringiscomplete,thenewfunctionalityoftheproductapplicationcanbeconsidered.Thefirstspecificationthatwillbeconsideredistheabilitytosee“recentlyviewed”items.Thespecificationisbrokendownintotwosteps,asfollows:
TheuserselectsaproducttoviewthedetailsTheywillbeabletoseetheviewedproducts
Thisisanexampleofwherebroadcastingwouldbeagoodcandidate.Intheprecedingcase,thespecificationisconcernedwithwhenaproducthasbeenselected.Inotherwords,whenaneventoccurs,asubsequentactionneedstohappen.UsingAngularJSevents($broadcast()/$emit()),theeventofselectingaproducttoviewcanbepublishedandthenconsumedbytherecentlyviewedcomponent.
ThestandardTDDlifecyclewillbeusedtobuildthiscomponent:testfirst,makeitrun,makeitbetter.Wewillbeusingabottom-upapproach(unittestfirst).Themainreasonforchoosingthisapproachisthattherearemultiplecontrollersinvolved,anditwillbeeasiertostartatthebottomandmakeourwayupthroughtheapplication.
TestfirstThefirsttestwewillbewritingisthattheSearchControllerclasswillpublishaneventwhenaproductisselected.Thefollowingsectionsdetailhowtowritethetest.
AssemblingSearchController
HerearethestepstoassembletheSearchControllerclass:
1. Startwiththeteststubusingthefollowingcode:
describe('',function(){
beforeEach(function(){
});
it(function(){
});
});
2. GetthescopeofSearchControllersothatanactioncanbeperformed:
describe('',function(){
beforeEach(function(){
module('product');
inject(function($controller,$rootScope){
varsearchControllerScope=$rootScope.$new();
$controller('SearchController',{$scope:searchControllerScope});
});
});
it(function(){
});
});
www.it-ebooks.info
3. PlaceaspyontheSELECTEDPRODUCTevent:
varselectedProductSpy=jasmine.createSpy();
varsearchControllerScope={};
beforeEach(function(){
module('product');
inject(function($controller,$rootScope){
searchControllerScope=$rootScope.$new();
$controller('SearchController',
{$scope:searchControllerScope,$rootscope});
searchControllerScope.$on('SELECTEDPRODUCT',selectedProductSpy);
});
})
4. Addacleanupfunctiontoclearthescopeaftereachtestandclearthespy:
afterEach(function(){
searchControllerScope={};
selectedProductSpy.reset();
});
Selectingaproduct
ThetestrequiresthataSELECTEDPRODUCTeventhasbeenpublished.TheeventwilloccurwhentheselectedproductmethodiscalledwithproductId:
varfakeProduct={productId:1};
searchControllerScope.selectProduct(fakeProduct);
Expectingeventstobepublished
TheexpectationisthatselectedProductSpyhasbeencalled:
it('',function(){
expect(selectedProductSpy).toHaveBeenCalled();
});
MakingthesearchcontrollerrunNowwehavetomakethetestpassandrun.Herearethesteps:
1. StartKarmausingthefollowingcommand:
$karmastart
2. You’llgetanerror,namelyTypeError:'undefined'isnotafunction(evaluating'searchControllerScope.selectProduct(fakeProduct)').Torectifythis,performthefollowingstep:
1. AddthemethodtoSearchController:
$scope.selectProduct=function(){};
3. Thenyou’llgettheerrorExpectedspyunknowntohavebeencalled.Error:Expectedspyunknowntohavebeencalled.Torectifythis,performthe
www.it-ebooks.info
followingsteps:
1. Theexpectationhasfailed,whichmeansthespywasnevercalled.OpenupSearchControllerandaddfunctionalitytotheselectProductmethodtoemitanevent:
$scope.selectProduct=function(productId){
$rootScope.$broadcast('SELECTEDPRODUCT',productId);
};
2. Rerunthetest.
4. Thetestwillpass.
Nowwhenaproductisselected,theeventisbroadcasted.Anyfunctionwantingtoknowwhensomethinggetsselectedcansimplylistenforthebroadcast.
RecentlyviewedunittestThenextstepistoaddanothertestfromthesubscriptionsideoftheeventtoRecentlyViewedController.
Testfirst
Again,thewalk-throughoftheteststepswillusethe3A’s.
AssemblingRecentlyViewedController
HerearethestepstoassembleRecentlyViewedController:
1. Startwiththeteststubusingthefollowingcode:
describe('',function(){
beforeEach(function(){
});
it(function(){
});
});
2. GetthescopeofRecentlyViewedControllersothatanactioncanbeperformed:
describe('',function(){
beforeEach(function(){
module('product');
inject(function($controller,$rootScope){
varrecentlyViewedScope=$rootScope.$new();
$controller('RecentlyViewedController',
{$scope:recentlyViewedScope});
});
});
it(function(){
});
});
3. Confirmthatthenumberofrecentlyviewedproductsisequalto0:
www.it-ebooks.info
expect(recentlyViewedScope.recent.length).toBe(0);
Invokingarecentlyvieweditem
TheactionforthistestisthattheSELECTEDPRODUCTeventhasbeenpublished.Nowaddthepublishevent:
varfakeProductEvent={productId:1};
$rootscope.$broadcast('SELECTEDPRODUCT',fakeProductEvent);
ConfirmingRecentlyViewedController
Theassertionisthatthenumberofrecentlyviewedproductsisnowequalto1:
it('',function(){
expect(recentlyViewedScope.recent.length).toBe(1);
});
MakingRecentlyViewedControllerrunHerearethestepstorunRecentlyViewedController:
1. StartKarmausingthefollowingcommand:
$karmastart
2. You’llgetanerror,namelyError:[ng:areq]Argument'RecentlyViewedController'isnotafunction,gotundefined.Torectifythiserror,performthefollowingsteps:
1. CreatetherequiredcontrollerandcreateanewfilenamedRecentlyViewedController.js.
2. Then,addthefollowingdetails:
angular.module('product')
.controller('RecentlyViewedController',['$scope',function($scope){
}]);
3. Rerunthetest.
3. Thenyou’llgettheerrorTypeError:'undefined'isnotanobject(evaluating'recentlyViewedScoperecent.length'),whichmeansthatthefirstexpectation,thatistherecentproduct0,hasbeenhit.Astheobjectisundefined,addittotherecentlyViewedScopescope.
4. Thenyou’llgettheerrorExpected0tobe1.Error:Expected0tobe1.Torectifythis,performthefollowingsteps:
1. Theexpectationhasbeenhit.Nowthebehavioroftheeventneedstobeaddedtothecontroller.
2. Add$rootScopetothecontroller:
.controller('RecentlyViewedController',
['$scope','$rootScope',function($scope,$rootScope){
www.it-ebooks.info
3. Subscribetotheeventfrom$rootScope:
$rootScope.$on('SELECTEDPRODUCT',function(productEvent){
})
4. NowaddproductEventtotherecentarray:
$rootScope.$scope.recent.push(productEvent)
5. Rerunthetest.
5. Thetestswillnowpass.
End-to-endtestingTheunittestsarecompleteandwillverifythatthepublisherandsubscribercanbothcommunicatewithevents.Nowthewalk-throughwilllookattheapplicationasawholeandwillshowyouhowtocreateanend-to-endtest.Thespecificationforrecentlyvieweditemsisthatinagivensearchresult:
AproductisselectedItwillbeavailableintherecentlyvieweditems
Now,itistimetomoveontoactuallycreatingthetest.
Testfirst
Asalways,startbytranslatingthespecificationinthetestusingthe3A’s,asthetestswillutilizetheexistingtests.Assemblingtherecentlyviewedend-to-endtest
BeforeyourepeatthecodefromChapter5,FlipFlop,youshouldnoticethatthefirsttestalreadysearchesforandretrievesthesearchresults.Therefore,therecentlyviewedtestcanbeembeddedwithintheexistingtestforasearchresultthatisalreadyavailable.Atthebottomoftheexistingfunctionofasearchquery,initializetheteststub:
describe('whenItypeinasearchquery',function(){
//...
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
Thereisnothingelsetoassembleforthetest,andyoucanmoveontothenextstep.Selectingasearchresult
Now,searchResultneedstobeinvokedusingthefollowingsteps:
1. ThefirststepwillbetoselectthefirstsearchResultelement:
varfirstResult=searchResult.first();
www.it-ebooks.info
2. Findthelinkwithinthefirstitem:
varresultLink=firstResult.element(by.css('a'));
3. Clickontheresult:
resultLink.click();
Confirmingrecentlyvieweditems
Nowthataproducthasbeenselectedandoneproducthasbeenaddedtotherecentlyvieweditemslist,weneedtoviewtherecentlyvieweditems.Herearethestepstodothis:
1. Gettherecentlyvieweditems:
varrecentlyViewedItems=element(by.repeater('itemsinrecent'));
2. Confirmthatthecountofrecentlyvieweditemsisequalto0:
expect(recentlyViewedItems.count()).toBe(1);
MakingtherecentlyViewedItemstestpass
Nowthetestneedstopass.Herearethestepstodothis:
1. Startthewebsite:
./node_modules/http_server/bin/http_server
2. RunProtractor:
./node_modules/protractor/bin/protractorchromeOnlyConf.js
3. You’llgetanerror,namelyExpected0tobe1..4. Theerroristhattheexpectationhasfailed.Itistimetoaddthecontrollerand
repeatertotherecentlyvieweditemslisttoshowtheitems:
<divng-controller="RecentlyViewedController">
<divng-repeat="iteminrecent">
{{item}}
</div>
</div>
5. Rerunthetest6. Theerroristhesameasbefore.Thistime,Protractorerrorsdon’tgiveanycluesto
whattheissueis.ThenextstepistoopenupabrowserandseewhatthewebbrowserJavaScriptconsoleissaying.Pointyourbrowsertohttp://localhost:8080/#/recentlyViewed.Immediately,oneerrorwillbevisible,namely[ng:areq]Argument'RecentlyViewedController'isnotafunction,gotundefined.Torectifythis,performthefollowingsteps:
1. Nowthatthereisanactualerrortofix,progresscanbemade.Theerrorindicatesthatthecontrollerwasnotavailable.Asthecontrollerhasnotbeenadded,itistimetoaddthecontrollertothepage.Openuptheindex.htmlpage
www.it-ebooks.info
andaddthecontrollerreference:
<scriptsrc="app/recentlyViewedController.js"></script>
2. Rerunthetest.
7. Nowthetestwillbesuccessful.
Makingrecentlyvieweditemsbetter
Therecentlyviewedcontrollerisnowcomplete.Itwouldbenicetobetterorganizetheview,howeverthiscanhappenlater.Thepointofthisexercisewastoestablishcommunicationbetweenseparateviewsandcreateausablefunction.Thishasbeenachieved,andnowyoucanmovetothenextstepofthewalk-through.
www.it-ebooks.info
CreatingaproductcartAnotherimportantaspectoftheapplicationistheabilitytoaddproductstoacart.Apublishingandsubscriptionmodelwillbeusedtopublishwhenanitemhasbeensavedtoacart.Asubscriptiontotheeventwillthenkeeptrackofitemsinthecartsotheusercaneasilyseewhensaveditemsgetupdatedinrealtime.Hereisthespecificationgiventheproductdetailsofaparticularproduct:
IftheproductissavedtoacartProductwillbedisplayedintheproductcartview
Nowthenecessarythingsareinordertogetdowntothe3A’s.
PublishertestfirstThepublisherwillcomefromsearchDetailController.Thetestwillneedtoensurethatwhenanitemissaved,aneventispublished.
AssemblingsearchDetailController
ThesearchDetailControlleralreadyhassomeunittestswritten.Theexistingtestcanbeleveragedtoconfirmthepublishingfeature.Herearethestepstocreateasubtesttohandlethesavingofacart:
1. Startwithaninnerstub:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
})
2. Inordertotestthataneventhasbeenemitted,aspywillbeneededon$rootScope.Bringin$rootScopeandaddaspytoit:
//...
varsavedToCartEventSpy=jasmine.createSpy();
beforeEach(function(){
inject(function($rootScope){
$rootScope.$on('SAVEDTOCART',savedToCartEventSpy);
});
});
3. AddafterEachtoresetthespy:
afterEach(function(){
savedToCartEventSpy.calls.reset();
});
Invokingthesavingofaproduct
InthebeforeEachsection,selectthemethodandmakethefollowingchanges:
www.it-ebooks.info
beforeEach(function(){
//...
varfakeProduct={productId:1};
searchDetailScope.saveProduct(fakeProduct);
})
Confirmingthesaveevent
Theexpectationisthatthespyhasbeencalled:
it('',function(){
expect(savedToCartEventSpy).toHaveBeenCalled();
})
MakingthesaveProducttestpassNowweneedtomakethetestpass.HerearethestepstomakethesaveProducttestpass:
1. StartKarma:
$karmastart
2. ThefirsterrorwillbeTypeError:'undefined'isnotafunction(evaluating'searchDetailScope.saveProduct(fakeProduct)').Ifyougetthiserror,thenfollowthesesteps:
1. Thefunctiondoesn’texistonthescope.Additusingthefollowingcode:
$scope.saveProduct=function(product){};
2. Rerunthetest.
3. NowtheerrorhashittheexpectationandsaysExpectedspyunknowntohavebeencalled.Inthiscase,followthegivensteps:
1. Thesmallestthingwecanaddtothetestistheabilitytoemittheeventfromthemethod.Firstadd$rootScopetothecontroller:
.controller('SearchDetailController',
['$scope','$routeParams','productService','$rootScope',function($sc
ope,$routeParams,productService,$rootScope){
2. ThenaddtheSbroadcast()eventtoit:
$rootScope.$broadcast('SAVEDTOCART',product);
3. Rerunthetest.
4. Thetestissuccessful.
TestforthesubscriberfirstThesubscriberunittestwillconfirmthatwhenaSAVEDTOCARTeventisemitted,thentheproductwillbeaddedtothecartobject.ThespecificationisasaSAVEDTOCARTeventis
www.it-ebooks.info
given,thefollowingactionwillbeperformed:
Itwilladdtheproducttothecart
Assemblingtheproductcarttest
Herearethestepstoassembletheproductcarttest:
1. Createanewfile,spec/unit/cart.js.2. Startwiththebasestub:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
3. Initializethemodule:
module('product');
4. Initializethescopesothatexpectationscanbemade:
varscope={};
beforeEach(function(){
//...
inject(function($controller){
$controller('CartController',{$scope:scope});
});
});
5. Initialize$rootScopesosubscriptionscanbemade:
inject(function($controller,$rootScope){
scope=$rootScope.$new();
$controller('CartController',{$scope:scope,$rootScope:$rootScope});
});
6. Thelastthingtoconfirmisthatthecartisempty.Addthefollowingexpectationtoensurethetestissetupproperly:
expect(scope.cart.length).toBe(0);
Invokingasavedcartevent
ThistestisaroundthefactthatwhentheSAVEDTOCARTeventispublished,theCartControllerpropertywillperformaspecificaction.AddthepublishingoftheeventtothebeforeEachmethod:
beforeEach(function(){
//...
varfakeProduct={productId:1};
$rootScope.$broadcast('SAVEDTOCART',fakeProduct);
});
Confirmingthesavedcart
www.it-ebooks.info
Nowthatthetesthasbeensetupandtheactperformed,youcanassert.Assertthatthenumberofcartitemsisequalto1byaddingthefollowingcode:
it('',function(){
expect(scope.cart.length).toBe(1);
});
MakingthecartcontrollertestrunNowit’stimetowalkthetestthroughthecyclebyfollowingthegivenstepsuntilwegetagreentest:
1. StartKarma:
$karmastart
2. ThefirsterrorisError:[ng:areq]Argument'CartController'isnotafunction,gotundefined.Asseenpreviously,thecontrollerhasn’tbeencreated.Createanewfileandsetupastubcontroller(/app/cart.js):
angular.module('product')
.controller('CartController',['$scope',function($scope){
}]);
3. ThenexterrorwillbeTypeError:'undefined'isnotanobject(evaluating'scope.cart.length').Thisindicatesthatnoobjectwasfoundonthescopenamedcart.Goaheadandcreateitnowinapp/cart.js:
$scope.cart=[];
4. Then,you’llgetanexpectationerror,namelyExpected0tobe1.Error:Expected0tobe1.Torectifythis,performthefollowingsteps:
1. Atthispoint,thecontrollerisnotdoinganythingwiththeeventbeingemitted.Add$rootScopeasadependencytotheapplication:
.controller('CartController',
['$scope','$rootScope',function($scope,$rootScope){
2. Addthehandlinglogictocapturetheeventandaddtheproducttothecart:
$rootScope.$on('SAVEDTOCART',function(productEvent){
$scope.cart.push(productEvent);
});
5. Success!Allthetestshavepassed.
End-to-endtestingTheunittestsarenowcomplete,anditisnowtimetoperformend-to-endtestingforthecart.
Assemblingthecart’send-to-endtest
www.it-ebooks.info
ThetestcomesfromtheperspectiveofbeingonaproductdetailviewandselectingaSavetoCartbutton.Oncetheitemhasbeensaved,itshouldbeavailableinthecartview.Herearethestepstoassemblethecart’send-to-endtest:
1. Createanewfilenamedspec/e2e/cartScenario.js.2. Startwiththebasetemplatetest:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
3. Thenextthingweneedtodoisnavigatetoaproductpage:
browser.get("#/product/1");
4. Selectthebuttonthatwillsavethecart:
varsaveToCartButton=element(by.buttonText('SavetoCart'));
Invokingasavetocartaction
TheactionistoclickontheSavebuttonusingthefollowingcode:
saveToCartButton.click();
Confirmingproductshavebeensaved
Theassertistoconfirmthatthecartviewnowhasatleastoneproduct:
it('',function(){
varproductsInCart=element.all(by.repeater('productincart'));
expect(productsInCart.count()).toBe(1);
})
Makingthecart’send-to-endtestpassHereisthewalk-throughoftheprocessofmakingtheapplicationrun:
1. Startthesite:
$./node_modules/http-server/bin/http-server.
2. RunProtractor:
$./node_modules/protractor/bin/protractorchromeOnlyConf.js
3. ThefirsterrorisNoSuchElementError:Noelementfoundusinglocator:by.buttonText("SavetoCart").Torectifythis,performthefollowingsteps:
1. Goaheadandcreatethebuttonwithintheproductdetail’sapp/searchDetail.htmlpartialview:
<button>SavetoCart</button>
www.it-ebooks.info
2. Rerunthetest.
4. ThenexterrorisExpected0tobe1.Torectifythis,performthefollowingsteps:
1. Thiserrormeansthatthecountis0forproductsinthecart.Byreviewingtheindexpage,youcanseethatthecartdoesn’tevenexistinthepage.First,addareferencetothecartcontroller:
<scriptsrc="app/cart.js"></script>
2. Next,theitemsinthecartneedtobeaddedtothepage.First,addatagwiththecontroller:
<divng-controller="CartController"></div>
3. Finally,addarepeatertodisplaytheproductinthecart:
<ul>
<ling-repeat="productincart">{{product}}</li>
</ul>
4. Rerunthetest.
5. Thesameerroroccurs,Expected0tobe1.Torectifythis,performthefollowingsteps:
1. Eventhoughtheproductdatahasbeenadded,thetestisstillfailing.Thenextquestioniswhetheranythingisbeingaddedtothecart.Inthiscase,no.Thebuttonisbeingselectedbutnoactionhasbeenassociatedwithit.Updatethebuttoninapp/searchDetail.htmltousethesearchDetailControllerclass’ssaveProductmethod:
<buttonng-click="saveProduct()">SavetoCart</button>
2. Rerunthetest.
6. Allthetestspass.
www.it-ebooks.info
Self-testquestionsThefollowingaresomequestionstocheckyourunderstanding:
Q1.Whenbroadcastingamessage,itpropagatesupthescope’shierarchy.
1. True2. False
Q2.ThefollowingcreatesaspyinJasmine:
1. varspy=jasmine.createSpy();2. varspy=jasmine.$new();3. varspy=jasmine.createFake();
Q3.The$rootScopescopeisthehighestlevelscopeinAngularJS.
1. True2. False
Additionally,ifyouwantmorepractice,addtheabilitytoaddlikestothepage.
www.it-ebooks.info
SummaryThischapterhasexploredeventswithinAngularJS.YousawtwotypesofAngularJSeventemitters:$broadcast()and$emit().YoualsosawsomeexamplesofapplyingTDDtoeventsandhoweventsgiveaseparationofcontrollersandcode.Inaddition,youexpandedthetypesoftestingtechniquestoincludeservicesandreiteratedthetestingofcontrollersandmodels.YoualsoexploredfurtherconfigurationofKarmatouseitsfeatures.Inthenextchapter,youwilllookattheintegrationandtestingofdataandAPIsintoanAngularJSapplication.
www.it-ebooks.info
Chapter7.GiveMeSomeDataApplicationsneedawaytoconsumetheever-expansiveworldofdata.Mostapplicationswrittentodayconsumedata.LuckilyforAngularJSdevelopers,consumingdataisquiteeasy.Testingdataconsumptionisalsoacorecomponentoftheframework.Inthischapter,wewillcoverthefollowingtopics:
IntegratingaREST-basedserviceCreatingandmockingAngularJS’s$httpHandlingexceptionsImplementingafakeAPIbuilderpattern
www.it-ebooks.info
REST–thelanguageoftheWebRepresentationalStateTransfer(REST)defineshowtheWebshouldcommunicate.FromanAngularJSapplicationstandpoint,themainconcerniswiththeHTTPmethods.ForHTTPmethods,RESTcanbethoughtofastheverbsoractionsthatanHTTPrequestcanmake.Specifically,anHTTPrequestcanmaketheserequesttypes:GET,POST,PUT,andDELETE.FromanAPIstandpoint,theHTTPmethodscanbeusedtodeterminehowlogicshouldhandlethespecificHTTPrequesttype.HereisafurtherlookatthecommonHTTPmethods:
HTTPMethod Description Example
GET Retrievesdatafromanendpoint curl--requestGET'http://<SOMEURL>'
POST Postsanewdataelementtotheendpoint curl–requestPOST'http://<SOMEURL>'–data'anydata'
PUT Insertsorupdatestheencloseddataelementtotheendpoint curl–requestPOST'http://<SOMEURL>'–data'anydata'
DELETE Deletesarequesttotheendpoint curl--requestDELETE'http://<SOMEURL>'
NoteThecurltoolisacommand-linetoolthatcanbeusedtomakerequests.OnUnixmachines,itisavailableinthecommandlinebysimplytypingcurl.ForWindowsmachines,itisbesttoinstallGitbashandaccessitthroughtheGitbashcommandline.InstallationinstructionsforGitandGitbashcanbefoundathttp://git-scm.com/downloads.
Ascanbeseenfromtheprecedingexplanation,theRESTfulcomponentsofHTTPcandefinethebasicsformostAPIs.TheprecedingRESTapproachisdifferentfromotherwebservicetechniquesorprotocolsandcanbeusedbypracticallyanything.Fortheirsimplicity,REST-basedwebservicesarethebestoptions.Inthischapter,thefocuswillonlybeonhowtouseAngularJSwithaREST-basedAPI.
www.it-ebooks.info
GettingstartedwithRESTBeforejumpingintohowAngularJScommunicateswithaRESTlayer,itisimportanttoseehowtocommunicateusingstandardtoolswithinabrowser.Asyousawfromthepreviousdefinition,curlcanbeusedtocommunicatetoaRESTAPI.AlthoughmakingamanualHTTPrequestoutsideofabrowserisuseful,youalsoneedtounderstandthebasicsofhowabrowsermakesanAPIrequestwithoutaframework.Inabrowser,requestscanbemadetoRESTlayersthroughasynchronouscalls.Thisallowsrequeststhatwon’taffecttheotherpartsoftheapplicationtobemade;thatis,thepagewon’tfreezeandbecomeunusable.Thewebpageremainsuseablewhiletherequestismade.
BrowsersprovideamechanismtomakeasynchronousRESTcallsusinganXMLHttpRequestmethod.AnXMLHttpRequestmethodcanbeusedtomakeanHTTPGET,POST,PUT,orDELETErequest.HereisanexampleofhowtomakeaGETrequest:
varrequest=newXMLHttpRequest();
request.open('GET','/any/rest/endpoint');
request.send();
Theprecedingexamplecreatesanewrequest,specifiestherequesttypeandlocation,andfinally,sendstherequest.Themissingpieceisthehandlingoftheresponse.Addthefollowingcodejustbeforethesendmethod:
request.onreadstatechange=function(){
if(request.readyState===4){
console.log('receivedresponsewithstatus:'+request.status);
}
};
Theprecedingcodehandleswhentherequesthasreceivedaresponsefromtheserverandiscomplete(readystate===4).Withintheconditiongiveninthecode,youcanhandletheparsingoftheresponse,thedeterminingstatusoftherequest,andsoon.
What’sgreatabouttheprecedingcodeisthatitdoesn’trequireaframework.Theproblemisthatthecodecangrowinsizeandbecomerepetitiveforeveryrequest.AngularJShasabstractedtherequestforyou.
www.it-ebooks.info
TestingasynchronouscallsNowthatyouunderstandhowtomakeHTTPrequeststhroughthebrowser,weneedtounderstandhowtotestthesecalls.Theprecedingrequestsareasynchronous.Asynchronousmeansthereisnoguaranteeofwhenthefunctionwillcomplete.Foryourreference,hereisanexampleofsynchronoussequentiallogic:
varsynchronousFunc=function(){
console.log('InsynchronousFunc');
};
synchronousFunc();
console.log('AftercalltosynchronousFunc');
Whentheprecedingcodeisrun,theoutputisasfollows:
InsynchronousFunc
AftercalltosynchronousFunc
Eachfunctioncalloccursintheorderofthecall.Withanasynchronousrequest,theorderisnotguaranteed.Acallbackfunctionispassedintoafunctiontoinformyouwhenamethodiscomplete.
TipCallbackfunctionshavetwomainconventions.ThefirstisthejQuery-basedmethod.ThesecondistheNode.jsmethod.ThejQueryconventionusestwocallbacksasthelastargumentstoamethod.Thefirstcallbackisforsuccess,andthesecondisforanerror.TheNode.jsconventionistouseasinglecallbackasthelastargument.Thecallbackhastwoparameters,thefirstbeinganerrorandthesecondbeingthesuccessfulresult.
Itisuptoyoutodecidewhichconventiontousebasedonwhatyou’redevelopingfor.Don’tcreateyourownnewconvention;useoneoftheprecedingconventionssothatotherdeveloperscaneasilyunderstandandreadyourcode.
Hereisanexampleoftheoutputofanasynchronousmethod:
varasynchronousFunc=function(callback){
setTimeout(callback,0);
};
varcallback=function(){
console.log('InasynchronousFunc');
};
asynchronousFunc(callback);
console.log('AftercalltoasynchronousFunc');
Whentheprecedingcodeisrun,theoutputisasfollows:
AftercalltoasynchronousFunc
InasynchronousFunc
ThenextsectionswilllookathowtesttoasynchronousfunctionsinKarmaandProtractor.
www.it-ebooks.info
CreatingasynchronouscallsinKarmaFromtheprecedingasynchronousexample,itshouldbeclearthatthewayinwhichyoutestneedstobemodifiedtoaccountforasynchronousbehavior.Luckily,thisisfairlystraightforwardwhentestingwithKarma.
HerearethestepstotesttheprecedingasynchronousmethodusingKarma:
1. Createthestubtestusingthefollowingcode:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
2. Createaspytotestwhentheasynchronousmethodgetscalled:
varspy=jasmine.createSpy();
3. CalltheasynchronousmethodinthebeforeEachfunction:
beforeEach(function(){
varasynchronousFunc=function(callback){
setTimeout(callback,0);
};
varcallback=function(){
spy();
};
asynchronousFunc(callback);
});
4. AddacallbacktotheparametersofthebeforeEachfunction.Bydoingthis,youhavemadethefunctionasynchronous:
beforeEach(function(done){
…
});
5. CallthedonemethodintheasynchronousFunccallback:
varcallback=function(){
spy();
done();
};
6. Addtheassertionfunction:
it('',function(){
expect(spy).toHaveBeenCalled();
});
ThekeytotheprecedingcodeisthatacallbackwaspassedintothebeforeEachfunction.Youcantrytorunthistestwithoutthecallbackandseewhetherthetestwillfail.A
www.it-ebooks.info
callbackcanbepassedintothebeforeEach,afterEach,describe,anditmethods.
Youwillbeleveragingthisexamplethroughtherestofthechapter,sobesurethatyouunderstandthemainconcepts.NowthatyouhavetestedinKarma,thenextsectionwillshowyouwhatProtractoroffersfromanasynchronousstandpoint.
www.it-ebooks.info
CreatingasynchronouscallsinProtractorProtractorisdifferentinthewayithandlesasynchronousactions.Ithasbeenoptimizedtohandleasynchronousactions,specifically,promises.Asanexample,whenatestnavigatestoapage,ProtractorwillwaituntilAngularJShasbeenloadeduntilitstartsrunningthetests.JulieRalph,themaincontributorandcreatorofProtractor,sumsitupinthisGitHubissue(https://github.com/angular/protractor/issues/716):
ProtractorpatchesJasminesothatitisautomaticallyasynchronous,andatestcasefinisheswhentheWebDriverqueueofcommandsisfinished.
Whatthismeansisthatyoudon’thavetothinkabouthowthecallsarebeingrenderedandwhenthepromisesarecomplete.Itevenwaitsfor$httprequeststocomplete.HereisanexampleofusingProtractor:
describe('WhenItypeinasearchquery',function(){
varsearchResult=element.all(by.repeater("resultinresults"));
beforeEach(function(){
browser.get("/index.html");
$('input').sendKeys('anyvalue');
element(by.buttonText('search')).searchButton.click();
});
it('Shouldthenaddtheresult',function(){
expect(searchResult.count()).toBe(1);
});
});
TheprecedingcodesnippetistakenfromChapter6,TelltheWorld.IthighlightshowProtractorexecuteseachoneofthecommandsandtakescareoftheasynchronousbehaviorsforyou.Inthenextsection,youwillseehowtomakeRESTrequestsusingAngularJS.
www.it-ebooks.info
MakingRESTrequestsusingAngularJSNowthatwehavelookedatwhatRESTrequestsareandseenhowtotestasynchronouslyinKarmaandProtractor,itistimetoseehowtomakearequestinAngularJS.Atthelowestlevel,AngularJSprovidesthe$httpmodule.ThemoduleallowsyoutomakeHTTPrequests.Byvisitingthedocumentation(https://docs.angularjs.org/api/ng/service/$http),wecanseethatitsaysthefollowing:
The$httpserviceisacoreAngularservicethatfacilitatescommunicationwiththeremoteHTTPserversviathebrowser’sXMLHttpRequestobject.
AsyouhavealreadyseenhowtomakeanXMLHttpRequest,youshouldfeelateasethatyouknowwhatisgoingonunderthehood.Hereisasimpleexampleofhowtomakean$http.getrequestinAngularJS:
$http.get('/any/rest/endpoint')
.success(function(data,status,header,config){
});
.error(function(data,status,header,config){
});
Thesuccess/errorfunctioniscalledasynchronouslyoncetherequestiscomplete.
Using$httpisnottheonlywaytomakearequest.IfanAPIiscompletelyREST-based,AngularJSprovidesthe$resourcemodule.Aresourcegetsdefinedandusedasshowninthefollowingsteps:
1. Definearesourceforaspecificendpoint:
varthing=$resource('/any/rest/endpoint/:id',{id:'@id'});
2. MaketheHTTPGETrequest:
thing.get({id:1},function(aThing){
…
});
TheprecedingexampledefinesaresourcethatretrievesaThingbasedonanID.ItthenretrievesthatdatawithaGETrequest.
BothoftheprecedingexamplesshowyouhowtocreaterequestsinAngularJS.Youwillbelookingatthe$httpmethodintheremainingexamples,butitisgoodtounderstandthedifferentwaysinwhichrequestscanbecreatedinAngularJS.
www.it-ebooks.info
TestingwithAngularJSRESTNowthatyouhaveseenhowtomakerequestsinAngularJSandhowtotestasynchronously,youwillneedtolookathowtoputittogether.ThefollowingexamplelooksataspecificserviceandthendiscusseshowtotestusingKarma.
TestingtheproductserviceTheservicethatneedstobetestedisasfollows:
angular.module('anyModule')
.service('productService',['$http',function($http){
return{
search:function(query){
return$http.get('/product/search');
}
};
});
TheprecedingproductServiceparameterprovidesanobjectsearchthattakesinaqueryandreturnsa$httppromise.Theproductservicecanbeusedinacontrollerasfollows:
productService.search(query)
.success(function(data){
$scope.result=data;
})
.error(function(data){
$scope.error=data;
});
angular.module('anyModule')
.controller('productController',['$scope','productService',
function($scope,productService){
$scope.search=function(query){
productService.search(query)
.success(function(data){
$scope.result=data;
})
.error(function(data){
$scope.error=data;
});
}]);
TheprecedinguseoftheproductServiceshowsyouthatbecausean$httppromiseisreturned,youcanusethesuccessanderrorfunctionstodefinewhatneedstooccurafter.Nowthatthereisacontrollerandaservice,thenextsectionwillshowyouhowtotestthecomponents.
Testing$httpwithKarmaTheKarmatestwilllooktoconfirmthebehaviorofproductServiceifthe$httpcallissuccessfulandisonetolookatifanerroroccurs.Themaindifferencebetweenthistestandothersthathavebeenlookedatsofaristhatyouarecreatingarequesttosomething
www.it-ebooks.info
outsideofAngularJS.Thisisaperfectcaseofusemocking.Youcansetupafakeobjectaround$httptotestthesuccessanderrorpathsoftherequest.AngularJSprovidesamockobjectthatcanbeused,whichisAngularmock’s$httpBackend.
Herearethestepstocreateapositivetest—whentherequestissuccessful:
1. Startwiththeteststub:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
2. Initializethemodule:
beforeEach(function(){
module('anyModule');
});
3. Inject$httpBackendandproductServiceinthebeforeEachfunction:
var$httpBackend=null;
varproductService=null;
beforeEach(function(){
module('anyModule');
inject(function(_$httpBackend_,_productService_){
$httpBackend=_$httpBackend_;
productService=_productService_;
});
});
4. MocktheGETsuccessfulrequestwithanHTTPstatuscodeof200asfollows:
it('',function(){
$httpBackend.when('GET','/product/search').respond(200,'');
});
5. Settheexpectationasfollows:
it('',function(){
…
$httpBackend.expectGET('/product/search');
});
6. MakethecalltoproductServiceusingthefollowingcode:
productService.search('any');
7. Flushtherequestusingthefollowingcode:
$httpBackend.flush();
Asyoucansee,$httpBackendallowsexpectationsandmockresponsestobecontrolled.Totieuplooseends,herearetheadditionalexpectationsforafailedrequest.Followthestepstoaddexpectationsforafailedrequest:
www.it-ebooks.info
1. Addtheexpectationstubtoanasynchronousparameter:
it('',function(done){
});
2. MocktheGETunsuccessfulrequestwithanHTTPstatuscodeof500:
$httpBackend.when('GET','/product/search').respond(500,'');
3. CallproductService.Search:
productService.search('any');
4. Confirmthattheerrorfunctiongetscalled:
productService.search('any').error(function(){
expect(true).toBe(true);
done();
});
5. Flushtherequest:
$httpBackend.flush();
Wehavenotaddedanyotherlayerstotheapplicationandareabletoconfirmhowitwillworkduringasuccessfulandunsuccessfulrequest.Inthenextsection,youwillseehowtotestHTTPrequestsinProtractor.
www.it-ebooks.info
MockingrequestswithProtractorNowthatunittestsforthebackendarecomplete,youcanmovetothefrontendandtestanHTTPrequestthroughProtractor.Youmightnotalwayswanttodothis.Protractorissupposedtotestyoursitefromanend-to-endperspective.Thismeansthatalllayersoftheapplicationwillbetouched.Onebenefitofthefollowingexampleisthatitwillhelpincaseswhereyouhaven’tsetupthebackendrestservice.Youcanbeginbylayingoutthepageandinteractionsbeforethebackendiscomplete.Thiscanhelpwhenyou’rejustputtingyoursitetogether.
InordertomockthebackendHTTPlayerforProtractor,wewilluse$httpBackend,whichispartofthengMockE2EmoduleandisusedtomockthebackendHTTPlayerforProtractor.The$httpBackendpropertyusedforProtractorisdifferentfromtheoneusedinthepreviousKarmatest.Touseend-to-end$httpBackendyouwillneedtoinjectngMockE2Easadependencyintotheapplication.Forthisreason,itisnotaviablesolutiontohaveinaproductionsite.
Herearethestepsthataretobemockedusing$httpBackendinProtractor:
1. AddAngularJSandAngularmockstothewebpage:
<scriptsrc="bower_components/angular/angular.js"></script>
<scriptsrc="bower_components/angular-mocks/angular-mocks.js"></script>
2. CreateamoduleandrequirengMockE2E:
angular.module('anyModule',['ngMockE2E'])
3. Addarunfunctionthatuses$httpBackend:
.run(['$httpBackend',function($httpBackend){
4. Createthemockdata:
.run(['$httpBackend',function($httpBackend){
varproducts=[{id:'id1',name:'product1'},{id:
'id2',name:'product2'}];
}]);
5. Setthemockdatarequest:
.run(['$httpBackend',function($httpBackend){
varproducts=[{id:'id1',name:'product1'},{id:
'id2',name:'product2'}];
$httpBackend.whenGET('/product/search').respond(products);
}]);
Nowtherequestto/product/searchwillrespondwiththeproductsdefinedinthemock.Thismeansthattheapplicationwillworkwithouttheneedforabackendserviceandwillbeabletobetestedasanapplicationwithabackendservice.Acompleteexampleusingamockedbackendwillbeshowninthewalk-through.
www.it-ebooks.info
DisplayingproductswithRESTAllthecorecomponentsofREST,asynchronoustesting,andmockingHTTPrequestshavebeendiscussed.Now,thefollowingwalk-throughwillprovideafullexamplethatwilllookatdisplayingproductsthatareretrievedthroughanexternalservice.Theexamplewillignorethecreationofanexternalserviceandfocusonthedataitprovides:alistofproductsinaJSONformat.Thewalk-throughwilltakeabottom-upapproachsothatthecoredatalayerisworkedoutbeforeaddingtheUIelements.
www.it-ebooks.info
UnittestingproductrequestsTheapproachfromtheunitlevelistocreateaservicetomanagetheHTTPrequestsforproducts.Thecontrollerwillthenbebuiltupthesameway.
SettinguptheprojectBeforewritingtests,theprojectneedstohaveastructure.Hereiswhattheinitialprojectstructurelookslike:
KarmaconfigurationNowthattheprojecttemplatehasbeensetup,acoupleofadjustmentsneedtobemade.TheKarmaconfigurationneedstouseaheadlessbrowserandsetupthetestfilestothecorrectlocation.Openupkarma.conf.jsandmakethefollowingchanges:
1. UpdatethebrowserssectiontoPhantomJSforheadlessbrowsertesting:
browsers:['PhantomJS'],
2. Updatethefilessectiontoincludetheunittestfolders:
files:[
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'app/**/*.js',
'spec/unit/**/*.js'
],
Karmahasbeenconfiguredandtheprojecttemplatehasbeencreated.ThenextstepistosetupanAPIbuilderfortheproductdata.Thiswillallowforaconsistentinterfacetobeusedinatestwheremockingdataisrequired.
UsinganAPIbuilderpatternAbuilderisanobjectthatisusedtocreateanotherobject;itwillbeusedtocreatetestdata.AnAPIbuildercanreduceduplicationandthetimetakentocreatetests.Itprovidesacentralwaytohandlemethodsandcreatedata.Ifabuilderisnotused,theneverytestwrittenwillhavetohaveaseparatedistinctwayofcreatingdata.Thisisanespeciallybad
www.it-ebooks.info
designwhentheAPIbeingusedchanges!
TheproductdataAPIisdefinedbyasingleroute/products.Theexpectedresponseisalistofproducts.HerearethestepstocreateabuilderfortheproductAPI:
1. CreateanewfileinthespecfoldernamedproductDataBuilder.js:
$touchproductDataBuilder.js
2. CreateanewfunctionnamedproductDataBuilder:
module.exports=functionproductDataBuilder(){};
3. ReturnanobjectwithmethodstosetIDs,names,andtoactuallybuildanobject:
module.exports=functionproductDataBuilder(){
return{
withId:function(id){
},
withName:function(name){
},
build:function(){
}
};
};
4. Initializeabasicproduct:
module.exports=functionproductDataBuilder(){
return{
_mockProduct:{id:1,name:'productName'},
withId:function(id){
},
withName:function(name){
},
build:function(){
}
};
};
5. Havethesettercommandsupdatethemockproduct:
return{
...
withId:function(id){
this._mockProduct.id=id;
returnthis;
},
withName:function(name){
this._mockProduct.name=name;
returnthis;
},
};
6. Havethebuildmethodreturnthemockdata:
return{
build:function(){
www.it-ebooks.info
returnthis._mockProduct;
}
};
Thebuilderallowsyoutouseafluentinterfacetocreateproducts.Thesimplestuseisasfollows:
varproductDataBuilder=require('../productDataBuilder');
varsomeProduct=productDataBuilder.build();
AmorecomplicatedusewillbetosettheIDandnametosomethingsuchasthefollowing:
varproductDataBuilder=require('../productDataBuilder');
varsomeProduct=productDataBuilder.withId(9999)
.withName('Product9999');
TheprecedingproductDataBuilderobjectwillbeusedintheKarmatest.
www.it-ebooks.info
TheproductdataserviceIt’stimetogettotheactualtest.ThesameTDDlifecyclethathasbeenusedthroughoutthebookwillbeused;testfirst,makeitrun,andmakeitbetter.AsthecreationandtestingofaservicethatusesHTTPhasalreadybeendiscussed,thiswalk-throughwillbeskipped.Forreference,thetestsareinthecoderepositoryandtheserviceisdefinedasfollows:
angular.module('product')
.service('productService',['$http',function($http){
return{
getAll:function(){
return$http.get('/products')
}
};
}]);
Withtheservicecomplete,thenextstepistolookatthecontrollerandhowtoactuallymakeuseoftheHTTPdata.
www.it-ebooks.info
TheproductdatacontrollerThenextcomponentneededisacontrollersothattheUIcanuseproductService.Thecontrollerneedstohaveonemethodtomaketherequestforproducts.Inthemethod,itneedstoset$resultwhentherequestissuccessfuland$errorwhentherequestisunsuccessful.
AssemblingtheproductcontrollertestHerearethestepstoassembletheproductcontroller:
1. Createanewtestfilefortheproductcontrollerspec/productController.js:
$touchspec/productController.js
2. Usethestandardteststub:
describe('',function(){
beforeEach(function(){
});
it(function(){
});
});
3. Createvariablesforscopeand$httpBackend:
varscope={};
var$httpBackend=null;
4. Initializetheproductmodule:
beforeEach(function(){
module('product');
});
5. Getthe$controllerand$httpBackend:
beforeEach(function(){
inject(function($controller,_$htttpBackend_){
});
});
6. Set$httpBackendtotheinjectedvariable:
inject(function($controller,_$httpBackend_){
$httpBackend=_$httpBackend_;
7. Initializethecontrollerscope:
inject(function($controller,_$httpBackend_){
$httpBackend=_$httpBackend_;
$controller('ProductController',{$scope:scope});
Gettingproducts
www.it-ebooks.info
Theobjectundertestisthecontroller’sscopegetAllmethod.HerearethestepstocallthemethodforasuccessfulHTTPresponse:
1. ForasuccessfulHTTPresponse,usethebuildertobuildatestproduct:
it('',function(){
vartestProduct=productDataBuilder().build();
});
2. MocktheHTTPrequestresponsetoreturntestProduct:
$httpBackend.when('GET','/products').respond(200,[testProduct]);
3. Calltheobjectundertest:
scope.getAll()
Now,theunsuccessfulHTTPresponserequiresanerrorresponse.HerearethestepsfortheunsuccessfulHTTPrequest:
1. MocktheHTTPrequestresponsetoreturntestProduct:
it('',function(){
$httpBackend.when('GET','/products').respond(200,[testProduct]);
});
2. Calltheobjectundertest:
scope.getAll()
TheHTTPresponsehasbeencovered,andthenextstepwillasserttheexpectation.
AssertingproductdataresultsAnassertioncanbeusedtorequirethatanHTTPrequestreceivesaresponse.Themocked$httpBackendpropertycancalltheflush()methodtoexecutetheHTTPresponsesynchronously,soyoudon’thavetoworryaboutasynchronousissues.HerearethestepsforthesuccessfulHTTPresponseexpectation:
1. Flushtherequest:
$httpBackend.flush();
2. ExpecttheresultvariableonthescopeobjecttohavetestProductData:
expect(scope.results[0]).toEqual(testProductData);
HerearetheassertstepsfortheunsuccessfulHTTPresponseexpectation:
1. FlushtheHTTPrequestusingthefollowingcode:
$httpBackend.flush()
2. Confirmthatthescopes’errorvaluehasbeenset:
www.it-ebooks.info
expect(scope.error).toEqual('error');
Nowthatthetestshavebeenassembled,thenextstepistomakethemrun.
www.it-ebooks.info
MakingtheproductdatatestsrunHerearethestepstogetthecontrollertestrunning:
1. RunKarma:
$karmastart
2. ThefirsterrorisError:[ng:areq]Argument'ProductController'isnotafunction,gotundefined.Torectifythis,performthefollowingsteps:
1. ThiserrormeansthatProductControllerdoesn’texist.Createacontrollerstubinapp/productController.js:
angular.module('product')
.controller('ProductController',['$scope',function($scope){
}]);
2. Rerunthetest.
3. ThisnexterrorisTypeError:'undefined'isnotafunction(evaluating'scope.getAll()').Torectifythis,performthefollowingsteps:
1. ThiserrormeansthatthereisnofunctioncalledgetAllinthecontroller.Addthefunctionnow:
.controller('ProductController',['$scope',function($scope){
$scope.getAll()=function(){
};
}]);
2. Rerunthetest.
4. ThenexterrorisError:Nopendingrequesttoflush!.Torectifythiserror,performthefollowingsteps:
1. ThiserroroccursbecausethetestisexpectinganHTTPrequesttobeflushedbutthereisnorequest.AddproductServicetocontrollersothattherequestwillgetmade.AddproductServiceasadependency:
.controller('ProductController',
['$scope','productService',function($scope,productService){
2. AddproductServicetothegetAllfunction:
scope.getAll=function(){
productService.getAll();
};
3. Rerunthetest.
5. ThenexterrorisExpectedundefinedtoequal{id:1,name:'productName'}.Torectifythiserror,performthefollowingsteps:
www.it-ebooks.info
1. Thiserroroccursbecausescope.resultshasnotbeensetwhentheproductservicewassuccessful.AddasuccessfulcallbacktoproductServiceandsetthescope’sresultsvariable:
productService.getAll()
.success(function(data){
$scope.results=data;
});
6. Nowwe’redowntoonefailure,whichisExpectedundefinedtoequal.Torectifythis,performthefollowingstep:
1. Thiserroroccursbecausewehaven’thandledtheerrorconditionoftheHTTPrequest.AddtheerrorconditionofproductServicesothatitsetsthescope’serror:
productService.getAll()
.success(function(data){
$scope.results=data;
})
.error(function(error){
$scope.error=error;
})
7. Confirmthatallthetestspassnow.
Theunittestsfortheproductcontrollerhavebeencompletedusingamockedbackendtotestbothpositiveandnegativescenarios.Thenextstepcanbeskipped,astherewerenocalloutsduringdevelopment.
Thenextsectionwilllookathowtotestfromanend-to-endperspective.
www.it-ebooks.info
Testingmiddle-to-endNowthattheunitleveltestingoftheapplicationiscomplete,theuserfacingtestscanbeworkedon.OneofthebenefitsofAngularmocksisthatitprovides$httpBackend,whichcanbeusedtomockdataforend-to-endtests.Asdataisbeingmocked,itisreallyamiddle-to-endtest.ThisisbecauseonlytheUIinteractionsarebeingtested,astherestofthebehaviorhasbeenmocked.ThiswillallowustocreatescaffoldingfortheUIlayer.Oncethedevelopmentiscomplete,thescaffoldingcanberemovedandafullend-to-endtestcanbeputinplace.
HerearetheinitialsetupstepstocreatetheapplicationUIusingamockedbackendwithProtractor:
1. InstallProtractor:
$npminstallprotractor
2. UpdateWebDriver:
$./node_modules/protractor/bin/webdriver-managerupdate
3. Copytheexample’sChrome-onlyconfiguration:
$cp./node_modules/protractor/example/chromeOnlyConf.js.
4. OpenupthechromOnlyConf.jsandupdatethedrivertopointtothenode_modulesdirectory:
chromeDriver:'./node_modules/protractor/selenium/chromedriver',
5. UpdatethebaseURLvariable:
baseUrl:'http://localhost:8080/',
6. Updatethetestdirectory:
specs:['spec/e2e/**/*.js'],
7. AddngMockE2easadependencytotheproductmoduleintheapporproduct.jsfile:
angular.module('product',['ngMockE2e'])
8. Setupthemockrequest:
.run(['$httpBackend',function($httpBackend){
vartestProduct=productDataBuilder().build();
varproducts=[testProduct];
$httpBackend.whenGET('/products').respond(products);
}]);
9. Createtheindex.htmlpageusinganHTMLstub:
<!DOCTYPEhtml>
<html>
<head>
www.it-ebooks.info
<title></title>
</head>
<body>
</body>
</html>
10. AddtheAngularJSreferences:
<scriptsrc="bower_components/angular/angular.js"></script>
</body>
11. Addtheproductmodule,controller,andservice:
<scriptsrc="app/product.js"></script>
<scriptsrc="app/productService.js"></script>
<scriptsrc="app/productController.js"></script>
12. Formockingpurposes,addAngularmocksandtheproductdatabuilder:
<scriptsrc="bower_components/angular-mocks/angular-mocks.js"></script>
<scriptsrc="spec/productDataBuilder.js"></script>
Theinitial’sindexpageandmockhasbeensetup.ThenextstepwillwalkthroughtheTDDlifecycleandgettheapplicationrocking.
www.it-ebooks.info
TestfirstThefirststepinthelifecycleistocreatethetestsusingthe3A’s.Thetestconfirmsthattheproductdatawillbevisibleonthepageonceauserpushesabuttontogettheproductdata.
AssemblingtheproducttestHerearethestepstoassembletheProtractortest:
1. Createanewfileforthetestcalledspec/e2e/productScenario.js:
$touchproductScenario.js
2. Createtheteststub:
describe('',function(){
beforeEach(function(){
});
it('',function(){
});
});
3. Browsetheapplication:
beforeEach(function(){
browser.get('/index.html');
});
4. Findthebuttonthatwewillbeselecting:
beforeEach(function(){
varproductButton=element(by.buttonText('GetProducts'));
});
Nowthatthetesthasbeenassembled,wecanhittheobjectundertest.
GettingproductsTheactionofthistestistoselecttheproductbutton.AswehavealreadyretrievedthebuttonintheAssemblesection,wecannowclickonit:
beforeEach(function(){
varproductButton=element(by.buttonText('GetProducts'));
productButton.click();
});
Finally,itistimetocreatetheassertionsandexpectations.
ExpectingproductdataresultsTheassertionforthistestistoensurethattheproductdataisnowdisplayed.Herearethesteps:
1. Findtheresults:
www.it-ebooks.info
varresults=element.all(by.repeater('resultinresults'));
2. Assertthatthecountisgreaterthan0:
expect(results.count()).toBeGreaterThan(0);
Thetestsetupiscomplete.Thenextstepistomakeitrun.
www.it-ebooks.info
MakingtheproductdatarunAshasbeendonewiththeotherProtractortests,oneprocesswillberunningtheHTTPpageandtheotherwillberunningtheprotractortest:
1. Installhttp-serversothatwecanrunthewebsite:
$npminstallhttp-server
2. Startthewebsite:
$./node_modules/http-server/bin/http-server.
3. Inanothercommandwindow,runtheprotractortests:
$./node_modules/protractor/bin/protractorchromeOnlyConf.js
4. ThefirsterrorisError:Angularcouldnotbefoundonthepagehttp://localhost:8080/index.html:angularneverprovided
resumeBootstrap.Torectifythis,performthefollowingsteps:
1. Theprecedingerrorisduetothefactthatwehaven’treferencedtheapplicationmoduleinthewebpage.Addtheproductmoduletothebodyoftheapplication:
<bodyng-app='product'>
2. Rerunthetests.
5. ThenexterrorisNoSuchElementError:Noelementfoundusinglocator:by.buttonText("GetProducts").Torectifythis,performthefollowingstep:
1. Addthebutton:
<button>GetProducts</button>
6. ThenexterrorhashittheexpectationandstatesExpected0tobegreaterthan0.Tofixthis,weneedtofirstaddproductControllertothepage:
<divng-controller='ProductController'>
<button>GetProducts</button>
</div>
7. Thenextstepistoassociatethebutton-clickwiththeProductControllerclassesscopetogetallproducts:
<buttonng-click='getAll()'>GetProducts</button>
8. Thefinalstepistodisplayallresults:
<divng-repeat="resultinresults">
{{result}}
</div>
Thetestnowshowsasuccessfulresult.
www.it-ebooks.info
Themakeitbetterstepwillbeskippedasthereisnothingimmediatethatneedstoberefactored.Atthispoint,theapplicationistestedandoperatedusingthemockeddata.Youshouldbeabletoseehowpowerfulthistechniquecanbeasyou’rebuildingupanapplication.Thenextsectionwilllookatremovingthescaffoldingandusinganactualbackend.
www.it-ebooks.info
Testingend-to-endRemovetheAngularmocksscaffoldingandsetupthetesttoactuallyconnecttotherealserverandsetup.
ThebackendofAngularmocksallowedustocreatetheapplicationwithouttheneedtoactuallyreturndata.Nowthattheapplicationhasbeensetup,wecanremovethescaffoldingandcreatearealHTTPrequestforthedata.Herearethesteps:
1. RemovengMockE2eandthemockresponsefromtheproductsmoduleinapp/product.js:
angular.module('product',[]);
RemoveAngularmocksandproductDataBuilderfromtheindex.htmlpage
2. ReruntheProtractortest.3. Theerrorstatesthefailedexpectation.
NowthatthemockHTTPresponsehasbeenremoved,weneedtoaddanactualrequest.Luckilyforus,wedon’thavetouseanyothertoolorframeworkandcanusethehttp-servermodulethatwehavebeenusingthewholetime.Inareal-worldexample,theproductroutewouldliveinaseparateservice,butthisexamplewilluseasimplerapproachforbrevity.
www.it-ebooks.info
GettingtheproductdataThehttp-servermodule,whichisusedtoservestaticcontent,canbeextendedtoservestaticcontentaswell.Thisallowsustosetupastaticfilethatmirrorsarequestroute.Inthiscase,asingleJSONfileofproductswillbeused.Theproductsfilewillhaveanarrayofproductdata.Herearethesteps:
1. Createanewfilenamedproductsintherootoftheproject:
$touchproducts
2. Openthefileandaddthefollowingcontent:
[{
"id":1,
"name":"productName"
}]
Now,the/productsrouteisavailableandwillreturnanarrayofproducts.ReruntheProtractortest,andconfirmthatitispassing.Withthesesimpletests,wehavetestedtheapplicationend-to-endandsuccessfullyremovedthemockscaffolding.
Thisconcludesthewalk-throughofusingTDDtocreateanAngularJSRESTlayer.
www.it-ebooks.info
Self-testquestionsQ1.Acallbackfunctionreferstoafunctionthatiscalledafteranasynchronousfunctioncompletes.
1. True2. False
Q2.AnXMLHttpRequestcannotsendorreceiveJSON.
1. True2. False
Q3.RESTstandsfor:
1. RepresentationalStateTransfer2. Nothing3. RepeatableEndpointStateTransfer
Q4.Asynchronousfunctionsalwayscompleteintheorderinwhichtheywerecalled.
1. True2. False
Q5.Therearetwodifferentimplementationsof$httpBackend:oneforunitandoneforend-to-endtesting.
1. True2. False
www.it-ebooks.info
SummaryThischapterexplainedthedetailsbehindRESTrequests,asynchronoustesting,andthemockingofAngularHTTPrequestsinKarmaandProtractor.Ithasbroughttogethermanyofthetechniquesandtoolsusedthroughoutthebook.Specifically,ithasshowedushowtoapplytheTDDlifecycle(testfirst,makeitrun,andmakeitbetter)toincrementallybuildyourapplicationstoaspecificationandhowtousethe3A’s(Assemble,Act,andAssert)toconstructatest.
Asyoucompletethisbookandgoaboutapplyingthetechniquesintherealworld,rememberthatknowingwhattotestisjustasimportantasknowinghowtotest.Thisbookhasshownyouhow;itisuptoyoutopracticeandcontinuetoimproveyourdevelopmentskillsthroughTDD.
www.it-ebooks.info
AppendixA.IntegratingSeleniumServerwithProtractorThroughoutthisbook,weusedSeleniumChromeDrivertotestwithProtractor.WhatthismeantwasthatinordertorunaProtractortest,wesimplyhadtohavethewebsiterunningandthenkickoffProtractor.InChapter3,End-to-endTestingwithProtractor,ChromeDriverwasinstalledandusedtorunthetests.FromtheperspectiveofthebookandTDD,thiswasacceptable.Ourtestsweresmallandcontainedanddidnothavealotofmovingparts.
TheproblemwithonlyusingChromeDriveristhatwecan’ttestonotherbrowsers.Asyourapplicationgrowsandyouwanttosupportmorebrowsers,youneedtothinkaboutrunningastandaloneSeleniumServer.Thissectionofthebookprovidesawalk-throughofhowtogetastandaloneSeleniumServerrunningandintegratedwithProtractor.
www.it-ebooks.info
InstallationThegoodthingaboutinstallationisthatwehavealreadydoneitbefore.EverytimeweinstalledChromeDriver,thefirstthingwedidwasinstallSelenium.Herearethestandardsteps:
1. InstalltheProtractornpmmodule:
$npminstallprotractor
2. InstallSeleniumWebDriver:
$./node_modules/protractor/bin/webdriver-managerupdate
That’sit.Seleniumisnowinstalledandisreadytobeused.Inthenextsection,wewillseehowtoupdatetheProtractorconfigurationtousetheSeleniumstandaloneserver.
www.it-ebooks.info
ProtractorconfigurationLuckilyforus,wedon’thavetorememberallthebasicconfigurationsforProtractor.Withinnpm_modules,thereareexamplesthatwecanuse.HerearethestepstocopytheSeleniumstandaloneconfiguration:
1. OpenuptheexampleProtractorconfigurationfilethatislocatedinthefollowingdirectory:
./node_modules/protractor/example/conf.js
2. Copythefiletoyourlocaltestfolder:
$cp./node_modules/protractor/example/conf.js
TheconfigurationshouldlookverysimilartothechromeOnlyconfiguration.Hereisasnippetoftheimportantconfigurationitems:
exports.config={
seleniumAddress:'http://localhost:4444/wd/hub',
capabilities:{
'browserName':'chrome'
},
…
};
ThefirstimportantitemistheseleniumAddressobject.Theaddressisthehostname,port,andlocationwheretheSeleniumServerisrunning.Thenextimportantitemisthecapabilitiesobject.Browser-specificcapabilitiesgiveyoutheabilitytodefinewhichbrowserswillbetestedagainst.AswearenotusingtheChromeOnlyconfiguration,youcannowchooseInternetExplorer(IE),Firefox,andsoon.Formoreinformationonmultiplebrowsersupportandcapabilities,refertotheProtractordocumentationathttps://github.com/angular/protractor/blob/master/docs/browser-setup.md
Inthenextsection,wewilllookathowtorunSelenium.
TipTheseleniumAddressobjectismeanttobeconfigurablesothatyoucanhaveaseparateinstanceinacompletelydifferentlocationthanyourdevelopmentmachine.VisittheSeleniumsiteformoreinformationathttp://www.seleniumhq.org/.
www.it-ebooks.info
RunningSeleniumSeleniumisquitestraightforwardtostart.Oncerun,itcanjustsitinthebackgroundwhilethetestsarerunning:
1. StarttheSeleniumstandaloneservice:
$./node_modules/protractor/bin/webdriver-managerstart
2. Theconsolewindowwilldisplayseveralinformationmessages.Ensurethefollowingmessagesaredisplayed:
3. Youshouldensurethatthedefaultportused,ascanbeseenintheRemoteWebDrivermessageintheprecedingmessages,isthesameastheonethatisconfiguredintheProtractorconfiguration:
seleniumAddress:'http://localhost:4444/wd/hub',
…
www.it-ebooks.info
LetitrunSeleniumisnowrunningonthe4444localhostport.InordertoensurethatProtractorcancommunicatewithSelenium,let’srunasimpletesttoensureeverythingisworking.Aswehavedonethroughoutthebook,wewillfollowtheTDDstepseventhoughthiswillbeanextremelyshortandsimpletest.AsProtractorisinstalled,theonlyotherprerequisiteistoinstallanHTTPserver.Installhttp-serverusingthefollowingcommand:
$npminstallhttp-server
Onceitisinstalled,starttheserver:
$./node_modules/http-server/bin/http-server
www.it-ebooks.info
TestfirstThetestwillcheckwhetherthetitleofthepageisequaltoseleniumTestTitle.CreateanewProtractortestfilenamedscenario.js.
AssembleTosetupthetest,weneedtonavigatethebrowsertotherootofthewebapplication:
beforeEach(function(){
browser.get("/");
});
ThereisnoActsectionaswewillsimplybecheckingthattheloadedindexpagehasthetitleweneed.
AssertTheassertneedsgetthetitleandcompareitwiththeexpectedvalue:
it('',function(){
expect(browser.getTitle()).toBe('seleniumTestTitle');
});
www.it-ebooks.info
MakeitrunNowthatthetestisprepared,wecanstartrunningtheProtractortestthroughthestandaloneSeleniumServer.HerearethestepstoruntheProtractortest:
1. AddthetestfiletotheProtractorconfiguration:
specs:['scenario.js'],
2. CreateanemptyHTMLpagethatwillbeusedtomakethetestrun:
<!DOCTYPEhtml>
<html>
<head>
<title></title>
</head>
<body>
</body>
</html>
3. AddtheindexpagetotheProtractorconfiguration:
specs:['scenario.js','index.html'],
4. Runthetest:
$./node-modules/protractor/bin/protractorconf.js
5. ThefirsterrorisAngularcouldnotbefoundonthepagehttp://localhost:8080/index.html:retrieslookingforangularexceeded.Torectifythis,performthefollowingsteps:
1. AngularJShasnotbeenaddedtothepage.Installangularthroughbower:
$bowerinstallangular
2. AddtheAngularJSreferencetotheindex.htmlpage:
<scripttype="text/javascript"
src="bower_components/angular/angular.js"></script>
3. Rerunthetest.
6. ThenexterrorisAngularcouldnotbefoundonthepagehttp://localhost:8080/index.html:angularneverprovidedresumeBootstrap.ThiserrormeansthatAngularJScouldn’tloadthemainmoduleofyourapplication.Torectifythis,performthefollowingsteps:
1. Addasimplemoduleintothebodytag:
<bodyng-app='test'>
2. Initializethemoduleinthelasttag:
www.it-ebooks.info
<scripttype="text/javascript"
src="bower_components/angular/angular.js"></script>
<scripttype="text/javascript">
angular.module('test',[]);
</script>
3. Rerunthetest.
7. Thenexterrorhashittheexpectation:Expected‘http://localhost:8080/index.html’tobe‘seleniumTestTitle’.Herearethestepstorectifythiserror:
1. Setthetitleofthewebpagetotheexpectation:
<title>seleniumTestTitle</title>
2. Rerunthetest.
8. TheProtractoroutputnowreports1test,1assertion,0failures.Withthesuccessofthetest,wehavenowsuccessfullyshownyouhowtousetheSeleniumstandaloneserver.
www.it-ebooks.info
SummaryThisappendixhasshownyouhowtosetupandusetheSeleniumstandaloneserver.Therearemanyoptionsandadvantagesofusingthestandaloneserver.TheadvantagesaregearedmoreforadvancedtestingwhenyouwanttouseadedicatedSeleniumServeroraPaaS(PlatformasaService)orifyouwanttotestafunctionalityondifferentbrowsersandasthevolumeofyourProtractortestsgrow.Formoreinformation,visittheSeleniumhomepageathttp://www.seleniumhq.org/.
www.it-ebooks.info
AppendixB.AutomatingKarmaUnitTestingonCommitRunningtestslocallyisonething,buthowdoyouknowwhethertheywillworkonsomeoneelse’scomputer.Settingupcontinuoustestingandintegrationshouldbepartofeveryapplicationyouwrite.Oneofthebestthingsisthatthetoolstosetuparefree,easytouse,andbestofall,theygettoshowcaseyourtests!ThefollowingsectionwillexplorehowtosetupcontinuousintegrationusingGitHubforsourcecontrolandTravisforcontinuousintegration.
www.it-ebooks.info
GitHubGitHubisasourcecontrol,collaboration,andall-aroundawesometool.Foropensourceprojects,itisfree.Onceyousignup,youcangetstartedandcreateanewrepositoryforyourproject.GitHubprovidesaGitURLforeveryproject;theURLcanthenbesetuptopushchangeslikeanyotherGitrepository.OneofthebenefitsofusingGitHubisthatitautomaticallyprovideshooksintootherapplicationsandservices.WhensettingupcontinuousintegrationandtestingthroughTravisCI,youwillleveragetheTravisCIGitHubhook.
www.it-ebooks.info
TestsetupInordertorunKarmaproperly,wewillneedtoaddthefollowingdevelopmentdependencies:
karma:ThebaseKarmainstallationkarma-jasmine:Thetestrunnerkarma-phantomjs-launcher:ThePhantomJSheadlessbrowserpluginwediscussedandsetupinChapter5,FlipFlop
InstallthefollowingKarmadevdependencies:
$npminstallkarma--save-dev
$npminstallkarma-jasmine--save-dev
$npminstallkarma-phantomjs-launcher--save-dev
www.it-ebooks.info
TestscriptsWhenusingTravisCI,ascripttorunthetestsneedstobedefined.Thebestplacetodefineascriptisinthepackage.jsonfile.Thepackage.jsonfileisusedinseveralwaysbynode.js.Herearethestepstorunthetest:
1. Thetestscriptcanthenberunwhenyoutypethefollowingcommandinthecommandprompt:
$npmtest
2. Updatethepackage.jsonscriptssectionasshowninthefollowingcodesnippet:
"scripts":{
"start":"nodeapp.js",
"test":"karmastart--single-run--browsersPhantomJS"
}
3. Confirmthatthetestscriptworks:
$npmtest
PhantomJSallowsteststorunontheTravisCIserverswithouttheneedforaUI.Thefollowingisasampleoutput:
Theapplicationsetupisnowconfiguredtorununittestsviathenpmtestcommand.ThiswillbeusedbyTravisCItorunthetests.
www.it-ebooks.info
SettingthehookGitHubprovidesseveralhooksintootherapplications.Ahookallowsyoutochainactionswhenacommitoccurs.Ahookisanextremelyusefulfeaturefromacontinuousintegrationstandpointbecausewecansetupthecodetobetestedoneverycommit.TravisCIhasaGitHubhookthatcanbeeasilysetuponanyGitHubrepository.Thefollowingisawalk-throughonhowtocreateaTravisCIhookonyouropensourcerepository.
www.it-ebooks.info
CreatingthehookHerearethestepstocreatethehook:
1. CreateaTravisCIaccountbygoingtotheTravisCIpageathttps://travis-ci.organdclickonSigninwithGitHub.Confirmthequestionsitasksandcontinue.
2. ActivateaGitHubWebhooktoTravisCI.YoucansetuptheWebhookinTravisCIthroughyourprofileURLathttps://travis-ci.org/profile
3. Turntheswitchon.Intheprofile,youshouldseeyourrepository.
HereisabeforeviewofWebhook(Switchoff):
HereisaviewoftheWebhookafteritisenabled(Switchon):
www.it-ebooks.info
AddingaTravisconfigurationfileTravisrequiresaconfigurationfiletobeattherootofyourrepositorynamed.travis.yml.Theconfigurationfilecontainsthesourcecodelanguage,languageversioning,metadata,andotherinformation.Thetemplateconfigurationwilllookasfollows:
language:node_js
node_js:
-"0.10"
Besidesthebasicconfigurationintheprecedingcode,additionalsetupisneededtorunKarmatests.Thebefore_scriptconfigurationwillbeusedtoinstallKarmaandBowerpriortorunninganytests.HereiswhattheconfigurationneedstolooklikeinordertoinstallKarmaandBowerbeforeanytestsrun:
language:node_js
node_js:
-"0.10"
before_script:
-npminstall-gkarma-cli
-npminstall-gbower
-bowerinstall
Nowthetestsarereadytoberun.Addtheprecedingcontentstoanewfilenamedtravis.yml.Bydefault,theNode.jsprojectwillexecutethenpmtestcommandinTravis.Thisiswhyyoudon’tneedtospecifytheactualcommandtotestyourapplication.
NotePleasenotethatTravisCIiscasesensitive.
Thefollowingscreenshotisanexampleofwhattheprecedingcodelookslike:
www.it-ebooks.info
Ifyouhaveanyissues,gototheTravisCIGettingstartedguideathttp://docs.travis-ci.com/user/getting-started/.
www.it-ebooks.info
ReferencesThefollowingaresomereferencesthatmayhelpyouwiththeconcepts:
ThisformofuserspecificationiswrittenusingtheGerkinsyntax.TheGerkinsyntaxallowsyoutowritethespecificationsinawell-formattedmanner.Seethefollowinglinkformoredetails:http://en.wikipedia.org/wiki/Behavior-driven_development.TheJavaScriptJabberhomepagecanbefoundathttp://javascriptjabber.com/106-jsj-protractor-with-julie-ralph/TheGitHubpageforhttp-servercanbefoundathttps://github.com/nodeapps/http-server
www.it-ebooks.info
Chapter1,IntroductiontoTest-drivenDevelopmentQ1 2
Q2 1
Q3 1
Q4 1
Q5 2
www.it-ebooks.info
Chapter3,End-to-endTestingwithProtractorQ1 1
Q2 1
Q3 1
www.it-ebooks.info
IndexA
3A’sreferencelink/Testingtechniques
3A’sassemble/Assemble,Act,andAssert(3A’s)act/Assemble,Act,andAssert(3A’s),Assemble,Act,Assert(3A’s)assert/Assemble,Act,andAssert(3A’s),Assemble,Act,Assert(3A’s)assemble/Assemble,Act,Assert(3A’s)
3A’s,applicationtoentercommentsassemble/Assembleact/Actassert/Assert
3A’s,commentaddingspecificationassemble/Assembleact/Actassert/Assert
3A’s,commentlikingspecificationassemble/Assembleact/Actassert/Assert
AngularJSinstalling/InstallingAngularJS
AngularJScomponentsattributes/Servicesdirectives/Servicescontrollers/Servicesservices/Services
AngularJSREST,testingwithabout/TestingwithAngularJSRESTproductservice,testing/Testingtheproductservice$http,testingwithKarma/Testing$httpwithKarma
AngularJSroutesabout/Walk-throughofAngularroutessettingup/SettingupAngularJSroutesdirections,defining/Definingdirectionsflipfloptest,assembling/Assemblingtheflipfloptest
AngularJSservicesabout/Services
AngularMocksinstalling/InstallingAngularmocksURL/InstallingAngularmocks
www.it-ebooks.info
applicationtoentercommentsspecification,preparing/Preparingtheapplication’sspecificationsettingup/Settinguptheprojectdirectory,settingup/SettingupthedirectoryProtractor,installing/SettingupProtractorProtractor,settingup/SettingupProtractorKarma,settingup/SettingupKarmahttp-serversetup/Settinguphttp-serverKarmaconfiguration/ConfiguringKarma
applicationtoentercomments,TDDlifecycleabout/Bringonthecommentstestfirst/Testfirst3A’s/Testfirsttest,running/Makeitrunmodule,adding/Addingthemoduleinput,adding/Addingtheinputcontroller/Controllertest,passing/Makeitpasstest,improving/Makeitbetter
asynchronouscallstesting/Testingasynchronouscallscreating,inKarma/CreatingasynchronouscallsinKarmacreating,inProtractor/CreatingasynchronouscallsinProtractor
asyncmagiccomponents,Protractorabout/Asyncmagicpage,loadingbeforetestexecution/Loadingapagebeforetestexecutionassertiononelements/Assertiononelementsthatgetloadedinpromises
www.it-ebooks.info
BbeforeEachparameter
about/Testfirst,Testfirstbottom-upapproach
about/Top-downorbottom-upapproachusing/Usingabottom-upapproach
Bowerabout/Bowerinstalling/Bowerinstallation
broadcasttesting/Testingbroadcast,Testingbroadcast
builderobjectabout/Buildingwithabuilder
builderpatternabout/Buildingwithabuilder
www.it-ebooks.info
C$controllervariable
about/Assemble,Act,andAssert(3A’s)Chrome
about/InstallationprerequisitesURL/Installationprerequisites
commentlikingspecificationabout/Onwardsandupwardstesting,withProtractortesttemplate/Testfirst3A’s/Testfirsttest,running/Makeitrununittests,fixing/Fixingtheunitteststest,improving/Makeitbettertest,coupling/Couplingofthetest
controllertesting/Testingacontrollersimplecontrollertestsetup/Asimplecontrollertestsetupscope,initializing/Initializingthescope
curltoolabout/REST–thelanguageoftheWeb
www.it-ebooks.info
Ddescribeparameter
about/Testfirst,TestfirstDescribeproperty,Karmatest/TestingwithKarmadirections,AngularJSroutes
ngRoute,configuring/ConfiguringngRouteroutecontrollers,defining/Definingtheroutecontrollersrouteviews,defining/Definingtherouteviews
documentation,TDDabout/FundamentalsofTDD
DocumentObjectModel(DOM)/TDDwithProtractor
www.it-ebooks.info
Eemit
about/Emittingtesting/Testingemit
end-to-endtestingabout/Gettingdowntobusiness,Testingend-to-endspecification,reviewing/Specificationdevelopmentto-dolist/Thedevelopmentto-dolistTDDprocess/Testfirstproductdata,obtaining/Gettingtheproductdata
end-to-endtesting,productcartend-to-endtest,assembling/Assemblingthecart’send-to-endtestsavetocartaction,invoking/Invokingasavetocartactionsavedproducts,confirming/Confirmingproductshavebeensavedend-to-endtest,passing/Makingthecart’send-to-endtestpass
end-to-endtesting,recentlyvieweditemsabout/End-to-endtestingtestfirst/Testfirstrecentlyviewedend-to-endtest,assembling/Assemblingtherecentlyviewedend-to-endtestsearchresult,selecting/Selectingasearchresultrecentlyvieweditems,confirming/ConfirmingrecentlyvieweditemsrecentlyViewedItemstest,passing/MakingtherecentlyViewedItemstestpassrecentlyViewedItemstest,improving/Makingrecentlyvieweditemsbetter
end-to-endtests,Protractortestwebserver,installing/Installingthetestwebserver
events,insearchapplicationimplementing/Harnessingthepowerofeventsplan/Theplanrebranding/Rebrandingrecentlyvieweditems,viewing/Seeingrecentlyvieweditemsproductcart,creating/Creatingaproductcart
Expectproperty,Karmatest/TestingwithKarma
www.it-ebooks.info
Fflipfloptest,AngularJSroutes
viewsflip,creating/Makingtheviewsflipflip,asserting/Assertingafliprunning/Makingflipfloprunimproving/Makingflipflopbetter
FunctionUnderTest/Testingtechniquesfundamentals,searchapplication
Protractorlocators/Protractorlocators
www.it-ebooks.info
H$httpBackendproperty/Testing$httpwithKarmaheadlessbrowsertesting,forKarma
settingup/SettingupheadlessbrowsertestingforKarmapreconfiguration/Preconfigurationconfiguration/Configuration
http-servermodule/Gettingtheproductdataabout/Gettingtheproductdata
HTTPmethodsabout/REST–thelanguageoftheWebGET/REST–thelanguageoftheWebPOST/REST–thelanguageoftheWebPUT/REST–thelanguageoftheWebDELETE/REST–thelanguageoftheWeb
www.it-ebooks.info
Iinjectvariable
about/Assemble,Act,andAssert(3A’s)installation
Karma/InstallingKarmaProtractor/Protractorinstallation
itparameterabout/Testfirst,Testfirst
Itproperty,Karmatest/TestingwithKarma
www.it-ebooks.info
JJasmine
about/Jasminepros/Jasminecons/Jasmine
Jasminespyused,forcreatingtestdouble/TestingdoubleswithJasminespies
JavaScripttestingframeworksabout/JavaScripttestingframeworksJasmine/JasmineSelenium/SeleniumMocha/Mocha
JavaScripttestingtoolsabout/JavaScripttestingtoolsKarma/KarmaProtractor/Protractor
www.it-ebooks.info
KKarma
about/Karmapros/Karmacons/Karmabirth/BirthofKarmafeatures/TheKarmadifferencecombining,withAngularJS/ImportanceofcombiningKarmawithAngularJSinstalling/InstallingKarmaURL/InstallingKarmaprerequisites,forinstallation/Installationprerequisitesconfiguring/ConfiguringKarmaconfiguration,customizing/CustomizingKarma’sconfigurationinstallation,confirming/ConfirmingKarma’sinstallationandconfiguration,ConfirmingtheKarmainstallationconfiguration,confirming/ConfirmingKarma’sinstallationandconfigurationcommoninstallation/configurationissues/Commoninstallation/configurationissuestesting,with/TestingwithKarmainitializing/InitializingKarma
Karma,usingwithAngularJSabout/UsingKarmawithAngularJSAngularJS,obtaining/GettingAngularJStesting,with/TestingwithAngularJSandKarmadevelopmentto-dolist/Adevelopmentto-dolistlistofitems,testing/TestingalistofitemsTDDprocess/Testingalistofitemsfunction,addingtocontroller/Addingafunctiontothecontroller
karma.conffile/InitializingKarmaKarmaconfiguration
about/Karmaconfigurationfilewatching/Filewatching
Karmaconfiguration,applicationtoentercommentstesting/Testfirst3A’s/Testfirsttest,running/Makeitruntest,improving/Makeitbettertestchain,backingup/Backupthetestchaininput,binding/Bindtheinput
Karmadevdependencieskarma/Testsetupkarma-jasmine/Testsetupkarma-phantomjs-launcher/Testsetup
www.it-ebooks.info
installing/TestsetupKarmaunittesting
testsetup/Testsetuptestscripts/Testscriptshook,setting/Settingthehookhook,creating/CreatingthehookTravisconfigurationfile,adding/AddingaTravisconfigurationfile
www.it-ebooks.info
Mmessages
publishing/Publishingandsubscribingmessagessubscribing/Publishingandsubscribingmessages
middle-to-endtestingabout/Testingmiddle-to-endtestfirst/Testfirstproducttest,assembling/Assemblingtheproducttestproducts,obtaining/Gettingproductsproductdataresults,expecting/Expectingproductdataresultsproductdata,running/Makingtheproductdatarun
Mochaabout/Mochapros/Mochacons/Mocha
www.it-ebooks.info
NNode.js
URL/Installationprerequisites,Installationprerequisitesabout/Installationprerequisites
NodePackageManager(npm)modules/Mocha
www.it-ebooks.info
PPhantomJS
URL/SettingupheadlessbrowsertestingforKarmaPhantomJSbrowserplugin
URL/Preconfigurationprerequisites,Protractorinstallation
Node.js/InstallationprerequisitesChrome/InstallationprerequisitesSeleniumWebDriverforChrome/Installationprerequisites
productcartcreating/Creatingaproductcartpublishertestfirst/PublishertestfirstsearchDetailController,assembling/AssemblingsearchDetailControllerproductsaving,invoking/Invokingthesavingofaproductsaveevent,confirming/ConfirmingthesaveeventsaveProducttest,passing/MakingthesaveProducttestpasssubscriberunittest/Testforthesubscriberfirsttest,assembling/Assemblingtheproductcarttestsavedcartevent,invoking/Invokingasavedcarteventsavedcart,confirming/Confirmingthesavedcartcartcontrollertest,running/Makingthecartcontrollertestrunend-to-endtesting/End-to-endtesting
productdatacontrollerabout/Theproductdatacontrollerproductcontrollertest,assembling/Assemblingtheproductcontrollertestproducts,obtaining/Gettingproductsproductdataresults,asserting/Assertingproductdataresults
productdataserviceabout/Theproductdataservice
productrequests,unittestingabout/Unittestingproductrequestsproject,settingup/SettinguptheprojectKarmaconfiguration/KarmaconfigurationAPIbuilderpattern,using/UsinganAPIbuilderpattern
products,displayingwithRESTabout/DisplayingproductswithRESTproductrequests,unittesting/Unittestingproductrequestsproductdataservice/Theproductdataserviceproductdatacontroller/Theproductdatacontrollerproductdatatests,running/Makingtheproductdatatestsrun
Protractorabout/Protractor,AnoverviewofProtractorpros/Protractor
www.it-ebooks.info
cons/Protractoroverview/AnoverviewofProtractororigins/OriginsofProtractorbirth/ThebirthofProtractorfeatures/LifewithoutProtractorURL/Commoninstallation/configurationissuesrealtest/HelloProtractorTDD,using/TDDend-to-endpre-setup/Thepre-setupsetup/Thesetupend-to-endtests/Testfirstconfiguring/ConfiguringProtractorgaps,cleaningup/Cleaningupthegapsasyncmagiccomponents/AsyncmagicTDD,implementingwith/TDDwithProtractor
Protractorinstallationabout/Protractorinstallationreferencelink,forguide/Protractorinstallationprerequisites/Installationprerequisitesperforming/InstallingProtractorWebDriver,installingforChrome/InstallingWebDriverforChromeconfiguration,customizing/Customizingconfigurationconfirming/Confirminginstallationandconfigurationconfiguration,confirming/Confirminginstallationandconfigurationcommonissues/Commoninstallation/configurationissues
Protractorlocatorsabout/ProtractorlocatorsCSSlocators/CSSlocatorsbuttontextlocator/Buttonandlinklocatorslinktextlocator/ButtonandlinklocatorsAngularlocators/AngularlocatorsURLlocationreferences/URLlocationreferences
publishingandsubscribingmessages/Publishingandsubscribingmessagesissues/Publishingandsubscribing–thegoodandbadscenarios/Thegoodcommunicating,throughevents/Communicatingthrougheventscoupling,reducing/Reducingcoupling
www.it-ebooks.info
Rrecentlyvieweditems,viewing
about/Seeingrecentlyvieweditemstestfirst/Testfirstend-to-endtesting/End-to-endtesting
recentlyviewedtestwriting/TestfirstSearchController,assembling/AssemblingSearchControllerproduct,selecting/Selectingaproductevents,tobepublished/Expectingeventstobepublishedsearchcontrollerrun,creating/Makingthesearchcontrollerrununittest/Recentlyviewedunittest
recentlyviewedunittestabout/Recentlyviewedunittestwriting/TestfirstRecentlyViewedController,assembling/AssemblingRecentlyViewedControllerrecentlyvieweditem,invoking/InvokingarecentlyvieweditemRecentlyViewedController,confirming/ConfirmingRecentlyViewedControllerRecentlyViewedController,running/MakingRecentlyViewedControllerrun
refactoring,TDDabout/FundamentalsofTDD,Refactoring
RESTabout/REST–thelanguageoftheWebgettingstartedprocess/GettingstartedwithREST
RESTrequestscreating,AngularJSused/MakingRESTrequestsusingAngularJStesting,withAngularJSREST/TestingwithAngularJSRESTmocking,withProtractor/MockingrequestswithProtractor
www.it-ebooks.info
SSaaS(SoftwareasaService)/LifewithoutProtractorSauceLabs
URL/LifewithoutProtractorScenarioRunner
about/Endoflifescopevariable
about/Assemble,Act,andAssert(3A’s)searchapplication
fundamentals/Fundamentalscreating/Creatinganewprojectheadlessbrowsertesting,settingupforKarma/SettingupheadlessbrowsertestingforKarma
searchapplication,TDDwayabout/SearchingtheTDDway,Thesearchapplicationapproach,decidingon/Decidingontheapproachsearchquery/Walk-throughofsearchquerysearchquerytest/ThesearchquerytestsearchqueryHTMLpage/ThesearchqueryHTMLpage
searchresults,searchapplicationabout/Showmesomeresults!searchresultroutes,creating/Creatingthesearchresultroutestesting/Testingthesearchresultssearchresulttest,assembling/Assemblingthesearchresulttestselecting/Selectingasearchresultconfirming/Confirmingasearchresultsearchresulttest,running/Makingthesearchresulttestruntesting,forlocation/Creatingalocation-awaretestimproving/MakingthesearchresultbetterrouteID,confirming/ConfirmingtherouteIDrouteIDunittest,settingup/SettinguptherouteIDunittestrouteIDunittest,confirming/ConfirmingtheIDrouteparameterstest,running/Makingtherouteparameter’stestrun
SeleniumURL/Seleniumabout/Seleniumpros/Seleniumcons/Seleniuminstalling/InstallationProtractorconfiguration/Protractorconfigurationrunning/RunningSelenium,Letitruntestfirst/Testfirst
SeleniumWebDriver,forChrome
www.it-ebooks.info
about/Installationprerequisitesinstalling/InstallingWebDriverforChrome
success,measuringinTDDsteps,breakingdown/Breakingdownthestepstestfirstmethodology/Measuretwicecutonce
www.it-ebooks.info
TTDD
about/AnoverviewofTDD,TDDend-to-endfundamentals/FundamentalsofTDDbenefits/FundamentalsofTDDsuccess,measuring/Measuringsuccesstestingtechniques/Testingtechniquesapplying/TDDend-to-end
TDDlifecycleabout/Divingintest,settingup/Settingupthetestdevelopmentto-dolist,creating/Creatingadevelopmentto-dolisttestfirst/Testfirsttest,running/Makingitruntest,improving/Makingitbetter
TDDprocess,end-to-endtestingtestfirst/Testfirst3A’s/Assemble,Act,Assert(3A’s)test,running/Makeitruntest,improving/Makeitbetter
TDDprocess,foraddingfunctiontocontrollerabout/Addingafunctiontothecontrollertestfirst/Testfirst3A’s/Assemble,Act,andAssert(3A’s)test,running/Makeitruntest,improving/Makeitbetter
TDDprocess,fortestinglistofitemstestfirst/Testfirst3A’s/Assemble,Act,andAssert(3A’s)test,running/Makeitruntest,improving/Makeitbetter
test,Seleniumassemble/Assembleassert/Assertrunning/Makeitrun
testdoubleabout/TestingdoubleswithJasminespiesusing/TestingdoubleswithJasminespiescreating,Jasminespyused/TestingdoubleswithJasminespiesreturnvalue,stubbing/Stubbingareturnvaluearguments,testing/Testingarguments
testingframeworkabout/Testingwithaframework
www.it-ebooks.info
testingtechniques,TDDabout/Testingtechniquestestingframework/Testingwithaframeworktestdouble/TestingdoubleswithJasminespiestestdouble,usingJasminespy/TestingdoubleswithJasminespiesrefactoring/Refactoringbuilding,withbuilder/Buildingwithabuilder
ToBeTruthyproperty,Karmatest/TestingwithKarmatop-downapproach
about/Top-downorbottom-upapproachTravisCI
configurationfile/AddingaTravisconfigurationfileURL/AddingaTravisconfigurationfile
TravisCIhookcreating/Creatingthehook
www.it-ebooks.info