Guide to Trendyol Android App Unit Testing

Murat Can Bur
Apr 16 · 5 min read

A unit test generally tests the functionality of the smallest possible unit of code (which might be a method or a class). This article defines how we implement unit testing flow to our application architecture.


Fundamentals of Testing and the Testing Pyramid

https://developer.android.com/images/training/testing/pyramid_2x.png

As you can see from the Testing Pyramid, there are three categories of tests that can be included in any app’s test suite.

We investigated and decided to include tests from each layer. After the research, we decided to start by writing unit tests. First of all, we try to find out the basic concepts, what are the steps to be followed when writing a test.


Fundamentals of Trendyol Android

At Trendyol, we try to follow best practices and recommended architecture to build a sustainable and less error-prone Android app. We try to follow the rules from Architecture guidelines recommended by Google. Our architecture has simply 3 layers as mentioned : UI Layer, Domain Layer, Data Layer.

More detailed explanation can be found in the shared link :


Unit test code location

Test code location should be under /src/test/java/ui or /src/test/java/domain. We always have to be sure put all of our local unit tests here so that Gradle will know how to run them on JVM only.


Unit testing and our Application Arch.

Before we started to write unit tests to the classes in our codebase, we tried to decide which classes we need to write unit test. After a certain exchange of ideas, we decided the classes to be tested should be: ViewModels, ViewStates, UseCases.

ViewModels

We use ViewModel from AAC to store and manage UI-related data and LiveData to update the UI every time there’s a change. Therefore, we need to make sure that the correct state changes occur at the right time during remote/local data request.

ViewStates

When we are observing a LiveData inside a ViewModel that contains result of our request, we should differentiate between loading, error state, an empty list and success state. ViewStates are responsible for reporting changes to the UI.

We have simply three states :

Our ViewState classes have another job. We use The Data Binding Library to bind UI components in our layouts to data sources in our app using a declarative format rather than programmatically.

We need to make sure that our ViewState methods return the correct values.


Unit testing scenario

Given -> When -> Then
Fetch order contract successfully and show success state


A basic template for each layer.

Think of all the possible test cases.

Every unit test must start with @Test annotation

Divide the test -using comments- into three sections given for preparation, when for execution, then for verification.


How to test inside a ViewModel

We generally make a network request or make database connection inside ViewModels. We always make the correct state changes occur at the right time during remote/local data request.

This rule swaps the background executor used by the Architecture Components with a different one which executes each task synchronously.

@Rule
@JvmField
val instantExecutorRule = InstantTaskExecutorRule()

This rule is necessary since we use RxJava in ViewModels

@Rule
@JvmField
var testSchedulerRule = RxImmediateSchedulerRule()

observeForever method adds the given observer to the observers list. the given observer will receive all events and will never be automatically removed. This means that observer is always active.

orderDetailViewModel.getOrderContractLiveData()
.observeForever(mockedObserver)

How to test inside a ViewState

We expose the information about the state of our data using a wrapper that is called ViewState. As said earlier, We need to make sure that our ViewState methods return the correct values.

Create more readable assertions with Truth

We use assertion library called Truth as an alternative to JUnit based assertions.

Truth.assertThat(errorMessage).isEqualTo(expectedMessage)Truth.assertThat(firstValue.isLoading()).isTrue()


Verifying invocations

When we write unit tests, we use mock objects to mimic the dependencies of logic hosts outside the unit. We do the verification of their interaction as proof that they are being executed, because they do not provide implementation. Most of the time, classes have more than one logic and more than one dependency. Therefore, it is also important to verify that there are no unexpected interactions as much as interaction verification. For this reason, we use a utility called mock collector:

https://gist.github.com/muratcanbur/5b444cbee118da48691b2771daa3c7ba#file-basemockitotest-kt
https://gist.github.com/muratcanbur/5b444cbee118da48691b2771daa3c7ba#file-mockitomockscollector-kt

Detailed description for “verify no more interactions” check automatically for all mock objects (works with Mockito version 2)

https://ufukuzun.wordpress.com/2019/04/09/ne-olup-bittiginden-habersiz-testlere-derman-mockscollector/ (Turkish)


Where To Go From Here?

  • after completing the unit test writing progress, start to write integration tests,
  • investigate how to use Mockk -a mocking library for Kotlin-
  • start to improve our codebase for UI tests in our application


I hope we helped you with this article, if you have other questions, please let us know! If you have liked this article, don’t forget to 👏 it. Thanks!

trendyol tech

Trendyol Tech Team

Thanks to Mert Nevzat Yüksel and Oğuz Çelik

Murat Can Bur

Written by

Blogger, Android Developer

trendyol tech

Trendyol Tech Team

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade