Tuesday, December 22, 2009

Android Testing: ContentProvider integration tests using mock con

Android Testing: ContentProvider integration test using mock content



















This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_221tm2kqmgp), cut and paste link if you have problems accessing it.












But when we try to produce functional or integration tests of your Activities against your ContentProvider it's not so evident what test case to use. The most obvious choice is  ActivityInstrumentationTestCase2 mainly if your functional tests simulate user experience because you may need sendKeys() or similar methods, which are readily available on these tests.





The first problem you may encounter is that's no clear where to inject a MockContentResolver in your test to be able to use a test database instance or database fixture with your ContentProvider. There's no way to inject a MockContext either.


We have visited Mock Objects before and evaluated Test Driven Development. You may want to take a look if you are not familiar with mock objects on Android.

An alternative approach to this problem is introduced by tenacious33 in Android functional testing with MockContext.







mocking content provider operations


In order to create an ActivityInstrumentationTestCase2 and be able to mock content provider operations and optionally use a database fixture we need a different approach.



















Download:

Binaries and source code of
android-mock and MockContentResolverActivity main and test sample projects can be downloaded from http://sites.codtech.com/android/Home/source-code




android-mock



android-mock is a library encapsulating some of the needed functionality to simplify functional and integration tests of activities that use content providers.

We are introducing the library here and we will be presenting a practical example later.
This is the UML Class Diagram of the library. Green classes belong to android library.













ActivityAndContentProviderInstrumentationTestCase2<T extends Activity, P extends ContentProvider>


This is a functional or integration test of an Activity that uses a MockContextWithMockContentResolver context and uses the specified ContentProvider under tests.

You should extend this class to create your tests and everything will be setup automatically.

During the setup process the abstract method createDatabaseFixture(Context mockContext) is invoked to give you the opportunity of creating your database fixture.

The last step of the setup process invokes your Activity's onCreate() method.

MockContextWithMockContentResolver<P extends ContentProvider>


A mock context, with a mock content resolver using the specified ConentProvider under test. The mock context is a RenamingDelegatingContext that renames file and database operations prefixing them with "test." by default.

This provides a way to inject a ContentResolver into an ActivityAndContentProviderInstrumentationTestCase2 permitting the introduction of database fixtures.

GenericMockContentResolver<P extends ConentProvider>


A generic mock content resolver using the specified ConentProvider under test. This content resolver uses the specified content provider to resolve data for authority.



examples


To demonstrate the use of this library we have an Activity that presents some data using a ContentProvider. They are very simple an similar to other examples with the same functionality. In this case we are using a content provider with authority on a simple table Samples.AUTHORITY.
Our Activity in the following example is MyMockContentResolverActivity and the content provider is MyContentProvider.








We will concentrate now on the tests.

content provider unit tests


This tests will be as usual implemented using ProviderTestCase2<P>. This is pretty standard so we are not pay much attention to it.



activity and content provider functional tests



We reach the point. MyMockContentResolverActivityTests class implements the tests extending ActivityAndContentProviderInstrumentationTestCase2 and specifying the Activity and ContentProvider under test as well as the authority, Samples.AUTHORITY in this particular example.
These tests can be run completely isolated from real data, so you can be confident and exercise your database through your Activity not worrying about your real data.















/*
 * Copyright © 2009 COD Technologies Ltd.  www.codtech.com
 * 
 * $Id$
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.codtech.android.training.mockcontentresolver.examples.test;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.TextView;

import com.codtech.android.mock.ActivityAndContentProviderInstrumentationTestCase2;
import com.codtech.android.training.mockcontentresolver.examples.MyMockContentResolverActivity;
import com.codtech.android.training.mockcontentresolver.examples.provider.MyContentProvider;
import com.codtech.android.training.mockcontentresolver.examples.provider.Samples;

public class MyMockContentResolverActivityTests extends
ActivityAndContentProviderInstrumentationTestCase2<MyMockContentResolverActivity,
                    MyContentProvider> {

private static final String TEST_CASE_1 = "test case 1";
private static final String TAG = "MyMockContentResolverActivityTests2";

public MyMockContentResolverActivityTests(String name) {
super(MyMockContentResolverActivity.class.getPackage().getName(),
                    MyMockContentResolverActivity.class, MyContentProvider.class,
                    Samples.AUTHORITY);
setName(name);
}

@Override
public void createDatabaseFixture(Context mockContext) {
final ContentResolver cr = mockContext.getContentResolver();
cr.delete(Samples.CONTENT_URI, " 1 = 1 ", null);
final ContentValues cv = new ContentValues();
cv.put(Samples.NAME, TEST_CASE_1);
final Uri uri = cr.insert(Samples.CONTENT_URI, cv);
assertNotNull(uri);
}

public void testDatabaseFixture() {
final Activity activity = getActivity();
final ContentResolver cr = activity.getContentResolver();
final Cursor c = cr.query(Samples.CONTENT_URI, new String[] { Samples._ID },
                    null, null, null);
assertTrue(c.getCount() != 0);
c.close();
}
public void testSimpleCreate() {
final Activity activity = getActivity();
assertNotNull(activity);
final TextView tv = (TextView) activity.findViewById(
                    com.codtech.android.training.mockcontentresolver.examples.R.id.TextView01);
assertNotNull(tv);
}

public void testDatabaseTestCase1() {
final Activity activity = getActivity();
assertNotNull(activity);
TextView tv = (TextView) activity.findViewById(
                    com.codtech.android.training.mockcontentresolver.examples.R.id.TextView01);
assertNotNull(tv);
assertEquals("Cursor returns 1 row\n" + TEST_CASE_1, tv.getText().toString());
sendKeys(KeyEvent.ACTION_DOWN);
}
}



createDatabaseFixture

Firstly, the database is cleaned up to start the test from a know base. Strictly speaking this could not be necessary but we prefer to err on the safe side.
Secondly, a single row is inserted to materialize our fixture.
Remember this is an abstract method in ActivityAndContentProviderInstrumentationTestCase2.

testDatabaseFixture

Simple test, just to verify that our database fixture is correct.

testSimpleCreate

Verify that our Activity has been created correctly and everything is in place.

testDatabaseTestCase1

Functional test, we verify the data presented in the Activity, check that the information is the expected one and finally we send key events simulating user interaction.
In this test we are not using the ContentResolver nor the database directly and we see everything from the Activity's perspective.





conclusion

Though this is an oversimplified example it shows the most important concepts behind android-mock library classes and how you can implement your tests relying on it.
Comments are gladly welcome.






Copyright © 2009 Diego Torres Milano. All rights reserved.

7 comments:

Unknown said...

Hi Diego,

this is great. I was stuck with the same problem and I was going to implement the same classes, but now I don't have to :)

Tenacious33's approach is good, but I really don't like writing ad hoc methods that are only used for testing and are not real production code (to inject the mock context and content resolver). This solution is much neater.

Keep up the good work!

gianpi

Diego Torres Milano said...

Thanks for your comments.

Unknown said...

Hi again Diego :)

I was stuck trying to test-drive a searchable activity, I found out I can't inject an Intent with setActivityIntent in your ActivityAndContentProviderInstrumentationTestCase2.

So I extended it overriding the above method:

@Override
public void setActivityIntent(Intent intent)
{
this.intent = intent;
}

declaring a field:

private Intent intent;

and then modifying your setUp() method:

if(intent == null)
{
intent = new Intent(targetContext, activityClass);
}

So I could start the activity in my tests with a custom intent (with ACTION_SEARCH in my case).

Please let me know if I missed something from your implementation!

gianpi

Unknown said...

Thanks for linking to my article (Tenacious33). This approach is better than mine and I'm using it now. However, I needed small modifications and am still having problems getting the UI to display in the emulator in certain situations. I finally delved more into the mechanics of how instrumentation activities are being started. There's room for progress in that area. Unless you object, I'm going to post the modifications I made on my blog (since also the source link here is stale). This functionality should be in the test pkg and it'd make sense to propose adding it to the SDK.

Diego Torres Milano said...

Hi Free,
Thanks for posting your comments here and feel free to publish your improvements.

Jan Schoubo said...

Hi Diego,

I just bought your Android Testing book, and look forward to reading all of it.

However, I scanned it for a tip on combining mocked database access with ActivityInstrumentationTestCase2 as I would like to use Robotium.

However, it seems this blog entry is the latest on the issue?
I read the Google Docs paper, but the link to the source at http://sites.codtech.com/android/Home/source-code seems to be outdated? Can you provide a new link, so I can dig deeper?

- Jan

FaB said...

Hi Diego,
nice post, i'd also like to get my hands on the source code ..

Could you provide a new link plsease?

Cheers,
Fabian