Trendyol Tech
Published in

Trendyol Tech

Trendyol Android Team Unit Test Practice

We have been writing unit tests for about two and a half years’ as Android Team and we have experience at this time. In this article, I will talk about the unit test practice of the Trendyol Android Team.

Firstly before talking about the unit test practice, I recommend you read Trendyol Android App Architecture. The links are below:

The classes we are writing unit tests are colored parts

Template Of Unit Test Methods

  • 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.

We use mockK library to verify the method, capturing, mock class, and a lot of things.

We have unit test rules to swap the background executor used by the Architecture Components with a different one that executes each task synchronously.

ViewModel

We write a unit test for three separate cases in ViewModel :

  • Logical State
  • Error State
  • Verify method

Logical State

There is a ‘temizle’ button on the filter screen. The button is active when the filter is in an initial state, the button is passive when the filter isn’t in an initial state. If any filter is applied by the user then the state changes to not initial. The button on the left image is passive, the button on the right image is active.

How can I write it :

There is FilterToolbarViewState and it has isRightTextEnabled field. The field determines if/whether the state is initial or not. Also there is a LiveData’s type of FilterToolbarViewState in MainFilterViewModel and its value is set in setToolbarClearButtonEnableState method. The method calls from the doOnSuccess extension in the applyCurrentFilters method. Now, I want to test if filterToolbarViewStateLiveData is set with correct data when the applyCurrentFilter method is called. And unit test in that :

Note: If you mock a class with the MockK library, MockK wants to know what that specific method inside the class returns. I define it to MockK with every {} returns. For example in the code above, I create searchUseCase with MockK and I used its method so I declare what the method should return to MockK using the code below:

preparation: The unit test above isn’t initial state. I start the unit test with Given using a comment. After that, I start to prepare for the unit test. Firstly I say the return of fetchFilters method, and then I create a filter model, also ViewModel, and initialize it. isCurrentRequestInitial return of method is false. Then applyCurrentFilters method return filter model. At the end of preparation, I create an observer to observe related liveData. Preparation is over.

execution: I call the method: applyCurrentFilters

verification: I take the first value of observer because one time observed it. And then I check if isRightTextEnabled true because the unit test isn’t initial state. And My unit test passed.

Error State

You can take an error from API sometimes. For example, I took an error on the Collection page when adding a new product to my collection. Now, I want to write a unit test for this situation. Before the unit test how can I write code:

This class is My CollectionDetailViewModel. There are an errorDialogEvent and fragment observe it. errorDialogEvent set value in doOnThrowable extensions in followCollection method. When a user wants to add a new product to his/her collection, followCollection method is called. Now, I want to test is errorDialogEvent set with correct data when followCollection method called And unit test in that :

preparation: Firstly I created retrofitException because the unit test for error state. And I called followColletion method and I gave retrofitException as a return. And then I create an observer. Finally, I added expectedValue

execution: Called the method : followColection(collectionId)

verification: I take again the first value of observer because one time observed it. And then I checked does retrofit message of the observer equal the expected message. And the unit test passed.

Verify Method

You want to make an API call after the success case of another API call in sometimes. In this case, you want to test it. For example: when our product detail screen opens, we make a lot of API calls. But the name of the main API call is fetchProductDetail. If fetchProductDetail returns from API call with success, then we make another API calls.

You see ProductDetailViewModel class. FetchRelatedProducts API call is making after fetchProductDetail API call in success state. In this case, I want to check did the FetchRelatedProducts API call made after success? And unit test in that :

preparation: Mocked product and productId. productResource was set product with success state because the unit test for the success of the unit test. And then fetchProductDetail method called and returned productResource.

execution: Called the method : fetchProductDetail(productId)

verification: I verify did fetchRelatedProducts API call made

UseCase

  • Verify method

Verify method

Verifying method unit test in UseCase class, same as ViewModel verify.

This is UseCase class. There is a logic in fetchSellerComponent method : isSellerComponentEnabled. This logic comes from the A/B test. isSellerComponentEnabled returns true in B case returns false in A case. if the method returns true, fetchAbVersionComponent method is called, else it returns false then fetchAsIsVersionComponent method is called. While made 3 API calls in the fetchAbVersionComponent method, made 2 API calls in the fetchAsIsVersionComponent method. In this case, I want to check have the correct API calls been made? And unit test in that :

preparation: A/B test returns ‘A’ in this case.

execution: Called the method : fetchSellerComponent(…)

verification: Two API calls should be made in this case: fetchProductDetailSellerQuestionsInfo and fetchSellerStoreAvailability so the top two verify the test passed. The third one is productSearch shouldn’t call so it exactly is 0. And also the last one passed.

ViewState

  • Logical State

There is simple logic in ViewState like the visibility of any view sometimes. We write unit tests for it every time.

There are two images above. These images are from the Trendyol product detail screen. The title of the view is ‘Dahili Hafiza’ and the view shows product options. Options of the left view are 64GB and 128GB. There is an info-title in the upper right corner. Info title is determined by the count of options. So, the title is ’2 Farkli Dahili Hafiza’ in the image on the left. But the title is no visible in the image on the right. Because there is a logic in the visibility of the title. The logic is that: if the count of options is bigger than one, then the title is visible, but it equal one, then the title is not visible.

This class is ViewState class and logic(are Count of options bigger than 1) is in shouldShowInfoTitle method. And unit test in that :

preparation: I provide ViewState with two options: 32 GB and 64 GB, for this reason, I expect visibility of title is visible and I set expectedValue is true.

execution: Called the method : shouldShowInfoTitle

verification: I verify actualValue is equal expectedValue and unit test passed.

preparation: I provide ViewState with one option: 32 GB, for this reason, I expect visibility of title isn’t visible and I set expectedValue as false.

execution: Called the method : shouldShowInfoTitle

verification: I verify actualValue is equal expectedValue and unit test passed.

Now we will talk about the different unit tests as a logical state. There are four product cards in the Image above. The product cards have a background depending on the situation. For example, the background of the first two product cards is passive, the third one is selectable, the last one is selected. How can write code:

I have enum class and you see three states: selectable, selected, passive. ProductAttributeBackgroundViewState has getBackground method. The method returns drawable according to state. And unit test in that :

preparation: This unit test is for a selectable state. I set selectable enum state to ProductAttributeBackgroundViewState constructor and I provide ViewState. I expect shape_product_detail_selectable as background and set it to expectedResult

execution: Called the method : getBackground()

verification: I verify actualResult is equal expectedResult and unit test passed.

preparation: These unit tests are: selected and passive. I provide ViewState and set expectedResult.

execution: Called the method : getBackground()

verification: I verify actualResult is equal expectedResult and unit test passed.

Parameterized Test

The parameterized test combines more than one method and makes them one method. The method takes parameters. In fact, it is a normal dynamic method. Using parameterized tests would be more correct sometimes. Because it can be expandable.

For example, examine unit test on above (getBackground method). There are three different options. In the future, it can be more options. In this situation, we should use parameterized test. Let's write the unit test above again as a parameterized test.

preparation: I have one method and the method called three times. I set input and output in provideArguments method. I create the ViewState class in the test method.

execution: Called the method : getBackground()

verification: I verify actualResult is equal expectedResult and unit test passed.

In the future, I add a fourth state: ABC

I add only input and output state in provideArguments, no need new unit test method. If I hadn’t written the unit test as a parameterized test then I should have created a new test method.

Mapper

  • Input-output

We write unit tests for important domains like search - filter. mapFrom method takes two parameters as input and returns ProductSearchResponse. And unit test in that :

preparation: You see, I write the unit test again as a parameterized test. Because there are a lot of test cases. I write the unit test for five different cases. I create filterResponseMapper.

execution: Called the method: mapFrom()

verification: I verify actualResponse is equal expectedResponse and unit test passed.

Decider

Decider class always holds logic.

PopularFilterAttributeDecider has one method: isPopular. The method takes one parameter: FilterDisplayResponse. If the type of FilterDisplayResponse is POPULAR then return POPULAR_BRAND else return null. The decider is used for searchAttributeFieldType. And unit test in that :

preparation: There are two unit tests. I create a decider and also FilterDisplayResponse. I take ‘Popular’ to FilterDisplayResponse constructor for the first unit test and I take ‘Visible’ for the second one. I create expectedValue for both.

execution: Called the method : isPopular()

verification: I verify actualValue is equal expectedValue and unit test passed.

That’s all! I have tried to explain the unit test practice of the Trendyol Android Team. I hope the article has been useful. If you have any questions, feel free to contact me.

Stay healthy 😊

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store