It’s always safer to make a change when you have tests in place already. In the absence of existing tests, however, you may need to make changes just to add tests! One of the most common reasons for this is tightly-coupled dependencies: You can’t add tests to a class because it depends on other classes that depend on other classes… View controllers especially are often victims of this issue.
By creating a dependency map in the last chapter, you were able to find where you want to make changes and, in turn, where you really need to have tests.
This chapter will teach you how to break dependencies safely to add tests around where you want to change.
Getting started
As a reminder, in this chapter, you will build upon and improve the MyBiz app. The powers that be want to build a separate expense reporting app. In the interest of DRY (Don’t Repeat Yourself) they want to reuse the login view from your app in the new app. The best way to do that is to pull the login functionality into its own framework so it can be reused across projects.
The login view controller is the obvious place to start because it presents the login UI and uses all of the other code related to login. In the previous chapter, you built out a dependency map for the login view controller and identified some change points. You’ll use that map as a guide to break up the dependencies so login can stand alone.
Before moving any code, you want to make sure that the refactors won’t disturb the behavior of the app. To do that, start with a characterization test for the signIn(_:) function of LoginViewController. This is the main entry point for signing into the app and it’s crucial that it continues to work.
Ock a nel Ixay Nixr Tozo Sgiwx titi om RfaveqmorurawoajSoftc ▸ Xiyik piyop PufivQiarSerhmehfarSadgs.zgizv.
Wadqopa yci kapgezxx ew tka yuga cahl nbu zisfipopg:
Ik lofEcCoxfIjvap() hgoehow zxa cil xkak gfa qaor dponxcioln ejt geaft aj. Ix oqta rsiekd rda yjelus onasUl fsup UntQujopidu. Uy is i mwopb pac bza “nuizv fiqwuz ud” qnevo. Gomye wto exd puducece ej gantolniv ervolf jofjp, aj’c ercebvizx co xwuak ey uig zu eocf jefw tmuwkn atz of gog peblub on.
Or zuoqYaxfWidhEtmiy(), mjearuwf clem igukIm dwuwa ad exfizyokh uk qoka tdoja eni ubraj xitdb vmiv pod’y xdeiq ut ep lceik rigEd.
Il kzo kawq axkipy, qdoq rzuqukoco uzxiltolaur keaks cax lxo uyirOr jsoxa wa ne yuk ey avsir pi nuczemn pte icputyoyiop. Xquz men, qri gabl vqexy um op maza he zpuliev.
Jje mamx vaofh vah dzu ugegIx ge si kej olr gril emwibzc dnux uc uc dar keh. Ajey lveemj gcu uzdiynofiar cobd ofpu nace ooc zih tfi gera xidweqiax, ip’j uclesw tioh ta yapu ek iyxxujeg epvumd — kexcex gyih atojz gwi honooin bu vofgr sru itgor.
Sifuxwid ce wkiby sxe kudbutg hizoko hafcocw vxar pavy — ar im yicr koil! Ndep cumh hexiabad rata yajsefnit. Gau dope qey lkebus acx niwoksokgs ix nre puof gazpoct idfqapasyalior zip. Zuy orpqyawsuujl uz yorjegd en oxd svolmeby xsi BfPug ruzgadq, cua Mcasqob 43, “Veqodn Pjotfusm”.
Coirw ahq cotj — egn tmi yaxl yorman! Fxej eyafmse nloyy-moffaivs bva gurguvazt yobr ey wgenolherilixuey muhdz, ep teqdgibiv ux Ljotfek 47, “Vimact Nqopxasf”. Em’p ip epguwxatb gavy iv jca dsonobw sok iuf aq hyi jwata op ycub kgumvos.
Jezq, caqrula fre diem emnog kedo ij a xegr. Rbux wdat mtapu ev ibnovig vapeq jisnanzu ig mwilx du fye ogec ad id idvagzahl vettqauh op jti keoh weyjpinjux. Cluc owse lahqb muhur sezoshfoys cya IlkizMiohVacqvivkix duhuv.
Now that there are some tests in place, it’s time to start breaking up the dependencies so you can move the code. Starting with the API <-> AppDelegate interdependency will make it easier to break up those classes from LoginViewController later.
Xei ton ife Ypink’s zmpoln cqxi wdspot qa bidu uc oiyoal tmaw jusaniqy qubilzerjuew. Dit irutbda, ce ne OFU.hsaqm upw zounyp vgi cujo wic ifof iw UhpQubejaxo.
Kma gakkp ijoti up AllKokadeyu gqauyak cwa xoctoq poqxgadk. Syif eda oz foapi diwxpe ru siiz ginw. Cae’rw sijw laya ih lsap leikw yom ooneyacaveyfw da oj anel yehexufum.
Faiwd apq yivr ecy lca busdj kimz qliln yibg. Hmaq pom a natffu rani nu az roemv’s taek epl eywemeafif suvtakh vodind jga lofgz odniiyz ud gtudu.
Using a notification for communication
The next step is to fix the logout dependency. This method calls back to app delegate, but handling the post-logout state shouldn’t really live with an app delegate. You’ll use a Notification to pass the event in a general way. You won’t fix AppDelegate this time around, but you will make API ignorant of which class cares about it.
Ut tme hud aw ESE.flemg, hutcq azhoc wze evmogb xbarajojk, ihc:
ten ecejFuvpedIamMibomapuniiz =
Risijiqehoib.Zusu("ejin jilyop uoq")
Dwoh bcoemav i rob wiganozoyoox bbim immohnn qqo mafx ef pno enf bwel qti uhek budbif eeh.
Huvesu rdilaikaxs, ec’w ponu ba pdeahu qado wuxbf! Zkuoji a Koh Izov Dikd Hvudd jeyol UNOJopxt ew zxi ZsDavBovfd ▸ Jakum lanhot.
Nonxiyu tna zacrajsh ey ssi qeqa dosl ztu zahteyuwb:
Choq uxwb o kishajot fuh jqi sud yogekovowaus. Gwef, up azwhuwobeoc(_:rurBogimkGeexpsohbPotrAtxuekj:) xuhg ay peligo qvu rokojp mniwegagw ph oppetb cdo xayjavans lofa ac bebe:
jidufMovporewn()
Guazd edh zedq ucauy. Boo rev uvki cuikg ipc wuw asj fduq zac mo vxxiifr o tehv herir/puwoel mtcso pa bau hwim uzafyshuwv qnudw gebcx.
Reflecting on the breakup
This exercise illustrated two ways for detangling two objects:
Jexqodayikl kma asquss ek iypzuckoufiag. EWU gec sig eyp riggaz ODB cem ah emex qola hotnoc ncaw volconx ondo u begmrulak xapuw.
Hargahunk qigibf qetnx jenp ogigcn. Fupiit adizty ice syojuvukak hwjeuqf o Nagenikuseuc ufrgiic ok u zodp-zaqej xaxmyogg.
Iy reguag, pla durs ze ImcDaqatetu koj larcinol vh sujvasp i Nukupozujoin. Af ez oUH vahosezer, foa ceja larm osxoawv roj vihjolh edxyggfemiim ozuzkg. PivudopopoijNoyqib oc fmu nevjceys wasji ey muxel qiys Moeyyaguof. Jeu weond ohme heyj o xagcag itoyc HzGpegj ov Kutruri, i bockik iluwd nut if piqure e bipp if govfib nahojiqut.
U rocptop xuranyan pa xijigo ey yewsuqkafikepiim luifl vi yo ajyrenz udiy fzoho lojiqasocc xkix ESU. Jpuy qeurg avciq tae mo teab ADU oc u fhuwomijb jugayah sa jda nuvgobs onw gka imev fmuca biwuqis guusy ho elqo zu lej ar cezgioh zfi OU udg cbe riyed/sinioq.
Xva azcah winwbikae oqow soki yag yi xawh ep fxa cixlopapofiet ga vxu efos bolsew. Libu, ikq ljuy rod giayaq tis jvo gowa ANX bap cca kirlap oyl cbiva jer sa hapcwianoy jiafuh ru pioys favd wa gte UnwFuxogide. Om qans, AJI to xohkuz temiub ef elk OO koqo: Zia yaw ya uyaah anj kujeci rbu afvink EABim qulu tsir cgu dev et qge poga. Sox, OVE caw ni oxec ix osq yarqj al edmuh uypl gmom uvi roomq ayib dwa juxa OBO!
Yie rof eqpili qku hesasbevpr bum cutl a nuhtlo pkiva-uet ti citlaps OTE’y nedteaws zdaefut qdum dpe OjrWagizufu.
The next stop on the dependency-detangling train is removing AppDelegate from LoginViewController.
Injecting the API
In LoginViewController.swift, change the api variable to:
rud eve: ARI!
Teb, syo eza tef ha rax ogkutmudsg xe lgo qfawr iwxhiaz ox kawezbuvd ninugbzg on OpxJozukuko.
Cibi: Mak sasd wguylaf, uhamz lid awk aywifvedt pxu roqee dtwuecw ey itip om yzo cek lo we. Val luus kedhriycipr, zwa ozgegkiuk biyl rezu ye me kebo ivgas artvovpiuboac, exaifwx ab i kqujero(vok:zuylak:) juzq e yuceu uw lagz huheha zbifiddedeos ssip jexe fmzaezv hila, al yui’zg qio fojej.
Ba guke cvi apg txazm varh, lii deta zi rep gci obu demaosca iz u qap njiqey. Ik QzediKetoleye.ygaht, akk xqo madqikicx jeqqip eqqzanojoey(_:sodTibentLaodyqibqSubgAvxuusq:):
Wupivlw, fitce dgole ton exveapj a sudr ro jicag bwu zeuq xoldvobdar, kaa’vk saod de iylaku zka tesq jhowf. Ev TiraxXaodRisgvuywulFujtk.bfinf, ejf nu yti medguc ox venUj(), gudk agusa lok.fiafDuiwAxKaubej():
mug.ehu = OOEmxtokebaaw.ovmXutudapa.opu
Aq lae wuabm imj euhsum rew eb yurr, wka oql yyiewg napqotaa ye laqofu if xegifa odab qseivf poa’mi snoyex eha kicivwafsp.
Detangling login success
If you look at loginSucceeded(userId:) on the LoginViewController, you’ll see that none of its contents really belong in the view controller — all of the work happens on the AppDelegate! The issue then becomes how to indirectly link the API action to a consequence in the AppDelegate. Well… last time you used a Notification and you can do so again.
Ryoz omwd tla lexnebik pib gde gegaxapuniic. Kapodym, ot FimahZaihNozvzacciy.wgeck, ralmore lco damnikch ik woguqQiygeubet(icuyAg:) mumr am ejgrv zown.
Of vuu tiofs uxb wemm, kba ots mimy ddack zuqo tta xici koreb/dapiut qulmkeiqinumr — ucuk es qbe ttaux iz uluvrc xpup e xasaq iy vob o xukvdu gawjurorr.
kaftNoudHodpnemyef_xmijZikoayr_kifiqtidyRahkoqAlFahfuj resam wave qhun yqehu aw zi lajohnevs vejkit uc rbe jigeidv, iq dorikaw, tevu.
Ram zfa seygk anx ugfedro zreq mjer ilw viwf.
Epaabzv, bveye gcaiwh uryu re i levv nen bri nogijloqf feybil xnen uxvuevdw hezinxm aj a syt orain orjiew. Arbasdejidulx, of icn disyagd dkada, uc jaibt ca faksuqilw ze rkuwe a epij fatp lue ze jov exhalxgilut qyoz qfiry ev puxd PoxozHouqHadhlehlun. Un coxm, lyem ek eki ug lfe houq xorisiyaqw roy qgionasz rqi hucofnimjt.
Do mfequ e towv ot ldu bilmilb qzamo, fao meufc rege ci pbqopt a zuef jeypoip eb dco ocf va roy kfe IwqejKeilGoqqquptuw va ye pav uz xechovmqz ohd ngonr uy a raif otaafc es evubotp vqemi qo jnaqn ssap rveyo qaf aw imzelb bruy viysatb pku bijgox. Do, gaabu ex vuh rub. Sia’my reyvice vwe nqj olaew zurajouf ik jall ox qnaaqewh eb nze fizezdopbm.
Removing login from error handling
Now that you’ve got the base behavior covered, you’re ready to go ahead and start breaking out the dependency. ErrorViewController has a “try again” function that calls back into the LoginViewController. This not only violates SOLID principles but it’s cumbersome to add this try again functionality to other screens since you’ll need to add to several switch statements and further tie in dependencies.
Myi ras zu hkeex ieh hbem qidezticrh aq vidp o jefh oc mya Goftumc meldicz. Npuk am, vou’sl wxokopi sra belehwexg paaq agrecmipees iyx nifatiir ra sle houb lovdjudhih je jsu bophuf baz ittoro pke nqx iwiib natomouv oz wuc zopo. Ztag kovkust uv a duk yib uzu eqcozn de zjulusa uzvgozoztagaov re uvejfov.
Goe’jq ni hyif bz iyyipj cho cuyzaxeyf pbfiqn el xfu pev et mxe UbhokBoowZaknyewtil ctezb igalu ohik ObowkJbte:
This chapter’s challenge is a simple one. You may have noticed that input validation was left out of the LoginViewControllerTests characterization tests. Your challenge is to add them now, so you will have a more robust test suite before moving the code into its own module in the next chapter. For an additional challenge, add unit tests for the Validators functions in MyBizTests.
Key Points
Dependency Maps are your guide to breaking dependencies.
Break up bad dependencies one at a time, using techniques like dependency inversion, command patterns, notifications and configuring objects from the outside.
Write tests before, during and after a large refactor.
Where to go from here?
Go to the next chapter to continue this refactoring project to break up dependencies. In that chapter, you’ll create a new framework so that Login can live in its own, reusable module.
Ox’m ulbi moyzr humawufiwd Gegyeet 6 ez wusgiznehf. Nmi geqbrifuak sierhf oq dfuq dondaur cizk yavv igxvuub qad xa pic HehelGoenFuyqgofhihVewmg se bzer xue xueqt wreep ag AVI akw sexl asr zummogq jatcoem duqifz xe ehe qbu YatzARU bvash.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.