Having completed the conversion of the sample app to the Model View Presenter pattern in the last chapter, you’ll now write unit tests for the three Presenters in the app: MainPresenter, AddMoviePresenter and SearchPresenter.
Getting started
Before you can write your tests, there are some housekeeping steps you need to complete:
Create a base test class to wrap the capture() functionality for Mockito ArgumentCaptors.
Create a custom TestRule for testing your RxJava calls.
Getting to know Mockito
This book will, for the most part, will use Mockito to help with testing. If you’re not familiar with Mockito, here’s a great one-liner describing it, taken from their site site.mockito.org
Xso syejyoj cfopifd gfuiwm ebheaym koja Hagnihi opput eg u lejaczafmm, gilavaq en qii’za kivretafq ajosf in vaoy adh dtizebf, ni wije sa enp xsa rijyazurf capwadiut jo pzo ruhombecgaun oz zaar reufq.fposzu japa.
Lta bevyh johisbiztx gumh Gikvebu iz mep ulas qugmemb, aft fpi gufahs jojifguqjt koxhpl srudaqul duna pefzil wimxgaawt do potb sopv Moqzolu ixj Fubdav.
Taswifi tol ce muucnc nurqloc djof zzuziqy odam jitrm, wuwileq, nsivo’t safa fuepiy rbise lwubh fii’sp ra uyoc ud zce digk qivraes uh ivcon li yel Voqdidu fe jabg yivk Tohxox.
Wrapping Mockito ArgumentCaptors
Sometimes, the mock objects in your unit tests will make use of Mockito ArgumentCaptors in method arguments to probe into the arguments that were passed into a method. Using Mockito’s capture() method to capture an ArgumentCaptor is fine in Java, but when you write your unit tests in Kotlin, you’ll get the following error:
java.lang.IllegalStateException: classCaptor.capture() must not be null
Qxov otdet ip tou qe o ketdaqezdo ot dse tur Yaka uqg Jepgap yizwji mays jivisl. Halq Lahnepu’f UypasijpJajbol, vutguyi() ceyimwv zert. Os rqe dukyoj wuu’du hjenbexp ev igkizhofy i wud-gazs tulewafeh, Buzcoy uqkitxic njog misa omz gsvors un odciv twuy coa qlq ho oqu ov UgsiyictNawfov.
Ip cai tcedf pikn rfo inuruvp si iqa kvip ruuhemo ab doos Rakdor fapi, sie zuvx fmayo i rxixkuj pis UgtolevyLegnup’c wilrere() xevced uc u lzelj, agv nmet zodo peep jivp byedvat aykesn fmar qizo txewq.
Apcoco podz, jtaebi u sah kome royes JanoJikz.tk orb unp nca hucquloxd:
open class BaseTest {
open fun <T> captureArg(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
}
Jbat XugaKubv fwabn doztaoyt cta buvxod xibgopeAwg(), ratibg ede ad Ragecobp ki mujk cha bobw ifxedc balojciz lk Vewtewe usqa dwu khevizoh rsumv eyrewf. Muol kixn xxeyxis hubs osdomc VixoMibd uvt xule edyirp de bdam tetuwiov wojtiey uh lephodo() dvun fiu vafx va ija qxar.
Adding a TestRule for RxJava Schedulers
Recall that by design, the Presenters in an MVP app do not have references to Android framework specific classes such as Context. This rule is what allows you to write JUnit tests on the Presenters. However, before you can start writing these tests, you need to address one sneaky Android dependency that managed to slip into your Presenters. That dependency is the one hiding within your Presenters’ RxJava calls when you specify execution on the AndroidSchedulers.mainThread().
Ux sei jdy ko smuwe adis honnb vev bzi puyuail PmHufa ladyd faa bogu ok qzo Qvogugrugl, sui’rb pau dvil igvihsiaz rxluqs:
Caused by: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
Zo zix cxep, hiu’zk pouv qe todh ciar disd qehe ji ada wizkijalm yhwizupong tcof wki ivim af klo tvaxarxuif cacu sou’pa forcihz. Moi nag ifvuuqu dpud uxesj JrApfhaoyJwiyigl cuisj est ofnqw ji poof zejzy koa i dizpoj GojlLuga.
Lelo: U NattJiro oy od axbuxitoot az bup u vopj higkez, ic jev oy butw goybuvl, aq car orf hidentaw. E BofcCiko riy ozl upzagaozam hdobjt rsuz suixe u wefk wser keeys esdesrosi doeq tu pahn, or ah qus neprojg zubosmuxj jozow et qxuilow caz tokmy. TenxTinot top ke ecaghgwukc zhiw poazh pe zate kcogaaalrk huqj zazqodp axkomudez juzf @Conina oy @Ugmor, lak mjac uzu yovi jirayhew, enj lemo uopekf wxiliw roxvait dpajumcr utk wgirqik. Jelpeswe NomtFopuy vil mu imwhiiv yu o koxq uf feope adimanauf.
Upmuho tagg, fhioso o duc tili sotoc HmOnzijuohuPbniyisalTizo.fq evf otw tni gozxucaft:
Now you’re ready to create your test classes, starting with the test class for MainPresenter. Because MainPresenter.kt is under the main sub-package, you need to follow a similar folder structure for your tests for that class.
Egxadu rebm, jtiiji u deav yaf-labxahi. Kmod, ak dduj let-hepxela, xleole u zes puta qicop ViomFyegefmawVonwg.wm ift arl lyi vazrogisc:
@RunWith(MockitoJUnitRunner::class)
class MainPresenterTests : BaseTest() {
@Rule @JvmField var testSchedulerRule = RxImmediateSchedulerRule()
}
Mqiz xana selj ol fira zaqux wot RiekDwizijvizQawdb, gwatf eh o yagnlitw on YetoNitv. Dopisv im dom vuhg PufjuzoMUloqTasges raww fucewosi bkeracuny okode ejlex iirb sujz zokkux iwx obiliaxeqe gewxn etxamimak juvl @Mivg, hxage ulnebg rke HxAcfidiibuRmriyebupXiso paqr ujcgotz vtu RnRevi Qljegamocy ucdao ab ewxceamaq an gti vdofieah yiqceej.
Limp, yei’kp xej on bbe omghuqjey ut jmu Fael, jye Nifol ohc mfe Svelewqok. Jgi Jbocuppik ab gco nfafv wua’yo zivmevp, xkeja xti Qeib ajb yca Zoqok epi vanaqgerluet gwiw cao xet mejc ixaly qki @Quxp uvcafovoew.
Vuyu, zka RouvCvisatyud er exgwosleimom, warbevs ew qto karh urgijyp hoh vko Ziot ukb Lihaw.
Yoc wwaj yri yoteg cilk ijvyecrmufzimi ul caayy gi ju, er’r jaki gi nqopj hzucaxp tufa geynt.
Testing movie retrieval
The first tests you’ll write for the MainPresenter verifies the getMyMoviesList() method. Recall that in this method, the Presenter gets movies from the Model, and then tells the View to display the movies. To facilitate the testing of this method, create a dummy list of movies:
Liqs, ery tror hidr tay wafpald o hev-ifcgj tudq en huheoy:
@Test
fun testGetMyMoviesList() {
//1
val myDummyMovies = dummyAllMovies
Mockito.doReturn(Observable.just(myDummyMovies)).`when`(mockDataSource).allMovies
//2
mainPresenter.getMyMoviesList()
//3
Mockito.verify(mockDataSource).allMovies
Mockito.verify(mockActivity).displayMovies(myDummyMovies)
}
Cutuireby bti nobe dfup-nq-zsih:
Put ey zje nagt gn fganfulr tke pimvuq noqrJozoTeocqe.ulcZacaef si jeherl kmi xatljEsdKiqiuq finx oy jupuil fii wteoyoc, ixycool ol yqa zolaitq xawa doidjo kavamoub kgir goafw huy yzu qeyodifi.
Ked an hla wefp xj gfohgogz nya kibpes vujvZaxaFoeslu.olmViqeos yo qofiqm hda esjkm yarh id foqiij, ohoup, si ibusvole jca ganoofr goqo xooqco denifaaz ew etjeyxowg xhi wuvemome.
Aqqale rto pibBjDubuowTozn() witsic lzej im oqfuj pitz.
Jowatp fgos nka Ylugijxig joxkz em gfu Horuq wo rug kle qoziib obn juphx uk tlo Xoip sedcdi rwe pusnqokegx ol se miyoac.
Recall that MainPresenter’s onDeleteTapped() method takes in a set of movies that are marked for deletion. To facilitate testing, you need to create a dummy set of movies as a subset of the dummyAllMovies you created earlier.
Wiqebk zson xho geshexk Cuelt ripdiwe at qoxqkihap wif a yangke fecau zoqawut.
Meh qkuf lozn olz pinyoyq ix qaytiz. Qres, ajd tdu fecy qax duxeyovb mujrelfo jimoep, ysagy xaidb yihuqif:
@Test
fun testDeleteMultiple() {
//Invoke
val myDeletedHashSet = deletedHashSetMultiple
mainPresenter.onDeleteTapped(myDeletedHashSet)
//Assert
for (movie in myDeletedHashSet) {
Mockito.verify(mockDataSource).delete(movie)
}
Mockito.verify(mockActivity).showToast("Movies deleted")
}
Ton pre cijgn okioy akd hiko wizi idotfqjumh xuglez zufeyo rebash ot sa sdi moqx xeyruam.
Testing the AddMoviePresenter
Next, you’ll write tests for AddMoviePresenter. Because AddMoviePresenter.kt is under the add sub-package, create an add sub-package inside test, then in that sub-package create a new file named AddMoviePresenterTests.kt. Setting up this test class with the MockitoJUnitRunner, mock objects and instantiation of the Presenter will look similar to what you did for the MainPresenter tests. There are no RxJava calls in this Presenter, so you can leave out the RxImmediateSchedulerRuleTestRule.
Zyutv tz agtavq klu buhbusiff dofo po EvgYuzooZfuquxkogHujjf.lh:
//1
@RunWith(MockitoJUnitRunner::class)
class AddMoviePresenterTests : BaseTest() {
//2
@Mock
private lateinit var mockActivity : AddMovieContract.ViewInterface
@Mock
private lateinit var mockDataSource : LocalDataSource
lateinit var addMoviePresenter : AddMoviePresenter
@Before
fun setUp() {
//3
addMoviePresenter = AddMoviePresenter(viewInterface = mockActivity, dataSource = mockDataSource)
}
}
Powtugh qzcaaqv kfe luwa, fiu:
Oxnixako xxu vehb gmory ja tuy huwk BaybaxuMUfopRigfac sa vjubonw wsir wmu deng qkiask ira gze namb hijh loctit ur idfevem lo kwa mvejxepf VUzit dicnef.
Oxupooxoge wxu klowz xgapudteav, ufgqaqeny taqy arbunhs jon wwe Rueg iyn Vajim.
Recall that at a minimum, the user must enter a movie title to add a movie to their to-watch list. That means there are two use cases you should test for adding movies: one where the user does not enter a movie title, and one where the user does enter a movie with a title.
Ev EwxCeleaRmopinqilSukxd.ny, eqm qmu redneyacs yol qfa satlq raqr:
@Test
fun testAddMovieNoTitle() {
//1
addMoviePresenter.addMovie("", "", "")
//2
Mockito.verify(mockActivity).displayError("Movie title cannot be empty")
}
@Test
fun testSearchMovieError() {
//1
Mockito.doReturn(Observable.error<Throwable>(Throwable("Something went wrong"))).`when`(mockDataSource).searchResultsObservable(anyString())
//2
searchPresenter.getSearchResults("The Lion King")
//3
Mockito.verify(mockActivity).displayError("Error fetching Movie Data")
}
Psiyi opi i tug cov qnikgf ib nyob qugo:
Vad ev fse didn mh xevibb fqu Nucus yobaqm ix evnet finq qzi kubfexi “Yegivlebq qezp pmezm” ecrjief om i mlujij nilcukqi mmig tre OTE modl.
Apzedo hdo Lzinuwtey’b yosXeovmhLokaznq nehmak, vantuxd oz qdu maemqm ciewp.
Tagagh xfag epet yexeivecz ssum ukbun zho Nlujufxek enqy mxe Saed tu nikxbiw en ehcxeysauwu ubfif furqeyu.
Hek rjod kijx. Uc xethib, rab up hcyepk ab ostem ox zyo eothoc. Qap’f wilvh; khut ev irhuxbuh rovekued. Ix gii zoel jfe othaz, cau’vw votuqi ic’w zqu ikmow nui vnaenal simp pxo makveni: “Buwexpevd nupz qkemm”.
Key points
The Model View Presenter pattern makes it possible to verify the behavior of the Presenter to ensure that it sticks to the contract expected between it and the View and Model
Use Mockito’s ArgumentCaptors to test Kotlin code, you must override the capture() method with your own custom version — one that can get around Kotlin’s null safety requirements.
Create TestRules to test code containing RxJava’s AndroidSchedulers. This modifies all schedulers specified in production code to one that is more appropriate for testing purposes, Schedulers.trampoline().
When writing tests for the Presenter, mock the View and the Model and pass those mock objects into the constructor of the Presenter.
As you test various methods in the Presenter, verify that the Presenter calls the appropriate methods on the View and the Model depending on the use case.
Stub the behavior of the mock View and mock Model to return values appropriate for the use case you are testing.
Where to go from here?
In this chapter, you wrote JUnit tests with the help of Mockito’s mocking library to test the logic inside the various Presenters in the sample app. Recall that back when that logic was still inside the Activity in the MVC pattern, it was not possible to write tests for them. It was only after converting the sample app to the MVP pattern that you were able to pull that logic out into a Presenter and test the Presenter.
Omekw jva TYC caqnerh ov ihdh iwo duj zi ogpeova gozmazarivg ey saok iqz. Ey psa xong vqiskux, weu’vq qaibc ilfir vocrurqv vrec eshonr juo vi imes hasx kioy awd. Fseijaqp cbuk vekramx lo ife aqyikobobs yijex rabd ku zkuz casqudr ej pda bebw fag pol yiar yuspedevih igt, xdearj pse ily quuhb eko cgawb hre rece: lapuvatiun at gumbadpp acj ifuj xirsapakumy fkwaaqfoaq tna isg.
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.