AngularJS Test-driven Development · 2015. 12. 1. · Simran Bhogal Maria Gould Ameesha Green Paul...

Preview:

Citation preview

www.it-ebooks.info

AngularJSTest-drivenDevelopment

www.it-ebooks.info

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-drivenDevelopment

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

AparnaBhagat

CoverWork

AparnaBhagat

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

www.PacktPub.com

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

multipleTest1();

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

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

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

AppendixC.Answers

www.it-ebooks.info

Chapter1,IntroductiontoTest-drivenDevelopmentQ1 2

Q2 1

Q3 1

Q4 1

Q5 2

www.it-ebooks.info

Chapter2,TheKarmaWayQ1 2

Q2 2

Q3 2

Q4 2

www.it-ebooks.info

Chapter3,End-to-endTestingwithProtractorQ1 1

Q2 1

Q3 1

www.it-ebooks.info

Chapter4,TheFirstStepQ1 1

Q2 2

Q3 1

www.it-ebooks.info

Chapter5,FlipFlopQ1 3

Q2 3

Q3 1

www.it-ebooks.info

Chapter6,TellingtheWorldQ1 2

Q2 1

Q3 1

www.it-ebooks.info

Chapter7,GiveMeSomeDataQ1 1

Q2 2

Q3 1

Q4 2

Q5 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

GGitHub

about/GitHub

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

Recommended