Android Tutorial

Mocking APIs using Build types in Android Jetpack Compose

Mock prevents Shock!

Saurabh Pant
Dev Genius
Published in
6 min readJun 29, 2023

--

For any Frontend Developer, it is a common practice to integrated apis for majority of the tasks. These apis are created by the backend teams and they take their own time as per the requirement to create, test and deploy these apis for frontend developers. So what should the Frontend developer should do meanwhile? Should it wait for the apis to arrive to start its integration and testing?

Well! The above approach could take longer than expected and seems inefficient in context of time and effort. In this article, we’ll see a better approach of integrating apis with mock data and switching to real apis without changing a single line of code.

Finalise the API Contract

First step in this whole process is to identify the requirement and finalise the api contract that the application is going to consume, with the backend team. Once this is done, document the contracts for everyone access and provide only read access to everyone.

For this article, let’s say we’ve an api which provides us a Joke. Yes Joke! And its contract looks like this

{
"type": "general",
"setup": "Why do mocking works well?",
"punchline": "Mock prevent Shock",
"id": 356
}

So every time we request for a joke, this is what we get. Cool! Let’s not laugh yet!

What our Application does?

Our application is a simple one and it fetches a new joke every time we click on “Get me a new Joke” button.

Expected Structure

The app is quite simple and so does the project. A high level overview of the app architecture is as follows

As we can see, there is JokesRepository via which we fetch our joke. This repository is injected in our view model and this is how the view model looks like

@HiltViewModel
class JokeViewModel @Inject constructor(
private val repository: JokesRepository
): ViewModel() {

private val _jokeFlow = MutableStateFlow(Joke())
val jokeFlow = _jokeFlow.asStateFlow()

fun getJoke() {
viewModelScope.launch {
try {
val joke = repository.joke()
Log.e("Tag","$joke")
_jokeFlow.value = joke
} catch (e: IOException) {
_jokeFlow.value = Joke(
setup = "There is an error",
punchline = "So handle it no!"
)
}
}
}
}

Everything in the snippet above is self explanatory. We fetch joke from our repository’s joke function.

Creating Build Types

Now we head onto our app’s build.gradle file and create two new build types mock and real along with existing release build as follows

buildTypes {
mock {
initWith debug
minifyEnabled false
}

real {
initWith debug
minifyEnabled false
}

release {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

Sync and build the project and we can see four build variants in the Build Variants.

Build variants

We’ll now create two directories within our source directory with same name as our new build types mock and real as follows

These new directories would have same structure as our main directory. Only those files which has multiple implementations, exist in these directories such as our JokeRepository Impl files.

Real and Mock directories structure

We also need to make sure that the file must exist and have same path in all directories. For example our jokes impl files have same path in both real and mock directories.

Mocking APIs in Mock Variant

While the real build variant has all the retrofit stuff for api calling, the mock variant contains dummy data which we prepared with us in the same format of the actual api as follows

{
"type": "general",
"setup": "Why do mocking works well?",
"punchline": "Mock prevent Shock",
"id": 356
}

This json file lies in the assets folder of the mock variant. Now we create a mocking implementation of the JokesRepository for successful response as follows

const val JokeJsonFile = "jokes.json"

class JokesMockRepository @Inject constructor(
private val context: Context
): JokesRepository {

override suspend fun joke(): Joke {
delay(2000) // mocking loading time

// function is defined in another helper class
return getResponse( // mocked response
context = context,
jsonName = JokeJsonFile,
Joke::class.java
)
}
}

Now, from the same joke function, we first create a delay to mock the loading time of api and then we return the mocked response via a helper function.

Now to test the mocking, we need to select the mock variant and the joke returned is mocked 😀.

This was a happy case. In case we want to test the exception handling, we can mock that too by throwing an exception from the mock impl as follows

override suspend fun joke(): Joke {
delay(2000)
throw IOException() // fake exception
}

Now we can tackle this exception during the response handling as follows

fun getJoke() {
viewModelScope.launch {
try {
val joke = repository.joke()
Log.e("Tag","$joke")
_jokeFlow.value = joke
} catch (e: IOException) {
_jokeFlow.value = Joke(
setup = "There is an error",
punchline = "So handle it no!"
)
}
}
}

If we run the app, we’ll see the error lines on the UI.

Switch back to Real APIs

To switch back to real apis, we simply need to select the real build variant and that is it. No code change required and everything is shifted to real world data.

Advantages of Mocking

  • Faster development without any backend dependency.
  • Less bugs as the contract is defined.
  • Parallel development for frontend and backend.
  • Majority of the apis can be mocked and complete UI flow can be tested.
  • Both success and failure scenarios can be handled pre-hand.

Things not to confuse

- The real build variant has all the retrofit regular stuff for api calling but not shown in scope of this article for simplicity but you can check that out in the github repo.

- Mock build variant uses a helper method to parse the json.

- The project is build using hilt, retrofit and a real open joke api.(you can use it too)

Bam! We successfully mocked our apis for both success and exceptions.

You can clone the repo from github and play around with different scenarios.

That is all for now! Stay tuned!

Connect with me (if the content is helpful to you ) on

Until next time…

Cheers!

--

--

App Developer (Native & Flutter) | Mentor | Writer | Youtuber @_zaqua | Traveller | Content Author & Course Instructor @Droidcon | Frontend Lead @MahilaMoney