What is Test Double?
Test Double:
We might have heard about the Term Stunt Double in the movies. Who are they? They are the one who just look like movie character but they are only for the stunts. But what we see in the movies, it looks like real actor is performing stunts. This exactly what test double means. Test doubles are the one that just look like real data but they not real data, they just acts as real data during testing. But why need it from the first place? Why not just using real data and test it? Well, the answer is “Testing Should be fast”. Let’s take an example of retrofit, if we start fetching the real data in testing (somehow) then what’s the reason of testing? Its gonna take time to fetch the data. We don’t only test either the data is retrieving or not, we test every possible use case so our final product won’t contain bugs or errors. Imagine for once, every use case we are testing if we have to fetch real data from server how much time its gonna take? well it’s not gonna only take too much time but it’s also be the waste of time. This is the reason we use test fakes or test doubles so we could test our classes or unit of code with the fake data which acts as real data and ends up making test so fast.
How to make test fakes?
We make fake data but the data is coming from where? Take an example of MVVM architecture we have Repository Pattern which is known as Single Source of Truth which gets the data from data sources: network data source and local data source and loads the data from local only, that’s another topic to discuss. Here I’m not highlighting the repository pattern but actually what i’m highlighting is data sources. What are data sources? These are the source of data which is coming either from local or network but in our example it will be only local data source and we will be making test fake of this local data source.
Let’s raise some questions, why we have to make data sources why not just putting everything inside the repository? doesn’t that make it extra work? When we build the application we have to make sure everything shouldn’t be too much tightly coupled to each other for example if we make little change to some functions and whole project gets broken this is the reason we have to follow Separation of Concern. It’s nice if we give as much as abstraction we can. Most of things should be loosely coupled it makes the code less complex in understanding the code, testing, in future if we upgrade the app so the code won’t become spaghetti. That is why we make data sources that provides abstraction.
How do we make data source? Data Source is nothing but an Interface that consist of functions which are the use case of our application. The reason of using interface it provides good abstraction to our code because it only gives function abstraction not the details of it which is the good thing cause during the Test we will be deciding how and what type of data we will be dealing with. We implement the Interface for actual data to access it from local database for example room or from network using retrofit but in test fakes we make fake implementation in our test directories to access fake data.
Remember: Local data source and Network data source are two different interfaces and never Confused them with one another.
In our example the Local data source is based on following functionalities:
1. C(reate) R(ead) U(pdate) D(elete) or CRUD.
2. Search the item through ID.
3. Retrieving whole list.
4. Search Item through Title.
Pro Tip:
I always recommend you to have these two functions in you data source:
- Search the item through ID
- Retrieving whole list
These two functions are most common functions that are always needed in testing in almost every single use case.
Let’s make data source:
First we need data class for Note, in the root directory make model package and inside model package make data class Note.
Note.kt
data class Note(
val id: String,
val title: String,
val content: String,
val timestamp: String
)
Make the data package and inside data package make another package called localdatasource and inside localdatasource package make an interface called NoteDataSource.
Why i made localdatasource? cause we could have networkdatasource as well that’s why i made this extra package. This is just to demonstrate the organized package structure of since this article is only gonna stay up to localdatasource you can completely omit extra package and stick with data package only but i suggest just do as i told, make localdatasource as well.
Add the following functions in your NoteDataSource:
interface NoteDataSource {
suspend fun insertNote(note: Note): Long
suspend fun deleteNote(note: Note): Int
suspend fun updateNote(pk: String, title: String, content: String): Int
suspend fun searchNoteById(id: String): Note?
suspend fun searchNotes(noteTitle: String): List<Note>
suspend fun getAllNote(): List<Note>
}
Your project structure will be like this:
There you have production code which is your actual data source, let’s make fake data source.
Go to the test directory and right click on the root package and make data package and localdatasource packages in the exact same way the way we did previously in the main directory, like this:
Inside the localdatasource (test directory) make a class called FakeNoteDataSourceImpl and implements NoteDataSource interface and implement all of the members.
Finally, it will look like this:
Now we have fake data source. which contains insert, delete etc these are those features which our will be consists of. But here is a thing to be noticed our function doesn’t contain any body yet. This is because we have source of data but we don’t have data yet.
Let’s talk about data, we don’t necessarily need to save permanent data somewhere all we need is just some way where we can save data temporary. We can use Hashmap or ArrayList for saving temporary data and it’s not mandatory, we can even use LinkedHashMap. In short i would say “we have to make such mechanism where we can save data temporary into it for testing ”.
Let’s do it:
Using HashpMap:
Using ArrayList:
Our fake data source will look like this. As it is mentioned above it’s not necessary either we are using hashmap or arraylist or any other mechanism.
Conclusion:
Test doubles are really good approach in Testing. It provides us production code scenario in testing. One of the best things about test fakes it helps us to go through every possible use case when dealing with data but not only the data also errors, exceptions on failed to retrieve data or sending, saving data.