CodeNewbie Community 🌱

Cover image for Testing in Android. Testing the Room database @Insert
Tristan
Tristan

Posted on

Testing in Android. Testing the Room database @Insert

Introduction

  • Now that I have officially released my first app, which can be found HERE on the Google Play store. I want to add more features to my app, however, I think the next best step is to add some tests. So, this series is going to be a practical series dedicated to understanding testing in the Android framework.

Source code

  • You can find my source code HERE

Video version

You can find the video version HERE

A warning!

  • If you want to use the industry standard for testing and creating Asynchronous code, then use RxAndroid. However, my codebase is small enough that I believe a library is not needed. Which means we are going to be using some good old fashion CONCURRENT PROGRAMMING!!!!!!!

Threads and Processes

  • Before we can go any further we need to talk about the two basic units of execution in concurrent programming:

1) Processes
2) Threads

1) Processes

  • A process is a self-contained execution environment, each process has its own memory space. Depending on the environment you might have a single process or multiple processes communicating with each other

2) Threads

  • Threads are sometimes called light weight processes. Both threads and processes provide an execution environment, but creating a thread requires fewer resources than creating a new process. Threads exist within a process and every process has at least one thread. Threads share the process's resources including memory. This makes for efficient but potentially problematic communication.

Processes and Threads in Android

  • In the majority of cases, every Android application runs in its own Linux process(Android runs Linux by the way).By default all components of the same application use the same process and thread called the 'main' thread.

Main thread

  • When an application is launched, the system creates a thread of execution for the application called main. The main thread is very important because it is in charge of dispatching events to the appropriate user. It is also the thread in which the application interacts with components form the Android UI toolkit( components from the widget and view packages). Now when dealing with the main thread main thread there are two simple rules to always follow:

  • 1) do not block the main thread

  • 2) do not access the Android UI toolkit from outside the main thread

  • To abide by these rules we are going to implement worker threads

Worker threads

  • To be able to keep our app responsive and not block the main thread, we make sure to do tasks that don't respond immediately in a separate thread(worker thread). Tasks such as decoding a bitmap, accessing storage, working on a machine learning model or performing a network request should be executed on a worker thread. Do not worry I will go into more detail about worker threads once we start using them.

Creating the test database

  • First, I have already assumed you have set up a Room database and a DAO(data access object). If you have not, you can follow the instructions HERE, comeback and continue once you have both set up.

  • Now make a class and call it TestDatabase:

public class TestDatabase {
   // should be an instance of your real world database
   private volatile CalfRoomDatabase calfDatabase; 

}

Enter fullscreen mode Exit fullscreen mode
  • Notice the volatile keyword, it is used to eliminate the risk of memory consistency errors. Read more about the volatile keyword HERE. Ignore my naming convention of CalfRoomDatabase, replace it with whatever your Room database implementation is called. Now lets move on to creating background threads(worker threads)

Creating worker threads

public class TestDatabase {
   // should be an instance of your real world database
   private volatile CalfRoomDatabase calfDatabase; 

private static final int NUMBER_OF_THREADS = 4;

public static final ExecutorService databaseWriteExecutor =
            Executors.newFixedThreadPool(NUMBER_OF_THREADS);

}

Enter fullscreen mode Exit fullscreen mode
  • The easiest and most straight forward way to create worker threads is through a thread pool and more specifically a fixed thread pool. A fixed thread pool contains a specific number of threads(worker threads). We will then issue asynchronous tasks to the fixed pool, which will store the tasks via an internal queue, which will hold the task until a worker thread is available. If a worker thread is not available then the task will wait until one becomes free to do the task. We create our fixed thread pool with the statement:

public static final ExecutorService databaseWriteExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);

  • We can now have access to threads outside of the main thread to perform asynchronous tasks.

In memory database

  • For testing purposes ONLY we will be creating an in memory database. This kind of database stores information in a memory database(on the device) that will disappear when the process is killed. This means that when the test ends so too will the database.
  • Now for the sake of good testing habits, we will be creating and destroying a database for each test. That way we will know the current state of each database. We will be able to achieve this with the @After and @Before annotations that Junit provides for us. As you might of guessed, any method marked with Before will run before a test and a method marked with @After will run after the test.
public class TestDatabase {
   //... still include other methods
   @Before
    public void init(){
        calfDatabase = Room.inMemoryDatabaseBuilder(
                ApplicationProvider.getApplicationContext(),
                CalfRoomDatabase.class
        ).build();
    }

    @After
    public void finish(){
        calfDatabase.close();
    }
}

Enter fullscreen mode Exit fullscreen mode
  • The @Before method holds all the code necessary to create a database for us. This means that before every test a new database will get created. The @After method will run after every method and destroy the database by calling .close()

The DAO

  • Next we need access to the DAO (data access object). This will give us access to DAO operations which is what we need to test. If you have not created a DAO yet or are unfamiliar with its job, use the link HERE. To get access to the DAO we simply create a method:
public class TestDatabase {
   //... still include other methods
    public CalfDao getCalfDao(){
        // getCalfDao() implemented by Room
        return calfDatabase.getCalfDao(); 
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Again, ignore naming conventions of my code(My app is cow themed). The difference between your code and my code is that you should change out calfDatabase with whatever your database class is called.

THE ACTUAL TEST!!!

  • Ok now that we have gotten all of the details lets create some tests. So, what we are actually testing is the DAO, because that is how we access our database. Create a new class called DAOTest and have it extend our TestDatabase:
public class DAOTest extends TestDatabase {

}
Enter fullscreen mode Exit fullscreen mode
  • Through class inheritance we now have access to everything inside the TestDatabase class. This way we can avoid having to deal with concrete implementations. The next step that we take is to create a test method.
public class DAOTest extends TestDatabase {
    @Test
    public void insertTest() throws Exception{

    }

}

Enter fullscreen mode Exit fullscreen mode
  • The @Test notation is from Junit and it signifies to the system that this method is going to be used for a test. Meaning that the @Before and @After methods will run. Then we can create the test object that is going to be inserted and a test value that is going to be returned upon a successful insertion.
Calf calfTest1 = new Calf(1,"test-1", "TEST 1", new Date(),"Bull","test-1");

int testingReturnValue = 1;
Enter fullscreen mode Exit fullscreen mode
  • Now, it is time to actually get asynchronous by using a Future. A Future is an object that represents the result of an asynchronous computation. The result can only be retrieved using the method get() when the computation has completed, blocking until the result is achieved.
public class DAOTest extends TestDatabase {
    @Test
    public void insertTest() throws Exception{
Future<Integer> calf =  CalfDatabaseTest
                .databaseWriteExecutor.submit(new Runnable() {
                    @Override
                    public void run() {
                        getCalfDao().insert(calfTest1);
                    }
                }, testingReturnValue); //RETURN A FUTURE OBJECT

            int returningCalfValue = calf.get();
    }

}
Enter fullscreen mode Exit fullscreen mode
  • We provide Future with a Integer type parameter because our testingReturnValue is an integer. Next we access our fixed thread pool, CalfDatabaseTest.databaseWriteExecutor. This gives us access to the worker threads which we can assign asynchronous tasks to. We create a task through the submit method and provide it a Runnable .submit(new Runnable(),testingReturnValue), since the Runnable does not have a return value we provide one with testingReturnValue. Inside the Runnable we override the run method and call the task that we want done on the worker thread. Since this code is asynchronous, we need to block and wait for the value. We do that with the get() method. The get() method will block until it gets a return value.

Assert

  • Lastly we can make the assertion that the returningCalfValue is the same as the testingReturnValue thus indicating a successful insertion.
Assert.assertEquals(testingReturnValue,returningCalfValue);
Enter fullscreen mode Exit fullscreen mode
  • With this we can hit the little green arrow next to the method to run the test and see that it has passed.

showing that the green arrow is right before the test method

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

Top comments (0)