How to test ContentProvider with ProviderTestRule in kotlin - android

I have implemented a ContentProvider that uses a Room database to store the data. The implementation is done in kotlin and it follows the same pattern shown in this Google example.
The ContentProvider works fine when used in an app. Now I want to write some tests and I am relying on ProviderTestRule for doing so. The configuration I have seems fine, but unfortunately I am getting the following exception, which looks like some initialisation is missing and then the context is not available.
java.lang.UnsupportedOperationException
at androidx.test.rule.provider.DelegatingContext.getSystemService(DelegatingContext.java:277)
at androidx.room.RoomDatabase$JournalMode.resolve(RoomDatabase.java:517)
at androidx.room.RoomDatabase$Builder.build(RoomDatabase.java:943)
I wasn't able to find any example of how to test this scenario. Any hint would be really helpful!

ProviderTestRule internally uses DelegatingContext, which is a wrapper around the application context that purposely limits its capabilities.
From the source code you can see that context.getSystemService is stubbed out, throwing UnsupportedOperationException most of the time:
/**
* This method only supports retrieving {#link android.app.AppOpsManager}, which is needed by
* {#link android.content.ContentProvider#attachInfo}.
*/
#Override
public Object getSystemService(#NonNull String name) {
checkArgument(!TextUtils.isEmpty(name), "name cannot be empty or null");
// getSystemService(Context.APP_OPS_SERVICE) is only used in ContentProvider#attachInfo for
// API level >= 19.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& Context.APP_OPS_SERVICE.equals(name)) {
return context.getSystemService(Context.APP_OPS_SERVICE);
}
throw new UnsupportedOperationException();
}
I have no clear explaination why they forbid access to system services for ProviderTestRule in the first place.
Unfortunately, it seems that Room requires access to the ActivityManager in order to find the most appropriate JournalMode.
What you can try to workaround the situation:
Force the JournalMode of you Room database to JournalMode.WRITE_AHEAD_LOGGING (or JournalMode.TRUNCATE), or
If it did not solve the situation, you'd have to write your own ProviderTestRule that uses the real application context to and allow access to the desired system service.

Related

Filtering Android Connected Tests

I have a few "connected" tests that are only relevant to run on a specific device model or on a specific brand and should be skipped on other brands/models.
I may be missing something, but this kind of filtering seems not possible out-of-the-box with AndroidJUnitRunner (by using annotation and/or passing appropriate arguments to it).
So, I was thinking to extend the AndroidX test framework to support this kind of filtering. In the end, I would like to be able to filter test with something like this
#TargetDeviceFilter(brand="SAMSUNG",model="XCover3")
#Test
public void myTestToRunOnSamsungXCover3DeviceOnly(){
...
}
My question: is there any way to accomplish this kind of filtering without extending AndroidX test framework? And if writing my own AndroidJUnitRunner and/or my own annotations is required, how should I start ?
I found a few interesting base classes that I may need to extend like :
androidx.test.internal.runner.TestRequestBuilder
androidx.test.internal.runner.TestRequestBuilder.DeviceBuild
but as those classes are in a "internal" package: attempting to extend them is probably not a good idea?
Any advice on how to deal with that problem is welcome.
I think, you may use org.junit.Assume.
Create a helper class DeviceHelper to detect mobile device informations for convenience.
Your test logic will be executed only if the assumption is correct.
#Test
public void myTestToRunOnSamsungXCover3DeviceOnly() {
// adapt this part to your business need
org.junit.Assume.assumeTrue(
DeviceHelper.isBrand("SAMSUNG") &&
DeviceHelper.isModel("XCover3")
);
// i.e. you can filter whatever you want test's according to device sdk_int
assumeTrue(SomeHelper.getDeviceSdk() >= 21);
// your test code
}

Has MatrixCursor implementation within Android P changed?

Recently I have observed a large number of crashes for an app that I maintain when the Android P developer preview is used.
Diving (deep) into the project's code, I have found the problem method to be the following:
public static <T> T get(MatrixCursor cursor, int column) {
try {
cursor.moveToFirst();
Method get = MatrixCursor.class.getDeclaredMethod("get", int.class);
get.setAccessible(true);
return (T) get.invoke(cursor, column);
} catch (Exception e) {
throw new IllegalArgumentException("Android has changed the implementation of MatrixCursor?!");
}
}
From what I understand, this code is used to retrieve a custom object from the MatrixCursor directly, rather than a primitive type, byte array or String. There has previously been a private method within MatrixCursor that performs this internally, and it is this method that we access through reflection.
Needless to say, there's a number of issues with this approach. As far as I am aware, reflection to access private APIs is a feature that Android advises heavily against. Nevertheless, until the Android P preview, this seems to have been working as expected.
This leads me to raise the following questions:
Has MatrixCursor's implementation changed or is reflection totally deprecated as of Android P?
Sadly, I am not 100% clued up on what alternatives I have to avoid this issue. Any suggestions for that are greatly appreciated, is there a Cursor that can be used to store custom objects?
Yes, something has changed.
No, the underlying implementation of MatrixCursor has likely not changed.
What has changed is that Android P is introducing restrictions on non-public members of SDK classes. Attempting to use private fields or methods on SDK classes (whether by direct invocation, reflection, or JNI) will result in a crash.
If you run the code in question on a device running P and look at the logcat output, you should see a message similar to this:
Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)
I highly encourage you to fully read the linked documentation on these restrictions for the full context and for more information on how you can handle it.
One option (which you should do ASAP if needed!) is to file a bug so the Android team knows that this is a method you use and does not have a public alternative. If you do this before the release of Android P, there is a much better likelihood that the team will either create a public alternative for this method or allow you to continue to access that method in P.

Using Java reflection vs checking Build.VERSION.SDK_INT

Why should I bother using reflection as discussed here, if I can simply test Android version from Build.VERSION.SDK_INT and conditionally run functions not available on lower API versions?
That article discussed how to get method ID, handle exceptions, etc, which seems more complicated than simply using:
if(Build.VERSION.SDK_INT>=11){
// some Honeycomb code
// example: findViewById(R.id.root).setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
}
This code works fine for me on various devices (2.2 / 3.2 / etc).
Thanks
Your proposal won't work (without reflection) when running on an older Android system, if the code hidden in "// some Honeycomb code" uses Class or Method names that only exist in the Honeycomb API. The root of the problem is that all classes referenced from code are loaded when the class is loaded. You need to use reflection to delay resolution of the code containing Honeycomb references until run-time.
Specifically, if you have a class:
class MyUseOfFeatures {
public void doSomething() {
if (TestIfPhoneHasFancyHoneycombFeature()) {
Object example = android.util.JsonReader(); // JsonReader is new in 3.0
}
}
Then when the JVM (er, DVM?) loads the bytecode for this class it will try and resolve the android.util.JsonReader name when the class is loaded (presumably when your application is loaded).
If you only rely on some behavior of Honeycomb (not any any new classes, methods or fields), then you'd be fine to just test the build number.

Android unit testing and interfaces

I have been having quite a bit of trouble implementing unit testing on the Android. As a simple test, I've been trying to match a string retrieved from string resources:
String myString = myActivity.getResources().getString(R.string.testString));
However, when unit testing this invariably results in a null pointer exception. This includes robolectric as well as the Junit implementation delivered with the Android sdk.
One possible solution is to approach the retrieval of resources in a manner similar to a data access object. That is, create an interface through which string resources would be accessed. This would allow me to mock access the string resource. Similarly, I could separate the non-android dependent behavior of, say, an Activity, into a separate pojo class. This would allow me to run unit tests using standard Java testing tools. In fact, I could potentially delegate any Android infrastructure related activity to an interface.
This seems like a lot of jumping through hoops to get to unit testing. Is it worth it? Is there a more viable approach?
It turned out, the problem was that the activity has to be gotten in the actual test method. So, for example, my method now looks like this:
public void testGetActivityResourceString() {
Activity myActivity = this.getActivity();
String myString = myActivity.getResources().getString(R.string.hello);
Assert.assertNotNull(myString);
}
Whereas before I was creating activity in setup. This giveaway was in the docs:
"For each test method invocation, the Activity will not actually be created until the first time this method is called."
This was a real hassle to figure out. The example for HelloWorldTest doesn't work for the same reason.
Here's the full entry:
Public T getActivity ()
Since: API Level 3
Get the Activity under test, starting it if necessary.
For each test method invocation, the Activity will not actually be created until the first time this method is called.
If you wish to provide custom setup values to your Activity, you may call setActivityIntent(Intent) and/or setActivityInitialTouchMode(boolean) before your first call to getActivity(). Calling them after your Activity has started will have no effect.
NOTE: Activities under test may not be started from within the UI thread. If your test method is annotated with UiThreadTest, then your Activity will be started automatically just before your test method is run. You still call this method in order to get the Activity under test.
This works correctly:
public void testGetResourceString() {
assertNotNull(mActivity.getResources()
.getString(com.example.pkg.R.string.testString));
}
Because you haven't provided any of your code but only the getReousrces() line, I will guess what you are doing wrong:
you are not using the correct base class for your test, use ActivityInstrumentationTestCase2 because you need the system infrastructure
you are using the resources of your test project instead of your project under test, that's why in my example the id is com.example.pkg.R.string.testString.

IsolatedContext vs. AndroidTestCase.getContext()

I'm writing some tests to test my sqllite database code. Can someone here explain if there would be a difference writing those tests using the context I get from AndroidTestCase.getContext() or using an IsolatedContext.
For those that don't want to follow the link to the Google Group, here is the answer given there:
AndroidTestCase.getContext() returns a normal Context object. It's the
Context of the test case, not the component under test.
IsolatedContext returns a "mock" Context. I put "mock" in quotes
because its not a mock in the normal sense of that term (for testing).
Instead, it's a template Context that you have to set up yourself. It
"isolates" you from the running Android system, so that your Context
or your test doesn't accidentally get outside of the test fixture. For
example, an IsolatedContext won't accidentally hit a production
database (unless you set it up to do that!) Note, however, that some
of the methods in an IsolatedContext may throw exceptions.
IsolatedContext is documented in the Developer Guide under Framework
Topics > Testing, both in Testing Fundamentals and in Content Provider
Testing.
Here is the Android docs on IsolatedContext.
And here is the relevant section of the Testing Fundamentals document.
The answer:
http://groups.google.com/group/android-developers/browse_thread/thread/3a7bbc78258a194a?tvc=2
I had the simple problem: I need to test my DAO class without touching the real database. So I found the IsolatedContext from docs. But finally I found the other context in the same docs: RenamingDelegatingContext might be more easier to use. Here is my test case:
public class AddictionDAOTest extends AndroidTestCase {
#Override
public void setUp() throws Exception {
super.setUp();
setContext(new RenamingDelegatingContext(getContext(), "test_"));
}
public void testReadAllAddictions() throws Exception {
ImQuitDAO imQuitDAO = new ImQuitDAO(getContext());
...
}
}

Categories

Resources