When doing TDD, or testing in general, on Android there's a lot of time you'll encounter an exception like the following java.lang.RuntimeException: Method {{METHOD}} in {{FQD.CLASS}} not mocked. See http://g.co/androidstudio/not-mocked for details.
This is just saying that a call into the Android system components has happened. There's a gradle flag that's shown at the link in the exception message;
android {
// ...
testOptions {
unitTests.returnDefaultValues = true
}
}
which will prevent the exception.
I have VERY STRONG reservations about this. Doing this allows some code paths to get by. It'll start to mask the actual problem though. You'll start to get NullPointerException
s; because the default for a lot of things is null
which then a method is called on... and BAM! NPE. This is bad; it inhibits further testing around that code flow.
It's a hack to allow bad design.
I like to get the explicit not mocked
exception as it's very clear what's breaking and where. It's the first call into the Android system.
The fix to this is to "wrap" the calls to the OS in a class that can be toggled during tests.
Check out FyzLog Refactor for an example of this.
I also have one in VBM in Android in the series of posts using the DateUtils
.
This approach; I feel; works for any boundary. While I'm against methods explicitly for test code in the deployed code - Boundaries are an exception. Being able to test is more important than preventing test only code in deployable code.
AlertDialog
I've done this before with the AlertDialog
and have a couple ways around this.
Array Store
The first; that I used in an experiment project before is to create a class with an array of AlertDialog
s. Something like (won't compile; consider it psuedo-java)
public class AlertDialogFactory{
public final static void SOME_DIALOG = 0;
/* package */ final static HashMap<AlertDialog> Dialogs = ...;
public static AlertDialog factory(final int dialog){
if(Dialogs.get(dialog) == null){
Dialogs.put(dialog, new AlertDialog.Builder()......);
}
return Dialogs.get(dialog);
}
Then from the test; just shove a fake/mock value into the HashMap
and when the code calls in; it'll return the faked object. This fake will then be called and methods controlled.
What I don't like about the Array-Store mechanism is that it removes ALL of the AlertDialog building into a single class. That's not ideal. It becomes a god class for AlertDialog
s. It breaks encapsulation, simple design, and single responsibility. That class then changes for as many reasons as alert dialogs.
Program to the interface
The next is using an interface - SHOCKER!
I've got together an interface wrapping the functionality of a Dialog
. I've adapted this from a github project. My version follows pretty close?
public abstract class DialogBuilder<T extends Dialog> {
public abstract DialogBuilder setTitle(int titleId);
public abstract T show();
...
}
Inside this I've created slug-implementations
public abstract class DialogBuilder<T extends Dialog> {
/* package */ static final DialogBuilder V7 = new DialogBuilder<android.support.v7.app.AlertDialog>() {
private android.support.v7.app.AlertDialog.Builder builder;
@Override
public DialogBuilder init(Context context) {
builder = new android.support.v7.app.AlertDialog.Builder(context);
return this;
}
}
Additional implementations can exist; like the non-support library version.
Once the implementation(s) are in place; we wrap the DialogBuilder
to set and utilize a specific dialog builder.
This follows a pattern I set in place in the Android Logger for switching between using the android.util.Log
and System.out
.
The AlertDialogBuilder
is what will be utilized in code instead of the AlertDialog$Builder
public class AlertDialogBuilder<T extends Dialog> extends DialogBuilder<T> {
private static DialogBuilder ActiveDialogBuilder = DialogBuilder.V7;
/* package */ static void setActiveDialogBuilder(final DialogBuilder dialogBuilder){
ActiveDialogBuilder = dialogBuilder;
}
...
}
setActiveDialogBuilder
allows configuration of what implementation to use... also known as, "Makes it testable".
While I loathe code specific for tests; there are two aspects in play here. It's a boundary. We play different at boundaries. We need a way to fake it.
It's not a TEST ONLY method. It's a way to configure the class.
I've seen a method in production code (on a singleton, so there's some questionable decisions happening) that was getInstanceForTest
... That's code in production for test.
Now that we have a way to configure how AlertDialogs
are constructed; we can extend DialogBuilder
in our Unit Tests. I have a FakeDialogBuilder
that I can use in a test like
public void someTest(){
final FakeDialogBuilder fakeDialogBuilder = new FakeDialogBuilder();
AlertDialogBuilderAccess.setActiveDialogBuilder(fakeDialogBuilder);
}
This approach can be used to either test the AlertDialog was constructed and called correctly; or to beable to pass by a cosntruction/use of a dialog. Though; the second seems like it shouldn't happen. But sometimes as you refactor technical debt; you'll need to bypass initially.
This works - I'm using it in my HackerNews App(LINK) and it looks super promising for flexibility in UI testing.
Downside at the time of writing; the base is from somewhere else - No tests. TIME TO TEST THIGNS!
The test writing will be part of the HackerNews app.
I'm glad to find a clean and flexibile solution to working with AlertDialogs in Android.