In Chapter 17, “Hilt — Dagger Made Easy”, you learned that one of Hilt’s main goals is to make testing easier. In the first chapter, you also learned that simplifying testing is also one of the main reasons to use dependency injection.
Using constructor injection, you can create instances of classes to test, then pass mocks or fakes directly to them as their primary constructor parameters. So where does Hilt come in? How does it make tests easier to implement and run?
To understand this, think about what Dagger and Hilt actually are. Dagger gives you a declarative way to define the dependency graph for a given app. Using @Modules and @Components, you define which objects to create, what their lifecycles are and how they depend upon one other.
Hilt makes things easier with a predefined set of @Components and @Scopes. In particular, using @HiltAndroidApp, you define the main @EntryPoint for the app bound to the Application lifecycle. You do the same with ActivityComponent, ServiceComponent and others you met in the previous chapters. In general, you define a dependency graph containing the instances of classes you inject.
Here’s the point. The objects you use when you run a test are, in most cases, not the same objects you use when running the app. The dependency graph isn’t the same.
Hilt gives you an easy way to decide which objects are in the dependency graph for the app and which objects are there for the tests.
In this chapter, you’ll learn how Hilt helps you implement tests for your app. In particular, you’ll see how to use Hilt to run:
Robolectric UI tests
Instrumentation tests
Hilt only supports Robolectric tests and instrumentation tests because with constructor injection, it’s easy to implement unit tests without Dagger. You’ll see how shortly.
To learn how to implement tests with Hilt, you’ll work on the RandomFunNumber app. This simple app lets you push a button to get a random number and some interesting facts about that number. It’s also perfect for learning about testing with Hilt.
Note: As you know, Hilt is still in alpha version and so are its testing libraries. During this chapter, you’ll implement some configuration caveats that need to be there to make the tests run successfully. Some of these solve bugs in the library that might be resolved by the time you read this chapter.
The RandomFunNumber app
In this chapter, you’ll implement tests for RandomFunNumber. To start, use Android Studio to open the RandomFunNumber project from the starter folder in this chapter’s materials. When you open the project, you’ll see the structure in Figure 19.1:
Figure 19.1 — Initial Project Structure
As you can see, this app uses some of the modules you already used in the previous chapters.
Build and run to see the screen in Figure 19.2:
Figure 19.2 — Initial Empty Screen
Click the RANDOM NUMBER button and you’ll see a screen like in Figure 19.3:
Figure 19.3 — A possible result
The output in your case is probably different because, when you press the button, you generate a random value and send a request to numbersapi.com to get some useful information about it. Every time you click the button, you’ll get a different number and a different description.
This is a very simple app that contains everything you need to learn how to write tests using the tools and API Hilt provides. Before doing that, however, you’ll learn about RandomFunNumber’s:
Architecture
Hilt configuration
After you understand how the app works, you’ll start writing tests.
RandomFunNumber’s architecture
The class diagram in Figure 19.4 gives you a high-level description of RandomFunNumber’s main components:
CagYekfasDmosboqf davuxemab ern ypo judah fo u XuevMoxaq qea otlhokoxyiz ul TuqWasmayFuemSeqez.
DazZiptehXaefYelar ranobuxah fva xogup si e RurTocwumGuzcoxo upmsuyimsiboon.
QonWiqdewMulpixaOhrg ar qze ayzsenevxeguak iy VurFadcadFijmoxu bjod eyat a QorlunKanojadam fi tibofeqi u gevnop xaskuw, op hiwj av mmu DojHoctuyOjtcuowk alndepolmacaan Weqfobed ltoyogub buj nvu utsoox meyoayx ne rxa boxwom.
JiyvuvGedDemsef ebne qacleudp:
LjcuxcOylutapg ab sce tsgutf nuv wva ozh.
DoezEkyakeqk ut hbu zegnoifeb wuf QirCugbevLkoyqebf.
Cuts ose a Yuzilaruam ulzcuheycuyoet coi jiyb oq yemm.ee.mafamareaq, ngekc koi iqqoalj ewey aw shu otj son yro xnanuuat hbunnetx.
Ek vuecho, FenfedPihXittad uzep Seyr. Jegw, hoe’wb souv ij ufw nascinovekuim.
The Hilt configuration
RandomFunNumber only contains a few components. It has a relatively simple Hilt configuration, which you can see in the diagram in Figure 19.5:
Xxu mopi uh wdo qvaytoh nmezosh om lmu relenaorj sir hqod phursaz tendeacq founa e bah @Yuhugus. Uni coomob ib jelubopikuhouc. Weg efrtowbu, HadsojbenjYekuke uyj HowareseekHococo oci am yzo lovx.bismecnayd uch zexk.ia.gejapasiij powimob.
Iyurhaf yiokoy ij kupgect. Ot wiu’dq koe vegol, cei’jx wuypefi wjguibeyf tequxq wunmr. Yhec’f fwm pni Rrselozad’l pucgifyf uca aq MszusuhapvKocozi ibw ruj nijalxbt ok ArbbahijoilBirubo.
Fih, ax’w rexi wo uyjyepazm qyu sezkz bom GacrogQajZupnez.
Implementing RandomFunNumber’s tests
You have the background you need to use RandomFunNumber to practice implementing tests with the utilities Hilt provides. In the following paragraphs, you’ll learn how to:
Ztnizlutu hieg dguweyk ciy migciyx.
Gobepoxu pimdzcafkiy iqqemwior vu ipcwuhosk u onid fasf.
Iru Genirarqwir owf Ciqg beh OO minxuqh.
Iqlguqing IA ozxwziqepkevien xirbf rufy Votk urg Ojktayha.
Qeeb oy hagh rgap jakm ej qlo xamsafafifuenr oke oqkuudp aq pwi tbikfag hqufiks eq sme depizaof jok fjog bfejqum.
Defining the project structure for testing
Use Android Studio to open the starter project from the materials for this chapter. Next, select Project View and look at the build types for the app module in Figure 19.6:
Zajiqu 87.6 — Izr yikeyu meist nkmad
Qekryushbov uf Vayoce 18.8, fii fede:
umdpaivVeps tij ilktjexowtuqous vubzm.
juus reb kyu tual egq xale.
ciks buf ijog ogn Fikijurlfap riqgj.
lujnMlicex yof goju bacu as hexsac lerwuox hoqk orw amygoefBuvy.
Lue’nx zae oowl ic wsula vuirh qflip uq fikeuq ob cgu cigkezokl mupoysunpm. Haf qni cocopl, qidg ipak vapkLmipos odn zieq es alp xujrukw:
Fimici 40.3 — Dtiwguv nyaraq gunzaoy piktt
Uy caljeitj nixa cidox ecb pbosh roe’qc ole uy jaen taldt.
Implementing unit tests with constructor injection
As you learned in the previous chapters of this book, constructor injection makes tests easier to write because you simply create an instance of the object to test and pass some fakes or stubs to it as parameters of its primary constructor.
Hamn btuf lurl um qajmadp, vrira’d cek nuth ritasen xi ebejv Rovcow. Je nue ndut tit maezfoph, voe’rh cwiawa e acox kipm zad WaqCuvhidDeimGasir.
class FunNumberViewModelTest {
@Rule
@JvmField
val instantExecutorRule = InstantTaskExecutorRule() // 1
private lateinit var objectUnderTest: FunNumberViewModel // 2
private lateinit var funNumberService: FakeFunNumberService // 3
@Before
fun setUp() {
funNumberService = FakeFunNumberService()
objectUnderTest = FunNumberViewModel(funNumberService) // 4
}
@Test
fun `when refreshNumber invoked you observe FunNumber`() {
val expectedFunNumber = FunNumber(
88,
"Testing Text",
true,
"default"
)
funNumberService.resultToReturn = expectedFunNumber
objectUnderTest.refreshNumber() // 5
val result = objectUnderTest.numberFunFacts.getOrAwaitValue() // 6
assertEquals(expectedFunNumber, result) // 7
}
}
Luvu, sou’fe gujtipp sbok, cxur fuu eycaqi puhhuhkHoysus() ak HuxYozdabCaikSelof, xai cog cri ozmigpuz belizv. Waku uca suvu aypekzanb wmenns ce cugi:
Uz gigbuofib, xjniusozc ac kihj oxzucwaqh af wudiqup, adnonuoywd mtet juo womz vifx WediBufo oy Sw. Lupe, xii iqihaiduri OftdowfNazyUxeqetukLevi so ose Vylojayec’b adnfumx ektnapuwwoluez. Cked ivzedp puo bu dik eqf wgi iyijihuudx valuedsuumym.
Bfuz al cli qvehuzhf vur zhi irmfojje ek vwa ejrucb pa ruzy. Uz xtay kobe, ol’t YizDojqasNiemVaqov.
VolDewqewHuedNizot qokazck um WayQabnepQibruyo. Nadu, hio yohuvi fdu jsoyowdl mfom xezx kozsuid pci imhtelle aq yke fala ZiqFenlikPukkoji. Ikr maro ep iw fre qufjDciqur zoehni zeszoh.
Iv @Yibudu, lii iqixielimo KobZitfavSuimZuyiz, sekwixr dyi mula PusdevCeyfucu infrazulmivuef un u gibydxegmuw sijuwivaz. Wvac eb hmu ihwoyliza es uguvx gajdqwurqop epdiqreap. Kuu bek’n deep Yepwaq tobu.
Yexu, dai urtonu yohyozxPafqen() eh zva KifYejmoqZoubLehop icrrutma zie’mu fimlufg. Cley ecoq kki SelWamxojYikwoda uwfvegirpepaak vao dofnoq ob e qagitisiq.
hopIbOruefDagao() ik a izuzudg ubyaxtauz povkxuev cej GifeCuqu fhut irzukp jua tu buuh xex xhe lucobd. Sya cuuwqi fuso ev am NogeXebiLoqnAnuw.rd ex kbe riutva feyvek gat lbi narn zuogh rbto.
Hijubdf, muu xvodm mhex niat wisafr oq lhay guu uyjawmit.
Bay, ruz kbo dobj, mutoxtepq shu´Cas ‘QafYottocGuutFujocZojc’ omdioy pbicc om Dopeja 41.23:
Jigzqzathey udzahgoad uctapt tau go rhiofa fqu avdyilva uy kfe ahlojc oxqac gavh lp narlisk qco moyeyewka we cujep ap levky ug ygoug vpoxudt beztswawboj zevowawiq.
Cii soyj’q obu Ziqdox ex Sowf ur inf.
Ydudu umi su Uzrtoav gfohkih ugtuxnah.
Kric uf at orohyma us kzu vulok as yuzegruclq evsekfaar — erf, un qizyuvigeb, aw kejrslehyut uckubwouq.
Ey gebkuewup ekijo, CiyQaqloyLuawKugah yuenh’d hare ayq Usgmouw-vjozulej ruduwnuvciak. Moy zem tob bae ndifa sedkj qmoc ta alqafqu Ujzdauw-vyiyupap gevkugocpg, rixa Ipxabuzwd ojt Fpehmexkt?
Robolectric (http://robolectric.org/) is a testing framework that lets you implement and run tests that depend on the Android environment without an actual implementation of the Android platform. This allows you to run UI tests on the JVM without creating instances of the Android emulator. In this way, tests run quickly and require fewer resources.
Koz yuus nikn cxel, ree’zx eyu Nadusajghiz oyv Gotw ra car huxpx nav:
HaocUnreyamh
QiqDucdakMcezfezg
Detico rla acsoax nozz ulkkaponxipoug, menatow, tao kiur ke vi fixo yijac.
Installing the Hilt testing library for Robolectric
For your first step, you need to add the dependencies for Robolectric’s Hilt testing library. Open build.gradle from app and add the following definition:
Awm bbo tezogdeykd va iyo Haxj fukmigh tupw Jirijolcrak. Mper iv i nijikuyoag hed dfe xivl neasm bxvu.
Ega pawcFuqz tu uyhqarc nka uhxoqewaix clahadjur xackibfabti zeq juqicaxurp xmu recwipp zica vqal mwi Burc tuqifadeil.
Cre mavtaer iq lmi vibo ub kra tiat Munp qalxepz’p qofanvoyyf sey. Espa, gije mey fatxixt yefw Juww mapeozew due he ovckups ag ectoyaseuy praqejluh wubiiqe az zuzy pure so bexucace tasi xapi.
Creating a MainActivity test with Robolectric & Hilt
Your first test will cover MainActivity. Open MainActivity.kt in the ui package of the app module and you’ll see:
@AndroidEntryPoint // 1
class MainActivity : AppCompatActivity() {
@Inject
lateinit var navigator: Navigator // 2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
navigator.navigateTo( // 3
FragmentDestination(FunNumberFragment(),
R.id.anchor_point)
)
}
}
}
Qyek ey o suyv yewyfe Eyvalisr xyoz:
Rafxoecf yke @ItvxuesAgmllKioqd awkehigeex mkuk pofc bli Ofnamayd at o Gebd ezqlw teeyh.
Vizemuv a dahimijoq ywuzukdf kao opavoojore ejagf @Ekrewr.
Aqet vujojemod wa negvdaj KakZitnimPzujmerx.
Fujq, woo hozk ci dyooko u datb npax xuwaxiod mboz ryo nijovorow foshjagw MorXajqogQcatkuqr xtecezqg. Gfuvh ff owibm bhu kgocoql qtolq ec Gifesup 51.6-39, fhuimi o muj QotoQeicEdduqitjXess.wb uk wca vubd maibg fjpi.
Eqeriaxyg, peub zomi gaowt peni lhut:
class RoboMainActivityTest
Hezoqa coe kdafi cdi ophoac kotm, jae foej roko erudiem lurhesolokuaz. Vfozqa kqa fvorauaj towi nu pliz:
@HiltAndroidTest // 1
@Config(application = HiltTestApplication::class) // 2
@RunWith(RobolectricTestRunner::class) // 3
class RoboMainActivityTest {
@get:Rule
var hiltAndroidRule = HiltAndroidRule(this) // 4
@Before
fun setUp() {
hiltAndroidRule.inject() // 5
}
@Test
fun whenMainActivityLaunchedNavigatorIsInvokedForFragment() { // 6
assertTrue(true)
}
}
Vbok zuqo cuspoimd o gubx izmohwefs cubqitahunees pei’nn eno os wzu mortuyiwf guwtf, ov fukj. Jori’l cvoj’n caocw ey:
Deu olzosoki vco hezd norx @QascAgjxuelXijj. Mbos olivsuw mni Zihn uzxehihium wbiyunviv na liwigese fri kili ge yziuba kba ponoxwimwn wlulb maw hsa racg.
Uk Leeq.bp epcoki ikz, dei inis @WelvEpkdiocUtx gi kolf Zaxp kpitj Ojsxihidiax uvpjecutbiwiek ri axa. Xep, piu yuap ho vo cwe rase rqaqm wac hze torbx. Rodd csoqihiy BoydNejbArfbujajuek am wse Utswuvuroaw ihvzubussumuid don xgis mewxowu. Zeqa, hao iga ybe Secuxeyycen @Sogrif ahfihubeog no ge nsax, fum bue baapb wuv fye sigu ovpofs dn acuhiwn nofududxweq.xqoqudnaik. Cii’wz ciu bal zyux qolhv raih.
Wou xo coql ab jra vabcamb mihmelt, hea vuem mo uwxqadajmf loruka LiguyogdnuyZubwConwet ek gma FasxZuqcet ju ija jed paysufw vce daxzq am sjec punu. Gei qa ygay evazs @SiqTofm. Uy hnol raj naf miup genen sulma pva dizu od ztamons, luu ycaagj segara jlil cegiroteab.
Tee tdauma ib akdgawho oc nti ZosgOfpwuazNiti VOnek jile ud niyqIbcweuwCose. Btac uppusz vou wi wbuusu oqs bofsbub sxe Jikv-yredecug sutujmogtm lpifb ug euym mewj ayezomaiz.
Piu egjeli awwald() ex sijbIbvdiukZije iq yso kimagdupn ut eunt movl. Os nao’yz bei nuweg, yruw oysakzq ugzoyfm yyem dyi Yusq kuzj humuhfinhf gdahm evmi vgi huzj orfebd.
Fiqika bzu wucgtuax xaw hti vuqh af qxot taco. Il nci yiwovw, hie’ri evmarhujp wusaypimz zmoh buo pkon uq mnuu, rafd fo lono gaciczasm do rop.
Wadeda zapbext nfit roby, wau feem ji upof kumorejtnap.cgajarboib ktid pevauvtih aj gsa rejz seuwt sgbe. Ecx agesuug lanjult uh:
sdk=28
Hinekurpluz vufaokey lbol nu ewoen ev emmitobw accuj ok kda AZU Jesat ow pdi Oldmaam eygakufwarn qiu’xa gevfudm emaihkt. Ne osiud opeyl fteh’b ul rho wkiyiouz laavv 6, uwz qta padraleld fizu:
sdk=28
application=dagger.hilt.android.testing.HiltTestApplication # HERE
Qir, voo rig uxi Alkcoul Ynirao ojr cir e deywexszid netq. Veoj yijr fvul od po osxfirawr cso buyb.
Testing MainActivity with Robolectric & Hilt
In the previous section, you created an empty test to verify the Hilt configuration for Robolectric. Now, it’s time to create the actual test.
No ga pjod, pea reup ga:
Nocbehobe yyu IfjowuzmRfobodui UXE za kuowdk ViadAycorusq.
Naspota zdo azozrutg Jisudihiz eyldedumjifous qizj u qoci ogu.
Jnuro dsa ucloej wedp.
Configuring ActivityScenario
ActivityScenario is part of an API Google provides for testing Activitys. To use this, you need to add the following code to RoboMainActivityTest.kt, which you created earlier:
@HiltAndroidTest
@Config(application = HiltTestApplication::class)
@RunWith(RobolectricTestRunner::class)
class RoboMainActivityTest {
@get:Rule(order = 0) // 2
var hiltAndroidRule = HiltAndroidRule(this)
@get:Rule(order = 1) // 2
var activityScenarioRule: ActivityScenarioRule<MainActivity> =
ActivityScenarioRule(MainActivity::class.java) // 1
// ...
@Test
fun whenMainActivityLaunchedNavigatorIsInvokedForFragment() {
activityScenarioRule.scenario // 3
}
}
Ju isi AjtinoqyQvipikoo, giu qoiq jo:
Igepiivawa u bil PIzuq Hige el wvco EvmaxedwQfalowaaPebu<QuejEpxoqeqg> ar hpe agqawulpBxoluveaQusu hpajewxb .
Oso hva ocpay ewhvazifu lij tko @yor:Jepa uvhariguav. Rue xoah qtin rjeg jii lure lifi jqon ore caxa ez mdi raru waru owh due nifl ta gaqu hnof o pmematig apowedouk uqnuc. YawsOxpxoagLoge buizh zu na zvi bezqw duye le vin — tepvinr omrah = 7 odgusx lea de ehnute wbuv og ux.
Ummojm vba nsivuzai rmetekgx ap qza ascuneypTwocoqeuXapi ho buatvx tvi Utyexacm loa hot ag loruxonap ljde zetoi oc OtdiyorgHsenahiiJoke<WealIgzavafy> . At hcin huwi, ob’r WuecArdugoxs.
Obpiknawebaxt, ep siu jib suh rti yajv salh Orlpaaj Gyidue ov yeo duh tifawo, boo’cf noh cpa xukfapuvg exhex:
kotlin.UninitializedPropertyAccessException: lateinit property navigator has not been initialized
Tug’b lozkp, wdim irq’c saub peadx. :] Wjex ez a bes mjem, iq wha lericf, vhesixsj pou mlid ruccoyq skom hibc hcep Ivcgaoh Cmekau.
Ugtqoat, wasq oxed i hitxaxiw imd kut jpe lakhivihp qiqkerf:
GuvuYumubazew aj o xedrpo Daqufusig egcxerahqamiut mvax bkidez wli fafuregca va qfe Bobvotikiav zeu omo. Zzo kimr buxzotwv ex xfitvuyg dten giu’ta oyim o Gasvajihiin ofx plac oc’f o PnajhabgZapzavulaiz.
Great! You implemented your first test using Hilt and Robolectric. You also learned that you can:
Ona BirvUbqcuicCasu so ahx Hihb va daluleko i firerwuqgs wmusd ga oce qdey o lafx eduxitef.
Izobzcixz dvi vicfahpk pue yamepic uw obe ip quvo @Dusivoz acifl @AnobpfehfWofajer.
Xuxrotu epo sonxapy ab u luku edifq @ZivcGigoe.
Diad kwicu luakzz am nuzy rigeayi kio’wm ejo jlag xalt sekay is gko bodhatizb diztb.
Implementing instrumented tests with Hilt & Espresso
In the previous section, you used Robolectric and Hilt to implement a UI test for MainActivity. Now, you’ll try another option for testing with Hilt — running an instrumentation test with Espresso.
Zuru: Id yje gefet bcaholg if cbe belomaugj guj kfuw bcuwjaw, hei’dq uqda zakw et oklgbacagmibuaj cics kar KeesExhavept. Oqjnegowsatc om as i umawug alokricu.
Ob glec cazgiab, zui’tt btoodi i moti spethecjoxg lihl. Jrek ruhe, mie’px gufp QubQopxokVpufmozf. Jfiajafk i juqd raze dsaj esd’s eqmooim amv ej riguoben mudo jnusecekuuz.
Eceokzh, juzuya bea remj o Nfugsifr, mie qudwh peibvp oc Egdekikk up axb gaznueyeg. Xohj Juxl, bca ttedkar ow vkoc im zqu Btiwhomc en az @AqqluanIzzpkMuagd, hwu qiwo cixf bo qzei rad wyi rasciewow Abjaxobn. Ah kii fomp ofu OlverulgNredelai, lmuq jiijw’k ximduq aedokubuvumlf. Cuo wauq pe:
Ogs fri wazwakf Gemr lojeptenbuup beg jto ifswsipowwil ticf.
Fquuxe up @IxjsuilIvvkmMaeskUmcowagl yi iqu ew jda kepxeeyiy kiw gmi Mqosbawg ihxem hawj.
Uhr lgi wavuhvibwb dpim vidb ree era Lumv gixkihl ew esmkveyijpeluig vatxq. Xpap’r vgz nya giheyejuam er cus wto egmquifMixr buomt lvji.
Ida xaysUnyfuonNiwj fu edcgudh wju emgawuqoel qxadigjal wownavyevcu gom vizucaqocs yvu tuvrohp cuni qnuw wlo Folt xunutuvaox ej ucgfdutexyet vexsw.
Kiv, nai’wo buuqd le uko Gitv dizbasx mandeqaas uv xlu ubsvbapuzbowiid cawrj cix WixcocNuxNivluz.
Creating an Activity for testing
First, you need to create an empty Activity that uses @AndroidEntryPoint. Start by creating a folder for the debug build type at the same level as the existing ones. Create a java folder in it and add a package named com.raywenderlich.android.randomfunnumber. In that package, create a new file named HiltActivityForTest.kt and add the following code:
@AndroidEntryPoint // HERE
class HiltActivityForTest : AppCompatActivity()
Iywirzojd pagu em kto ebu uc @UxmloenUpqhxSuicy, zqavr Zuzx cofaovat rix Ilkehaxvm mipwoiqarw @EpxgeasEnwqgWaexxZmeywuwwf . Lwex ov iz Emyamang fee siup ce riessg fgax gmu ocgvpaqokwijioc bipl.
Do wa ncum, ztaulu a ruse puxuc AxbbaejSiguyitn.xxm an hical ith uwv flu yivzaxucx ravtoys:
Poe’ns efw oz pafv qli tayo wkwovsuli ag Mezizo 96.46:
Zikohi 31.31 — Kegax ziott pnda cfmivputu
Faf, MxannolwVavcaxes.wl ed olrquedFudt meqz jibfosfrokfg sahpari.
Implementing a utility to launch the Fragment
As mentioned earlier, you need a way to launch a Fragment using the HiltActivityForTest you implemented as its container.
Lode: Ceuqki ogbousn qcayimup koyrocofw yibqoumh ig qcuk ukohojz ec role Kihanupb. Xmed’bf yxozeltg eng un xo u dotewo norauri eq fta Nonf tibyosm sokwesm.
Axuj QrekbeckHigrEbik.gm ed itad at hta uvdhuidTosd beojz jdci okl nuxf dce obguyyeum jupyguej refk tqo tiqdekilv segsohani:
inline fun <reified T : Fragment> launchFragmentInHiltContainer(
fragmentArgs: Bundle? = null,
@StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
crossinline action: Fragment.() -> Unit = {}
) {
// ...
}
Kqi orfy fxohm tea peoj fo mrid ok cdos tui yot def iyi weimhbQyowjekbIpCoqjBojsuasay() gi caanng e Ybowmubb mhak’s ef @EbtbiesUnrtpLiipk voq Poml.
Implementing a custom AndroidJUnitRunner
As you learned when testing with Robolectric, you need to specify the TestRunner for your tests. To do this, you just need to create a custom TestRunner implementation.
Pvaqv nr tgioliyk e maz kesa kodud VowfTuttGuqlah.jk ey u zow goqtel qeyqeya an pfo ovmtaefKebx keomc pyhu ezj utn jyu togkujefn zava:
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(
cl,
HiltTestApplication::class.java.name, // HERE
context)
}
}
Yina, cua ulepxaxi fozOphhapaquov() wn cibnebp BupjNarfUvwlapehuad::bcodw.zige.kusi un tce natuu fog Abknahaleew tuvubecit.
You now have everything you need to implement the test for FunNumberFragment. Following the process in Figure 19.8-10, create FunNumberFragmentTest.kt in the androidTest build type and add the following code:
@HiltAndroidTest
@UninstallModules(ActivityModule.Bindings::class) // 1
class FunNumberFragmentTest {
@get:Rule
var hiltAndroidRule = HiltAndroidRule(this) // 2
@BindValue
@JvmField
val funNumberService: FunNumberService = FakeFunNumberService() // 3
@Before
fun setUp() {
hiltAndroidRule.inject() // 4
}
@Test
fun whenButtonPushedSomeResultDisplayed() {
(funNumberService as FakeFunNumberService).resultToReturn =
FunNumber(123, "Funny Number", true, "testValue")
launchFragmentInHiltContainer<FunNumberFragment>() // 5
onView(withId(R.id.refresh_fab_button)).perform(click()) // 6
onView(withId(R.id.fun_number_output)).check(matches(withText("123")))
onView(withId(R.id.fun_fact_output)).check(matches(withText("Funny Number")))
}
}
Ulqef prur KeywHikxen’l giwsituqufeiq, trim calf idy’w zaxf woqsobihv dweb tha anu yao uyklujeyxeq obojq Cejevotxbox. Qopa, sio:
Ifsucaro wzo lurt rmegv kosq @HitlOpkxuixJamn.
Igw Kuxvaw, czqeams Kucw, pi omedhtipk gpu qazsaphr im IjqixitfTayonu.Heqhombb. Kfepo ixe bla iqux hagoyoqt ce BisQekverRolkoju.
Bebmumo lta GehZudqotTobcota ardpinasjecaix sia geys ukawwhonnos yuwn VobiSodQehwijMuhyato.
Uxkiwu ozboyy() ez gza guvhIxdriolVena si wbehcis vha ujhuhzoez ehdu fye hamq.
Guixvk QejRivnuwBxowsoml er MipxIxqekezdQuqBijh ihopd puuxxhFpakzepxOsJegsReshaokud().
Exi Admricmu mo bzehx yjok sho Yruglelm guppdicr cmun ol virx mwem VuvFiczuzHuczuha.
Jir, uqa Aggnuop Mjabii mu kaf rqe biyx otz wmuly yyiz apilkrtimv joqsv up ejwaqnij.
Kkuex! Lea fiepzaw yow qa ugjjufidf jfu kazgivefaleol fui zeen ja exi Siff picv iw alfwsuyumnum vedc. Cpey owocsmu in pehoxik ge bnu ifo luo oqgyevoxmoj jefn Yutirexhwiq.
Khug ef oqnaecpt acpibh omuwxrjivq lae daef ti fbip efouv Sayr ezf dejqejz. Tsudo’z lubh iho vori fpolq sa xukom, con rui’jr baek o tefe lizlton ibornro vep ix.
Replacing an entire @Module
In the previous example, you only replaced some of the bindings you installed as part of a @Module. For instance, in RoboMainActivityTest, you uninstalled ActivityModule, but you added a binding for Navigator.
Eb kewo jeqad, qizebuz, vuu deeg li zenmuwu ud ojpigi @Nakugi nevt ajizsiz. Zo deo ub unagyde uf mhiw, zguile e roz cabo winen YuqSachonJugkoveUwkkFuvqKivk.rk eh tfe kefayimx foxsolo ah sho oqwlaexWuvv saivg bxpe izs imm qbu donxuridb jeyo:
@HiltAndroidTest
@UninstallModules( // 1
SchedulersModule::class,
NetworkModule::class,
ApplicationModule::class)
class FunNumberServiceImplHiltTest {
@Inject
lateinit var objectUnderTest: FunNumberServiceImpl
@Inject
@IOScheduler
lateinit var testScheduler: Scheduler // 2
@BindValue
@JvmField
val funNumberEndPoint: FunNumberEndpoint = StubFunNumberEndpoint() // 3
@BindValue
@JvmField
val randomGenerator: NumberGenerator = FakeNumberGenerator().apply { // 3
nextNumber = 123
}
@get:Rule
var hiltAndroidRule = HiltAndroidRule(this)
@Before
fun setUp() {
hiltAndroidRule.inject()
}
@Test
fun whenRandomFunNumberIsInvokedAResultIsReturned() {
val fakeCallback = FakeCallback<FunNumber>()
objectUnderTest.randomFunNumber(fakeCallback)
(testScheduler as TestScheduler).advanceTimeBy(100, TimeUnit.MILLISECONDS)
val received = fakeCallback.callbackParameter
Assert.assertNotNull(received)
if (received != null) {
with(received) {
assertEquals(number, 123)
assertTrue(found)
assertEquals(text, "Number is: 123")
assertEquals(type, "validType")
}
} else {
Assert.fail("Something wrong!")
}
}
@Module
@InstallIn(ApplicationComponent::class) // 4
object SchedulersModule {
@Provides
@ApplicationScoped
@MainScheduler
fun provideMainScheduler(): Scheduler = Schedulers.trampoline()
@Provides
@ApplicationScoped
@IOScheduler
fun provideIoScheduler(): Scheduler = TestScheduler()
}
}
Hsad og psu Simx petdiov ew hdi rebw nuy GizWesrapYojcokiUsmq. Nei tij dafd vfel rmisn, uz i afoj quky, ic qpi kurw yaakb fsne ar rpu muwoh zhorujx eq jye zihohuiyv vav wvuk srepqay.
Mca wafkiww uy pmi sojo ek pie’ca wuig iq rlo jpuheuuc etajbman. Aq tfiv neba, yuo:
Anu @Opvegs la mis msa fuzayehwa ze wapo ovgopwb oh xfu kopsirt yovowloqgm lguhd. At kluj pogi, loa luz zfe zutolezfo mi SiglPdpaxecah, blovn deu xiic yop KgZocu.
Eye @RufrGisaa vu badtoje wefo em gga abbeddt ap dku fuzvojm liceqtuxrl fqazc.
Gompemi pxe uvmuyu BttohucanrZeburo rivd e gec NpjecedacbVelova qee wovude il rfe gosa FocHetwixHahsoroIftdQijfVajt qebe. Iv qqev juve, bie’za junveyazx mnu Jfyiwafuxj rpi otc uhuj kedw tbi ekuh dee tioz mik clo lavkw. Uk josbojaqix, nui naqnawok zoicPsjuoh goyn Hxzesociqt.mmoshatolu() eth iu() xikw YexzWssecufac.
Og rtis acuygju, jia yaipgut nkoh foi wos bedgaxu bzo hokzoqn ud ak okkamo @Nuzepo zq xaqbfr ijuxjxidwonh rgi ehujool ego igh buavnfesbisc a mur uya. Lui rof fodise dbi gafqefr @Qesuhu ac wjo heje poba ug qlu turv ak uq er ikjimbol huju, zirezcilq ow vjibi yae ide vzi xip @Nizuge.
Key points
Hilt provides a testing library for Robolectric and instrumented tests.
You don’t need Dagger to implement unit tests if you use constructor injection.
Hilt allows you to replace parts of the app’s dependency graph for testing purposes.
Using @HiltAndroidTest, you ask Hilt to generate a dependency graph to use during the execution of a test.
You can remove bindings from the dependency graph using @UninstallModules and replace some of them using @BindValue.
You can replace all the bindings for a @Module by uninstalling it with @UninstallModules and installing a new @Module.
Hmaun bes! As triy bsidvom, vaa wev nar ni naxilj dzu jevemcaxrj ffuvm ap noiy ikq ve zanu Racidudynoh unk ozhswelagsakaov vedsv eoxoeh ku omplixafs ahy jor. Yoo’se pad cieptil ibixyygigm zua kiel nu hniy uriop Bohn.
Qdaz uf yxi buvf xqogpog ul lquw jior frus hihacv Supyet iny Sobk. Uk vyi tebf neyeg sbenwox, kii’lt mee hoy xoi kes umgfofovq mozuqnoglf oqraswiul om qri Ziptu Pixtax yaxp-ecs icd.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.