The Best Way to Unit Test in Android: Part 1
I’ve been doing some development in Android lately on a top secret project, one that hopefully will change the way you run with your phone.
In the course of building this app, in a previous post I mentioned that I wanted to find the right, or perfect way, to build an Android application.
I haven’t found the best way to build an Android application into a nice clean design pattern, but have found a way that seems to work, and makes the application testable and easy to maintain.
I do believe though, that I have found the optimal way to unit test in Android right now. Yes, a bold statement, but I have looked high and low for a better solution, and can’t find one.
So first a little bit of background on the problem with unit testing in Android. Take what I say with a grain of salt here, because I am not an Android platform expert, and feel free to correct me if I misstate something.
If you download the Android SDK from google, you will find that the android.jar you get with the SDK is much like a C++ header file; it doesn’t actually contain any working code. As a matter of fact, all the methods are stubbed out to throw an exception with the message “Stub!” when you call them. How cute.
The real android.jar implementations live on the emulator, or your real Android device. So, if you want to actually run any code that is going to call any methods or try to construct real Android framework classes, you must run that code inside the emulator or a real device.
When you're working with Android, it sure feels like you are writing regular old standard Java SE code, but you are not. You probably won’t notice the difference, as almost everything that you need is there in the Dalvik VM implementation of Java.
Dalvik is not even a Java Virtual Machine at all. That is right, it runs on cross-complied .class files which are converted to a .dex format. Yes, it does not use java byte code.
Why do I mention all this? Because you might want to use something like JMock to mock your dependencies when writing your unit tests.
Well, you can’t. It just isn’t going to work, because the Dalvik VM doesn’t use java byte code, so the reflective coolness that mocking frameworks like JMock use doesn’t work the same.
Be aware that any external library you try to use in your Android project may not work because Android does not support the full Java SE implementation. It actually is a subset of the Apache Harmony Java implementation.
There is no main()
Where does an Android application start running? From the activity. Which is basically the view. (Some people will argue this is the controller or presenter. And yes, I agree in respect to the Android framework it is, but in regard to your application framework it is the view.)
Android applications define a starting activity and launch from there. Activities can even be launched from other applications.
This tends to disrupt most of our MVC, MVP, MVVP pattern architectures, as the view is going to be the entry point and will have to be responsible for initializing the rest of the application. (That is not entirely true, as there is an android.app.Application class that gets called the first time your app is run, to launch the main activity, but for the most part you can’t do much here.)
Essentially though, you have to build your architecture based on each Activity being its own separate little application, with the entry point being in the activity. This puts some serious constraints on unit testing.
Putting it all together
So if I can sum up the problems briefly, I would say:
- Android framework code has to run in the emulator or on a device.
- Dalvik VM doesn’t allow us to use our standard mocking frameworks.
- Entry points for our applications are in the views.
The first problem, combined with the second, lead us to an interesting choice. Should we run our unit tests on an actual emulator or device using the Dalvik VM, or should we run them in a JVM?
It is probably not an obvious question, but let me explain why it is the most relevant.
In writing an application, we are going to have application logic that has nothing specifically to do with the Android platform, and we are going to have Android platform specific logic (drawing views, handling Android OS events, interacting with Android APIs etc.)
If we want to write true unit tests, we need to isolate our classes and test them individually. We should be able to do this for our application logic, without relying on the Android framework. If we don’t rely on the Android framework, we don’t need to run on a real or emulated device, thus we are not constrained to the Dalvik VM.
If we choose to run our unit test code on a real or emulated device:
- We will be able to use the Android framework APIs in our testing efforts. For example, we can create new location objects instead of mocking them up.
- We will be completely true to the real execution of our code since we will be using the real VM the code will run on.
- Since we are running our tests on a device or emulator, they will run much slower.
- We won’t be able to use JMock, EasyMock, or Mockito, we’ll either have to roll our own mocks or use a fledgling Android mocking framework.
If we chose to run our unit test code in a JVM on our PC:
- We will have the full power of the JVM available to our test code, so we can use mocking frameworks like JMock, and BDD frameworks like Instinct.
- We will run our unit tests much faster, since they will be using our PC instead of a device.
- We can use standard unit testing practices and not have to inherit from special Android classes, or use special Android test runners.
- We will have to wrap any calls to the actual Android framework if we need to use any Android classes or services deeper down in our application.
- We have a small risk of having different behavior between running the tests and the real application because we will be running the code on different VMs.
In my next post, I’ll detail which option I chose and why and also give some detailed steps of how to get setup and running.