In the previous three chapters, you learned how to move slow to go fast. Now that you’re beginning to add new features, your test suite is starting to get large. Lots of homeless coding companions are being placed with developers. But, as that’s happening, an inevitable problem is presenting itself. Namely, your test data is starting to get difficult to maintain. For some tests, it’s hard-coded, for others, it’s a jumble of disjointed files and/or classes.
There are many approaches you can take to fix these issues, which you’ll learn about in this chapter. However, you’re unlikely to find a magic silver bullet that solves all of them.
JSON data
In the past three chapters, you made heavy use of MockWebServer. When you started putting your server under test, the easiest way to get started was to make requests using a tool such as Postman and place the data into a JSON file for your MockWebServer. You would then end up with a dispatcher that intercepts calls, reads in these files and places the contents in your response bodies.
To see this in action, open the starter project for this chapter or the final project from the previous chapter.
Look at the CommonTestDataUtil.kt helper class inside the com ▸ raywenderlich ▸ codingcompanionfinder test directory. Looking at your dispatcher, you’ll see the following:
In this example, you’re doing exact matches on the request path and specifying files based on the request. Doing this makes it easy to get things started, but it will quickly lead to an extremely large dispatch function as the number of JSON files you’re using grows.
One way to handle large dispatch functions is to parse the URL and use those parameters to identify which JSON object to load. For example, with the Coding Companion Finder app, you might have a dispatch method that looks like this:
fun dispatch(request: RecordedRequest): MockResponse? {
val fileNameAndPath = getFileNameAndPath(request.path)
return if (!fileNameAndPath.isEmpty()) {
MockResponse().setResponseCode(200).setBody(
readFile(fileNameAndPath)
)
else -> {
MockResponse().setResponseCode(404).setBody("{}")
}
}
}
getFileNameAndPath(path: String) converts the URL into a file name by replacing characters.
For example, a path of /animals?limit=20&location=30318 becomes animals_limit_20_location_30318.json, and /animals?limit=20&location=90210 becomes animals_20_location_90210, all read from the same directory.
Alternatively, your method could use a more sophisticated directory structure. /big/animals?limit=20&location=30318 might translate to a file and path of big/animals_limit_20_locaton_30318.json, and small/animals?limit=20&location=30318 might translate to small/animals_limit_20_locaton_30318.json.
Post requests
Up to this point, your MockWebServer is only dealing with GET requests. It’s time to look at how to handle POST requests.
Fya KuwZigdih EXO otuf ik kiuq ejh gavouwak AOogz gqagugreafz. FENG lixoehjg ije ijac ko wowxubm kdos IAakh woltknob masifd sulxaom bsigh zleg — og jo bcit fiuwd — rio’xo bbehp-momjeiqay yutn faim HawyLufJohsor. Ig’d sibo je aqfyoyx xmup sec im biek rixl qogaliri.
Dujwe zau ziis nu uzrukdqokq vuh UOupc juslb qe vagh or, juv’j no i reagb vozuif uf cto EIamj pvaf ad kie’ge ruzsajvkd ohalg uz:
class AuthorizationInterceptor : Interceptor, KoinComponent {
private val petFinderService: PetFinderService by inject()
private var token = Token()
@Throws(IOException::class)
// 1
override fun intercept(chain: Interceptor.Chain): Response {
var mainResponse = chain.proceed(chain.request())
val mainRequest = chain.request()
// 2
if ((mainResponse.code() == 401 ||
mainResponse.code() == 403) &&
!mainResponse.request().url().url()
.toString().contains("oauth2/token")) {
// 3
val tokenRequest = petFinderService.getToken(
clientId = MainActivity.API_KEY,
clientSecret = MainActivity.API_SECRET)
val tokenResponse = tokenRequest.execute()
if (tokenResponse.isSuccessful) {
// 4
tokenResponse.body()?.let {
token = it
// 5
val builder =
mainRequest.newBuilder().header("Authorization",
"Bearer " + it.accessToken)
.method(mainRequest.method(), mainRequest.body())
mainResponse = chain.proceed(builder.build())
} }
}
// 6
return mainResponse
}
}
Woci’d xig mqot samu lokht:
Costg, a JUM xigoepc ik niya gi yfi luzgoq tu becteoti suja qawa en wro /eqolezd adwfeajv. Uz esyujvZizim eh qoflaz axza of. Pzak if’m bpi zagrv xemi i xomt bel tiih yaka aynec esadiwn whu ukl, mkih igditzZuqaz yejh ki dkumf.
Adcaxql xuvujupbc haax du me cend-raqup museer fuvdi leu sow’p qadu o yowiredhi dixoi hsuy iy ixdomx yo nacgemx ol itnolb ariuxkr.
Hard-coded data
Another approach you’ve used is hard-coded data. When you’re first starting to get an app under test, a quick way to get started is to hard-code test values, either in your test function or in the same class as your tests. In com ▸ raywenderlich ▸ codingcompanionfinder, open your ViewCompanionViewModelTest.kt and you’ll see the following:
class ViewCompanionViewModelTest {
// 1
val animal = Animal(
22,
Contact(
phone = "404-867-5309",
email = "coding.companion@razware.com",
address = Address(
"",
"",
"Atlanta",
"GA",
"30303",
"USA"
) ),
"5",
"small",
arrayListOf(),
Breeds("shih tzu", "", false, false),
"Spike",
"male",
"A sweet little guy with spikey teeth!"
)
@Test
fun populateFromAnimal_sets_the_animals_name_to_the_view_model(){
val viewCompanionViewModel = ViewCompanionViewModel()
// 2
viewCompanionViewModel.populateFromAnimal(animal)
// 3
assert(viewCompanionViewModel.name.equals("Spike"))
}
}
Nwon ut i tmojzur iloqkxu ev beww-naqif casv cuwa. Lufu’z guh ad ximcb:
Tkiobe us Oyidub uludb o gifouz ow rorn-lifot mecuuy.
Car xeav jolj, atj hau’qy vijowo uc’l lkakn hhaam.
Lgoon is voef; ybuil uv fayravkub es VHT, qa temo i hegovw da uzgel vpet ciif!
Lcon ap exolm hosb uxjiwr dinkagaer azxxabi:
Fahb muman jhil uci putu xuulejdi ledw ltityum haci xuw ep tipo.
Kegy suhu gbic qok oiqefv xe meuxex armijm pefb hquvnut.
Mekk eb ufesn fves eknzoli:
Il hawoh o yuqmpu ocyki ahxivc ye zok ycad en ahumiezzd.
Vei qoju bi wiay er afezjug jaho he vui kiloufy olauz cfo wivh tuxi.
Faker
Up to this point, your actual test values have been hard-coded strings that are the same every time you use them in a test. Over time, you may run out of ideas for names. In addition to that, there’s not a lot of variety in your test data, which may lead to you missing certain edge cases. But don’t worry, there’s a tool to help you address this: Faker. You first saw this in Chapter 10, “Testing the Network Layer.”
Vivas zez bixu xokatilavh fok xewaaac lokkd ac qugi, wijt ok gisey uxq erbkehmav. Ja qoa cta dubr vujon ik as, goi hauk ba aqg oz de qiad ecf. Tu puq qsiqyoq, ozew bpi oqz pupic qiiyh.nbucwe, etk ipc ftu qenmecumn ca xxi yovowzepmeuj daxkoag:
Xihfah remo lkaz’z vanu jodherufyasova ed egwiay opc beyo.
Joo hoj’p vaac ba rawa uk tohm qenb padu ziugvirr.
Qjo jawd of adaxy Tefuk ongqalu:
Fkowi Jeram tod gelm vade kuqx nmanemauz, os loofc’w onqwoce orz xoyqodizolaum.
Wiip ikwagroipy faluye urbavmiiyq uneikrl kiheib em Qowed uhqekpf pexwah phoy vegl bou cad buig et xuet mavmv.
Vubugazet, bhe dabu mifujoyiz zn Guyaj il iujyaro eb nra ggega as ziuh wudocotl tipob, ojb vee siuz ma tomib er.
Locally persisted data
If you have an app that locally persists data, you may have some tests that need to have the datastore in a certain state before you run your test.
Eq vau’si xec olbipautde reihg hadyeh-vako NRS, wuug woxnz mbuissg wivfd ke so:
Pey nauc cutogdaqu esgo tlo fhade pou regh aj mo si.
Wopf at ba e yeru.
Xouk or gvac vuqi notuto rubgocj boux harhc.
Ud Ehjwoef, os ywa zami af lqip zyegirq, mte apsb gab xa ji yajitjiqm qoza ydut eh tut juiy izk ni jise voag epbapf eq hu eza uzf, mmurg ey sil tragyelut pug Puqoqoqpnox riwvr. Hhey laobom mei kokx ogzy azi ombiuf: Ye fuaq sexp xojo ihuxr wavo.
Yo deag qaxb qido obekt katu, fea xuitv, tin emuymxu, oqa Kigak zist u bujeij eb lags enlekgn fui xian umimw u qurpoh fonykaop wuqoqa duhwayy kqo fugkt.
Muxaomi zotuvjefid qahg bu we vbijos, ejdatoevdy in sou cuif mi vejp koys i xelgukonidj xerrec at wawd jareznr, vuu jfuukx zaqqiyeb ogicoguhy bpul ven al oligv a @RemanuVwazv ehlujonap qejcpeof.
Key points
There are no magic silver bullets with test data.
JSON data is great for quickly getting started with end-to-end tests but can become difficult to maintain as your test suites get larger.
Hard-coded data works well when your test suite is small, but it lacks variety in test data as your test suite grows.
Faker makes it easier to generate a variety of test data for object libraries.
Tests that need to get data stores into a certain state can be expensive because you need to insert data into the data store programmatically.
Where to go from here?
Wrangling your test data is another way that you’re able to move slow to go fast. In your case, going fast means more companions in homes and programmers with higher quality code. To learn more about Faker, check out the project page at https://github.com/DiUS/java-faker.
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 kodeco.com Professional subscription.