Skip to main content

What I learned from unit testing



Testing, testing testing. I was getting inside the "Fear of getting behind" every time I heard that word. So I decided to react quickly. I knew nothing about testing and this is my experience getting my hands dirty with it. Please feel free to correct anywhere I'm wrong. This is a reason why I'm writing blogs.

So what the hell is testing?

Testing is just a piece of code where you invoke your written production code and check its' behavior.

So lets get more specific. In android there is 3 kind of tests:

  1. Unit testing:  tests that validate your app's behavior one class at a time
  2. Integration testing: tests that validate either interactions between levels of the stack within a module, or interactions between related modules
  3. End - to end tests: tests that validate user journeys spanning multiple modules of your app
Definitions from the official Android documentation

This article is about the first point , but you shouldn't ignore any of them.  The other 2 kinds, also need a real device or an emulator in order to run. Unit testing don't need a real device to run. 

The setup: 

JUnit is a nice testing tool for unit testing. So we need the dependency:

Unit test on Android run on the test package. Which is the same as your package name but greener. The IDE will show that for you. Don't confuse the test with androidTest because the second one is not for this case but for UI and Integration tests.

So what I'm going to test?

The simplest case is a repository from one of my personal projects.

So what does this repository do? It has a suspend method which calls some data from a public API (using Retrofit), a method which saves my data to SharedPreferences (ignore the rocket naming, it's a refactored SharedPreferences class) and a method which just reads from SharedPreferences and returns true if one of my read values are empty. 

Now let's start testing my class. On the test directory create a new Kotlin class. Let it be empty in the begging. Annotate the class with @RunWith(JUnit4::class) and create 2 methods, setUp() and finish():


This is the basic setup. But don't run the test yet, because it won't fire. It needs at least one test case. What would be the simplest thing to test here? Looks like it's the saveCountryToSharedPreferences. But I said that unit testing doesn't run on a real device. How in the world would you access the SharedPreferences. I wont. I will introduce you to test doubles: 

My definition for Test Doubles is : Objects you need, but you don't even care about who they are. To make it clear, this is the definition of Test Double by Google

A test double is an object that can stand in for a real object in a test, similar to how a stunt double stands in for an actor in a movie. These are sometimes all commonly referred to as “mocks”, but it's important to distinguish between the different types of test doubles since they all have different uses. The most common types of test doubles are stubs, mocks, and fakes.

I'm going to stop at mocks. You can build your own mock or you can use mocking libraries. Java/Kotlin applications have a library called mockito which is awesome and super easy to  use, without caring to create mocks on your own. Along with that, I will add mockito-kotlin for more syntactic sugar and easy usage for testing my suspend methods.

Implementation:

So, I don't care about my Rocket nor my TreasureApi class/interface. But I need them to instantiate the SetupRepository class. Let's mock:

Let's start testing that method now. One last thing: Unit testing is based on a triple A rule: Arrange, Act, Assert. Sometimes you might find some codelabs using the Given, When, Then keywords but it's pretty much the same.

Arrange: Let's pretend that something will have a certain behavior.
Act: Let's call the method.
Assert: Check if the selected behavior matches your expectations.

So, let's pretend that something will have a certain behavior. Since I want to send a country as parameter inside that method I will create a fake one: val country = Country("Albania", "Tirana", "blablabla.com"). Let's call the method, just like you do in production code: setupRepository.saveCountryToSharedPreferences(country). And now let's check if my fake object has been executed on my mocks (with the same content). For that we will need the help of verify operator from mockito: verify(rocket).writeString(COUNTRY_SHARED_PREFERENCE_KEY, country.name). Let's check the full method: 

Run your test with the help of IDE. You will get notified if the test is correct it the IDE shows a green light:



Since you can see my method, the test should go fine, but if the tests fails for some reason you have to check the method first. If you are sure that the method is correct, you have made wrong assertions.

Let's check a little harder implementation:

I want to check if my makeCountryApiCallAsync method returns an error, when my retrofit api returns an error. But you are not making a real call, how do you know what the server returns? I don't. But I don't care for the real response, so I'm gonna use mockito to fake that response for me. So let's pretend that the server brought some error: 

A small detail. since method is a suspend method, you should wrap it in a runBlocking block. After we call the method: val apiResponse = setupRepository.makeCountryApiCallAsync(). After that we need to assert that the response code, is the same as the simulated response code: assertEquals(400, apiResponse.code()). And the full methoud would be: 


And pretty much that's it.

Note: Don't really test only one case. One method can have lot's of cases to simulate.

A small heads up for naming methods: Test methods should be as clear as naming production methods. Perhaps wrong named tests, would confuse you more than you might be. Another thing I want to mention is that my naming pattern is not the best approach, but I find it nice like that. Name the method according to this pattern: <tested entity>_<conditions/state during test>_<expected result> .

Small talk about Test Driven Development: TDD means writing tests before production code. Since I learned testing after I created most of my project, I would skip TDD on this one, but i believe that TDD is the masterpiece of programming and everyone should use it as a technique.

Conclusion: 
Unit testing speeds up time of development. Also unit testing helps you catch more then 70% of your bugs in business logic. Please don't forget to learn all kinds of testing, but this one is the one you should start.  There were parts in my development life, where I couldn't simulate the behavior to reveal the bug, and so I ended up rewriting methods, classes or even packages. That's what unit testing taught me.

Popular posts from this blog

Modularizing your Android app, breaking the monolith (Part 1)

Inspired by a Martin Fowlers post about Micro Frontends, I decided to break my monolithic app into a modular app. I tried to read a little more about breaking monolithic apps in Android, and as far as I got, I felt confident to share my experience with you. This will be some series of blog posts where we actually try to break a simple app into a modularized Android app.

Note: You should know that I am no expert in this, so if there are false statements or mistakes please feel free to criticize, for the sake of a better development. 

What do you benefit from this approach:
Well, people are moving pretty fast nowadays and delivery is required faster and faster. So, in order to achieve this, modularising Android apps is really necessary.You can share features across different apps. Independent teams and less problems per each.Conditional features update.Quicker debugging and fixing.A feature delay doesn't delay the whole app. As per writing tests, there is not too much difference about…

What I learned from Kotlin Flow API

I used to check the docs and just read a lot about flows but didn't implement anything until yesterday. However, the API tasted really cool (even though some operations are still in Experimental state).Prerequisites: If you don't know RxJava it's fine. But a RxJava recognizer would read this faster.Cold vs Hot streamsWell, I really struggled with this concept because it is a little bit tricky. The main difference between cold and hot happened to be pretty simple: Hot streams produce when you don't care while in cold streams, if you don't collect() (or RxJava-s equivalent subscribe()) the stream won't be activated at all. So, Flows are what we call cold streams. Removing the subscriber will not produce data at all, making the Flows one of the most sophisticated asynchronous stream API ever (in the JVM world). I tried to make a illustration of hot and cold streams: Since I mentioned the word asynchronous this implies that they do support coroutines also. Flows vs…

From Gson to Moshi, what I learned

There is no doubt that people are getting away from GSON and I agree with those reasons too. The only advantage GSON has over other parsing libraries is that it takes a really short amount of time to set up. Furthermore, the most important thing is that Moshi is embracing Kotlin support.

First let's implement the dependency:
implementation("com.squareup.moshi:moshi:1.8.0") It's not a struggle to migrate to Moshi. It's really Gson look-a-like. The only thing to do is annotate the object with @field:Json instead of @SerializedName (which is Gsons way for JS representation):

data class User( //GSON way @SerializedName("name") val name: String, @SerializedName("user_name") val userName: String, @SerializedName("last_name") val lastName: String, @SerializedName("email") val email: String ) data class User( //Moshi way @field:Json(name = "name") val name: String, @field:Json(name = "user_name…