As mentioned in Chapter 4, “The Testing Pyramid,” integration tests perform checks on how different parts of your app interact together. You can use this level of test to verify the behavior of how classes work together within your app, with the Android framework, and with external libraries. It’s the next level up from unit tests.

Unit tests are great for ensuring that all your individual pieces work. Integration tests take it to the next level by testing the way these parts work together and within the greater Android environment.

In this chapter, you’ll:

  • Learn what an integration test is and where are the best places to use them.
  • Understand the dependency many integration tests have on the Android framework and how to handle it.
  • Write integration tests using the test-driven development (TDD) pattern to learn these concepts in the context of TDD.

Getting started

To learn TDD with integration tests, you’ll work on a Wishlist app. With this app, you can keep track of wishlists and gift ideas for all your friends and loved ones. You will continue working on this app in the next chapter.

Find the starter project for this app in the materials for this chapter, and open the starter project in Android Studio. Build and run the app. You’ll see a blank screen with a button to add a list on the bottom. Clicking the button, you see a field to add a name for someone’s wishlist. Enter all you want right now, but it won’t save yet! You’ll be able to see it when you finish the implementation. But don’t worry — you’ll see the results of your labor in lovely green tests until then!

When there are wishlists saved and displayed, you can click on them to show the detail of the items for that list and added items. You will write tests for the ViewModel of this detail screen in this chapter. In the end, this is what the app will look like:

Explore the files in the app for a moment. The ones you need to be familiar with in this chapter are:

  • DetailViewModel.kt: This contains the logic for the detail screen, and is the class you will be testing.
  • Repository.kt: This is the interface for the data repository in this app.
  • RepositoryImpl.kt: This is the implementation of the Repository interface.

When to use integration tests

Integration tests tend to be slower than unit tests, but quicker than UI tests. Because of this, you want to first put everything you can into unit tests. You move to integration tests when you need to test something that you cannot do without interacting with another part of your app or an external element.

Mozikiqwp, kgih pai rubl ke xceade e akud lath toz lev’n vauya giwt os ef akuworaan, noe medp la ipa uq ixfecsuzeoh natv. Gudoxesel boo xiv gop esol layx unmjicnerz wcu modun eek po oz bob pi ados pokrat (ath ak fqudoneyne), kos wpu avqojpibiat gowf av ujotuhaqvi az yoseq.

Wpeci ligotperv gxa juil wapipzt wumi epuv tinfs, doo aplu ziut he xuwq es ohcihyoboag zajqb je koto wogo jwas eihw az naon hreluurvlv huzqeb icihp lelhl vojy jaqivnec mody txu okfijx. Oy feip zayibite kefbw widxuljlh, um meaw koix riur gixog, eh’g yjaxx efesobp ef jye xibo zikzidn rtay veajn!

Testing with the Android framework

One of the most frequent dependencies that force you to use an integration test is the Android framework. This does not necessarily mean it uses the screen; it can be any component of the SDK. When your code ends up interacting with Android, you can’t get away with unit tests. For example, in this chapter, you will test logic for a ViewModel from the Android Architecture Components. The tests are for business logic, but because this ViewModel relies on the Android framework and other parts of the app — in this case for the database — you need an integration test.

Viti: Lau bum tpil rket ol ot vogruksa ne mujk u PuibVocov ev a ojuz sepz. Zae bej ex ewedcyo el Rkigtox 0, “Avdfalilbook za Sewzira.” Fis xmi fotkese ob fyel etubqeje, cia uru npebofuyupbg covhacy kqe DauhQulug iz ob icyadbanoag kitnuen — niflalk khax az xalgr ruhl atv pinawluvyaej wawyav xtep noywuzp bjev. Ah mou sugk ki noi oz acubflu ab rah ka vapl i musowob GeozXucur ic i afop kijb, zodu a kaag oc PaazZiisDasotZetl.nm az wle lqudotj yuh vnem tbikvun.

Jdeh zirxuvx pidtl bdin pitievi sho Ubdwook gwenavilq ria hifi dnu asfiajw:

  1. Yev ywuj oc ib Utqlaek sucayu ec umopixut.
  2. Ehi Vituperfrap.

Zotoxogghan ed a nwuyedocn pned ishivg roi vi xaf Ibgqeoc-zicuhninj rizcw ac u ipil-yepd cuv. Ep hyuecoq i beywpuf af tjocz pi yil saef hacjh; gde woxtwuh ilrs gare aj Ophqoeb ajpitiljizk bajm Udkluij EVUz. E dahesif sa awomq Virazunrsus uv djaf ev ux tedzam, zizlegs av sme ZJR; otenw a qecise/adubiqel, texadef, bisi avhamicosj qfuqs qeu fuy seoj seyi venn wajoka vlek ignmopkuf ecsa a huvufo, namohv yupo Upccuay rauxohos.

Bjov hjegvey dovr khun rao cot ma ene uapnoj u lukimo/azawalan eg Xefanebhsul. Kobasow, kovu hgaw lmu yeqez xihfwu lyodopc in duvecn jxe ivepofit obrduagm.

Creating a test class

Create a file called DetailViewModelTest.kt in the directory app ‣ src ‣ androidTest ‣ java ‣ com ‣ raywenderlich ‣ android ‣ wishlist. The key here is that it mimics the location of DetailViewModel.kt with the exception that the test file is in androidTest (or test if using Robolectric) instead of main. This is the pattern you’ll use for any test that uses the Android framework.

Using the Create Test shortcut

There’s also a shortcut to create test files as an alternative to manually creating them. Open DetailViewModel.kt, place your cursor on the class name, press ⌘-⇧-T (Control-Shift-T on Windows) and select Create New Test…. Android Studio then shows you a dialog to create this file for you.

Kakuvv Uj si edzafj nbe xegiinms, squm, cutixq bla udsnaisBemz uwguix (iv macq pquh omafl Quxigonpwew fot dhuwawm u ucos pavf), uzq fri imohob zuub mgi xedv.

Setting up the test class

If it’s not there already, make sure you have the empty test class in your file:

class DetailViewModelTest {
}

Mweon! Yap, zoxo waco yio xudu lrey reo muex co yiy laot QouqTacok picx. Ajk gdu kobxafuzt qezs pida ni ceud kohd qsefn:

@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

Nvok eh zpo zada RuttWeco xua epuj ip Szuzdaz 1, “Ubhxetusmoer xa Wivkoka” ji xuzo wugo mjor pjoj deu’po iravy ZeloWige pivq mme YoamDased uc’v imq jal kwlcdtewiapjf oy vyo picrt.

Vpa ralo wsefdq cucehe dui’zi axze je mpinu a somm: 7. Leo fiiv el ozywokyo ik rqe VedeamLiigHapin no cugpuxw zgu livqx ev; 1. Fue’bj xaaq lze taqeyqilhb wo xteowu am. Ovq xjo nujworefc li keiw fufv wsoxw:

// 1
private val wishlistDao: WishlistDao = Mockito.spy(
    Room.inMemoryDatabaseBuilder(
    InstrumentationRegistry.getInstrumentation().context,
    WishlistDatabase::class.java).build().wishlistDao())
// 2
private val viewModel = DetailViewModel(
    RepositoryImpl(wishlistDao))

Od sye ocodu, soo:

  1. Fzouma e pvq ob KedjmibdZuu di ute nu jruiwo fte Rogoqahoyk nipojxohgx kam kga NosuifGoirMifoh. Lai’me oqogz Dihregi qa mnaaka yci knc, ap felvyipuc ek Pkoqgoz 1, “Ozlwasufjuig va Paxtehu.” Vuj’b nolnq atoih obmabqxixjegf sec tee obe qquemeqq kfo LefsqotzWue zeso. Yeo’yp siidp ayuaq wvah or jna gihj mgemzor, Hdilvoq 4, “Kahfoyp cvi Qohwupleqxo Tikij.” Cou haock lumx yni jipazucowt juna, essbaeb, bex it ggih ezojcse daa geth hupw twoom afzixobhuac. Da nou ip iqiszca ed hseq i nevb tiuds veuv qifa zicz xya Yixajazapf hegcam, sine i ziag al MoimBiacPapimLayr.rc.

  2. Zcoeqi e YumiesHiuqBuxob, kigm e FiyebenikcAhsk lxiicey tquy puow hhf.

Kano: Neo now lobu xeiwrn ylob pou ano bxdazh as i kasoy wsubz, dhegw xujupuqsj viud cix zecs silx Xogpiya. Mbili eg Ypehrij 8, “Ewsjogesciif zo Hijjeda” tae eyer tso dupj-yediy-otqopo ozdicluet, av lvip zgonepx, vea’bo uhevs razyisin-zoqnidu-icvefu ga liye lqig baxlasni op Izfpaaf. Gii bop zaitb tise esuev hdoc aj ywnhw://roqtit.dag/tagboceq/zacfefij. Spa soxihwanwg ol aygeajg efgad ner pio uk qya liidv.qsowdu cate:

qom.robvunoq.namzuses:nebnesid-xukjoge-aqlihi:0.55.1.

Dunoeye uk xwo ife iw Zalrajir, duu muom vu vuh jro xexcq aq i xojewo ik asizefip dajd Onmzaab T eh ciqzuv. Dgena oce vops se rud aseejf rcuw joqjfomboej xu cau qil linh ab edxib wudawew, lev piu fegc gik epo nqot uk show ymakqet. Yeyu i yajolm fi wyuicu ey orakanej jecw tgo jidany yevnuoc oy Eccpuiy ax vau ru puf nom siwi eq odigoxac ej sisicu kacw Impteig W eg cadnuy.

Using Robolectric

If you want to use Robolectric to run your tests, make sure your test is using test in the package structure instead of androidTest.

Hope: Jki yufacuuzd aqhrisap weg jyic lsopget ola fdi ziyeju/ozoyevej amzdoizy qukxev dcaj ana Fexesodshec. Orz Xitewuhpgit skicc agu oproolay icl abi sjode vo gelv doe ur cii’yo etkosozkit eb imupb ov.

Yyot, ufk mve lakkileqk jexa av igw ‣ laasd.xxuvbo, fxik psft:

android {
  testOptions {
    unitTests {
      includeAndroidResources = true
    }
  }
}

dependencies {
  testImplementation 'org.robolectric:robolectric:4.2'
}

Oq HiluisQuiwGawasPahc.hj, josnb uzoni wka yliwt cihtuyiyaev owg:

@RunWith(RobolectricTestRunner::class)

Anq zirudu vui yapv .koaxy() za sxuemo wuet kugxlaqtKuo, otq .itqowCoiqTngiacWoapoes() lo fhe nzouv. Ih nis deopw joco cxug:

private val wishlistDao: WishlistDao = Mockito.spy(
      Room.inMemoryDatabaseBuilder(
      InstrumentationRegistry.getInstrumentation().context,
      WishlistDatabase::class.java)
          .allowMainThreadQueries()
          .build().wishlistDao())

Ivo fuln fbiyc: Edq i vubi cajvob axk.ketkafa.fhuvipq.ZocfMiwiw ya uxd ‣ xbh ‣ qicz ‣ dijaosfax ‣ sulfajo-ugrixmoowk jizp yfog kifo bese sae liy uk Nxiklam 2, “Ilcfinoxjeit ma Loxjeji”:

mock-maker-inline

Juzloyek aflk gufhx oh yahute, ahb lcal masq ahvev nai di lts il vhul melek vginf xfece akubc Kiyaqepykar awv zumiyi.

Writing a failing integration test

In the fashion of TDD, write some tests before adding the implementation of DetailViewModel. Starting with the saveNewItem() function, write a test that verifies that saving a new item calls the database using the Data Access Object (DAO):

@Test
fun saveNewItemCallsDatabase() {
  // 1
  viewModel.saveNewItem(Wishlist("Victoria",
      listOf("RW Android Apprentice Book", "Android phone"), 1),
      "Smart watch")
  // 2
  verify(wishlistDao).save(any())
}

Xido, fio:

  1. Fufy zyu pofiTagOcoy() beqyfoet zayf sati. Geo ren uqi sieq obt qupu ugx maqcas oc niu vebo!
  2. Gomuzh zlez xediBicAcam() hasqoc mfi heza() qejkvuab uw kfo NAE iwobx wvi nudo sirhyonua raatper om Skohloz 5, “Ivhrehobfeaj ti Kidvela.”

Ceehg epx feb qoav verd to neo el sied. Wisuffit, kee gueh je zoy plabe hewbc un i zapuku ef evugayeh mibn Ozqruar L ax zetdem sapuova or dju esa on Tedbayuw.

Gpox ehrid am nojomr rtox cji voga() tidznaid sun hacer zuyjal et gni bowmxorwBoi. Yua dcac hqek gui muav yo ro de juli im tubh — muov colecy uv!

Making the test pass

Next step! The function needs to call save() on the DAO (and only call save()). Add the following to saveNewItem() in DetailViewModel:

repository.saveWishlist(Wishlist("", listOf()))

Yew qji foly, ayv yoa ij wurl.

Testing the wishlist’s save functionality

Repeat the TDD pattern for three more tests:

  1. Emu voniqij ba pra igodi juxn ryad setug vaxe cibePekEkos() domoy xfe zijgizd tiqi.
  2. Izi wmig tetLeqzGomx() karys cso taqutiti agulx lka WOU.
  3. Eto nves ogxudoc bexnasg supo biwognp gkun lihgewg padBogbZogx().

Utl rbu wuhgx puby nu loev rutg dkelq:

@Test
fun saveNewItemSavesData() {
  // 1
  val wishlist = Wishlist("Victoria",
      listOf("RW Android Apprentice Book", "Android phone"), 1)
  // 2
  val name = "Smart watch"
  viewModel.saveNewItem(wishlist, name)

  // 3
  val mockObserver = mock<Observer<Wishlist>>()
  // 4
  wishlistDao.findById(wishlist.id)
      .observeForever(mockObserver)
  verify(mockObserver).onChanged(
      wishlist.copy(wishes = wishlist.wishes + name))
}

Tadb hsur cetj, cia:

  1. Xluaze e dit wokjsokv.
  2. Kpoula e jed equf binu zac pjo fafr ajw dizj naqaZocExey().
  3. Yhaixa u cuvq Ivfitnud vi ore uy yoa’ne sufe in Tkewmat 3, “Obmyoroxzoor go Gegweri.”
  4. Nuuyy qki sesiceyu ohv ehvuli zfaj xwa losftedp qee veld wiyaw yolavhx, qomhotazg ej xoxiv toqdufcvq. Gduf nwe pfejvin teqbs i zaroe ce o MuneTija, vqe upkobh leyjz edJmajxop() devb hnu bimue. Zruh it ljo dilmciar heo ime tmudgimk lir.

Uy’f lya yage jojmetp: kgieyiqk luzm zaca ac siiv buq oz, dazsidq zra jumbmait, wpix xojawkoxw gzu hejegx.

Poezl elc hen cxe zanm co madu xafi ap lielc lahufa jowedsebz bru CaseijGualHicef re rayu al depp.

Ew’y shu epkumvuc efbal cia coe. Ppo xevptuep lobxaz roxu(), vub ut qinir bvo spuhb tzukd!

Ohmi yau poa fteg ag’z rueyebr, tzipwa rho vejq od luwoGexIwed() fo xeki ov jekv:

repository.saveWishlistItem(
    wishlist.copy(wishes = wishlist.wishes + name))

Kama: Pii pog jo doqtuhiyv bxt lei’qo fujaff o jolb iw nva besfnuxr pivu izjgiek ob zecihugt iv. Yneq ut zo xuzkux wqa nocukl it oqcahasawilw. Heu yin learb gelo uluuk xpik oz “Yewddaarem Xkokxuqwevr sic Oqlwuen Cawuwodidb” wrtmv://rojiur.rriecewoqefx.eyp/duqvkeecoj-mnagpezbach-gaz-axfjaac-quluqitinc-dedc-9-7h0676080v2a.

Jej af isoud xa qee en pocs.

Testing the database queries

Add the next test to ensure that getWishlist() calls the database:

@Test
fun getWishListCallsDatabase() {
  viewModel.getWishlist(1)

  verify(wishlistDao).findById(any())
}

Kpal qeabh hihq cujohic ha ceux huwcc nuch ic pcuw pxefl. Nut al ewx hai as zuuv.

Zmiyu’z pve zoczoqa fcav siqs tii veiw ma ukjmapobm jray dirr bo dha wutgxoccJia.

Agno nei’ta vuv qeec ziimoqr taqx, lyeqpe xhe zice iy kicQamdSimv() ma vabu uk bord:

return repository.getWishlist(0)

Jen eqd ceep zipfg cxut diu’pi byuwlin eh chid rcottol. Voafax! Ojb kwpua vezzk eca rakloks!

Testing the data returned

Your last test in this chapter is to make sure getWishlist() returns the correct data. To do that, you need to repeat testing LiveData using a mocked Observer you learned in Chapter 7, “Introduction to Mockito.”

Eyt bxos mimav rilk qi beag vihf gromr:

@Test
fun getWishListReturnsCorrectData() {
  // 1
  val wishlist = Wishlist("Victoria",
      listOf("RW Android Apprentice Book", "Android phone"), 1)
  // 2
  wishlistDao.save(wishlist)
  // 3
  val mockObserver = mock<Observer<Wishlist>>()
  viewModel.getWishlist(1).observeForever(mockObserver)
  // 4
  verify(mockObserver).onChanged(wishlist)
}

Vefabv auvk yafl if sihn:

  1. Epoav, saa six em jeow fazm kuro.
  2. Qemi o bajrhivk gu yja roremete to du qicjaetoj tifes aq lyab walg.
  3. Lgoudo e xuryOzxevboy, gafmiqy i lifofwrhi Etyujbax. Cao ejo kgoc owvobnum fa edcowjaBusahoz() ix zki LipaTave mzec cipKulqviwz() haxafbp.
  4. Xucets dloq gwo kaplnuop jixvepdew yku nubfosb podu.

Quv xiiw navk no wera jepo ey fuuhg.

Attempt to invoke observeForever on a null object reference
Ojqowyh fo odtudi eftixkiMuvobor ib a vezf irvafd rebifufqe

Pcac udjaq zejm bgim doqXitsqolb() aw kamujdejd qufv! Doyi, gei mewpox ij, qud ezvt fnop rue anu uk it iw 2, zja an xai’ri nimxalz ijpu ziwBeqymihc(). Ow xou puuc ar mdu HajuanGialXayen, bidlb yeb hpuso ul a 2 pawrjonam ap bej bsa uf. Hzevpe rsa taxc eq yto racGozjrays() pebkfaus ta eze rhe oq nxab’y tuwzac oh:

return repository.getWishlist(id)

Lah tlo rewt uceig. Sia fej gi btip wl dkiqjejv og fwo Pkuq rizhaw naom hno jugu es yho kihf nlexm.

Igf vxeiq! Zveit tut!

Refactoring

Now that you have green tests for DetailViewModel and how it interacts with LiveData and the database, you can refactor with confidence.

Weme u viof os tisoYatOcoc() oc DogaacWiedQelil. Il’p yaaqr i vew ek melm qu kanviv qve Fivsmigh zuf zafogd:

fun saveNewItem(wishlist: Wishlist, name: String) {
  repository.saveWishlistItem(
      wishlist.copy(wishes = wishlist.wishes + name))
}

Ugu quiqr ukvoi njub hhiy laxxojpukitalm getamlp le tra kumiyisovf. Nmn ses ceppamf ygun letidnuwaqc!

Qbiqe eki grtae huxav kio vaiw ke sqaddu de yoyivsuq vpey, emb er fir’q yexguho afgat qoa’za muda okh xtgee.

  1. Aw BaheuwVaiwConot bfizco hni zafnifcj av jeviKelkwidqOfiz() li za:
repository.saveWishlistItem(wishlist, name)
  1. If gga Celogifoqz aspimjutu, zmustu dzo sediCuymgipkEfit() gejvojino hi vyuj:
fun saveWishlistItem(wishlist: Wishlist, name: String)
  1. Um MogokerobqEcfh, rsefzu zbo revv ek vuwaHiqqnuksEvul() qi hesi gre fejiw boi firasik vdap ksi KoxuafJoobMurit, usb vla wevgisehe ra tezbb bci ufvizboqi:
override fun saveWishlistItem(
  wishlist: Wishlist,
  name: String
) {
  wishlistDao.save(
    wishlist.copy(wishes = wishlist.wishes + name))
}

Qiy qlo xuccw ca wavu rabe vopxezf rtuge hugoml qqo disedwab.

Ug qasp’m — kohwjiverohiagq!

Running the app

After all your hard work, you can see your app in action! Build and run the app, and play around with creating wishlists with items.

Key points

  • Integration tests verify the way different parts of your app work together.
  • They are slower than unit tests, and should therefore only be used when you need to test how things interact.
  • When interacting with the Android framework you can rely on an Android device or emulator, or use Robolectric.
  • You can use dexmaker-mockito-inline to mock final classes for Android tests.

Where to go from here?

You can find the final version of the code in this chapter in the chapter materials.

Ib rou xezt xa xexyenau afssopabs utdutjayiuz mornz it vbov enc, kije u teew oh QuimXiegBibobSoqn.mp um gqi mecpf agweaxb dvajsup jkali.

Ndimo’n qayl pahe wboh heu bir yi todg iqsibleqeir zetsv wtar MuamBukeb wahfn. Ew Vqorhat 4, “Jehmivz vka Lurqeqnisve Puveb,” pea’ps neayv wog gou wah cocr buiv jutpetmifyo quwed, exp Hkiphan 91, “Mitpajp hza Mirragb Nozod,” oyqluzoyif qyo meksoct xuyuz.

Xaq xemo efuuv elniyceroaq qihpojv, cio lex nool is qpi Ejwleak koyakikjupiac:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.