Building High Quality Android UI: Embracing Test Driven Development with Jetpack Compose
Tags: jetpackcompose, android, tdd, design
Published: 2023-07-18 13:48:22

Cover Photo by <a href="https://unsplash.com/it/@david_watkis?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">David Watkis</a> on <a href="https://unsplash.com/photos/LwRUp8vJJI8?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>

**How can we approach UI development in Android while ensuring we are writing high-quality software from the design spec?** Test Driven Development (TDD) is a way of approaching software development that is very handy for both feature writing and bug fixing.

It provides a path for developers to request more information when needed before writing “just in case” software, also known as [YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it) (You Aren’t Gonna Need It), while fostering the creation of optimized code using the [KISS](https://en.wikipedia.org/wiki/KISS_principle) principle (Keep It Simple, Stupid), I guess? Additionally, TDD is highly effective for writing code that is easy to update. Introducing a new test is straightforward once the suite is running, and tests are always there for you if you accidentally or intentionally decide to update the behavior of your software.

So, if we all agree that TDD is the way, let’s give it a try and attempt writing our UI with it using Jetpack Compose.

---

## Plan
First, let’s consider a common scenario for a mobile engineer: receiving a new design specification for a brand new screen to be added to our app. For this exercise, I have chosen [Taras Migulko](https://dribbble.com/ui_migulko)’s fantastic work on an [E-commerce app design from Dribbble](https://dribbble.com/shots/19463387-E-commerce-app-interaction).

Specifically, we will be implementing one of the UI elements of a clothing list of items, as depicted in the following screen:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtxa6weba8dux64tx5ct.jpg)

Keeping this in mind, let’s set up our development environment. In this case, we will start a new project with Compose, assuming that this step has already been completed for the purpose of this sample.

I’ve created a default project using the templates of Android Studio for an Activity + Compose. Let’s make sure our project is including all the required libraries to do TDD in Compose:

{% gist https://gist.github.com/zurche/95d3ac65488055846d1732bfca751a0a %}

Lastly, if we choose to follow a Test-Driven Development (TDD) approach, it is essential to remember and adhere to [the three rules of TDD](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd) defined by Robert C. Martin, also known as Uncle Bob. These rules serve as our development traffic lights🚦when writing or updating any tests in the app:

> 1. Write production code only to pass a failing unit test.
> 2. Write no more of a unit test than sufficient to fail (compilation failures are failures).
> 3. Write no more production code than necessary to pass the one failing unit test.

By following these rules, we ensure that our development process remains focused and efficient.

---

## 🚦Action
The Design Spec is ready and the environment is set, so **How do we approach a new piece of UI with a test?**

This will depend on each framework used for UI, for this example we’ll be using Jetpack Compose so it becomes relevant to understand how to “[Think in Compose](https://developer.android.com/jetpack/compose/mental-model#paradigm)”

With Compose is easy to dissect the UI into very small pieces that are described as Composables that will be put together to render the full UI.

So the first thing we’ll need to do Is have a close look into the UI to define how can we divide it and go from small to big.

Taking a first glance I’ve identified (at least) five different Composables that will conform this screen:
- A top Action Bar which includes the Back, Name, Item Count and Filter action.
- A main List of clothing items
- Each Clothing Item
- The Clothing Item Content (display name, price and the add to cart action button)
- The Favourite Button

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq7m23lrzpngl8qv8djc.png)

There’s already some added value in doing this as now we are starting to surface both reusable components such as the top action bar and also starting to think on naming for our composables, without even touching a single line of code.

So let’s go ahead and pick the smallest possible composable from our new screen; the Favourite Button.

Assuming this behaves as any favourite button this will have to support 2 states; filled heart and empty heart. Let’s write our first test:

{% gist https://gist.github.com/zurche/0aa11413f66244d5c7ef64d14f04bb9b %}

Worth mentioning at this stage that we’ll be following the suggested documentation on [Testing Compose](https://developer.android.com/jetpack/compose/testing) so we also need to add the **composeTestRule** and initialise our screen using its **setContent** function:

{% gist https://gist.github.com/zurche/11ae8c5a2d400efcae6d875510fe562a %}

Let’s now bring in our test condition

{% gist https://gist.github.com/zurche/9bc33964608b7492d61236cee7f4a7c6 %}

🔴 And when we write our Composable name (that still does not exist) we brake the second TDD rule; the test no longer compiles

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jak2dfarg9wqtsdt0n06.png)

To address this issue we create a new Kotlin file and write our Composable

{% gist https://gist.github.com/zurche/f0ede310cb59748c06712e8c6aa65c43 %}

And we go back to the test

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pp4n1py96p00kmnl3j9d.png)

🟢 Compilation issue addressed! We may continue writing the test now

{% gist https://gist.github.com/zurche/876313ee9d25d648260a359b6b5e3dbe %}

So the test is ready and compiles, next step; let’s run it

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b7dpj93pmonxit1wt2es.png)

🔴 We made it; we have a failing test now. Let’s follow TDD rules 1 and 2 now to make It pass before writing any new tests.

{% gist https://gist.github.com/zurche/d28cf3180229948fd43ac56327331aaf %}

And I know what you are thinking, we are not checking the boolean! We are not supporting the other scenarios, we could write so much more code! But this, this is the exact point where you need to take that hat of and pick the TDD hat If you want to make the switch and **only** think: Are we following all the TDD rules? Yes, then Its ok, lets keep going.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7jqii5gtl6vcd7w65y9w.png)

🟢 Test is going fully green now, this means we are able to write the next test. So let’s try the other scenario our composable needs to support, displaying an empty heart if isFavourite is false.

{% gist https://gist.github.com/zurche/a37c4f10c7b0c5ad14f89a9b118fe628 %}

🔴 And when we run it:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pcawu0fayja25vy7h6bn.png)

So once again, following TDD rule number 1 & 3 we go back to our composable and add the required logic to make this test pass, being careful enough that our previous test will work too this time.

{% gist https://gist.github.com/zurche/5735a5e3a56e4c6953a7bf485ce1bda5 %}

And we make sure to run **the entire suite** now so that both tests are ran:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xt9abo9bgc4eckzwsuh6.png)

🟢 And we can see all our tests going green now!

Finally we can update our tests to make sure that the other icon is not displayed in the incorrect scenario.

{% gist https://gist.github.com/zurche/d1cee9bd3bbddfe756de64caed94510f %}

With all of this in place we now have peace of mind that our Composable will do exactly what we need to, so we can take a pause on our Test writing and go to the Composable itself to make sure It looks as it should by adding Its background, colors and right sizing.

{% gist https://gist.github.com/zurche/6696818792f774c6811a0b7f42162ae3 %}

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/beginbdv5xycjylkga2s.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rk0ku1cb930bpa23krf.png)

---

## Conclusion
As you can see with this process of following just the 3 rules of TDD, stoping when requried 🔴 and moving on when we can 🟢 we can ensure that our UI will work as expected and is fully covered in case we want to update it in the future; a.k.a.: high quality software.

By incorporating Test-Driven Development (TDD) into our development process, we eliminate the guesswork associated with coding and relying on trial-and-error to verify functionality. Instead, our code is designed to precisely meet our requirements. TDD acts as a lighthouse, guiding us to write the necessary code while helping us identify any gaps in the requirements specification through thoughtful consideration.

Moreover, implementing TDD in Jetpack Compose is a straightforward process, as we have seen. This approach resolves any potential issues during the design phase, allowing us to confidently write our user interface (UI) knowing that it will seamlessly integrate with the business logic layers in the future.

In upcoming posts I’ll continue developing this entire screen with TDD for now I encourage you to embrace TDD from the outset rather than writing tests after developing the code. By doing so, you will write less code, think through your implementation thoroughly, and easily identify any missing components in the requirements. Embrace the power of TDD and have a wonderful day!