Bug 1411654 - Part 2: Update Robolectric to 3.5.1. r=mcomella draft
authorNick Alexander <nalexander@mozilla.com>
Tue, 07 Nov 2017 20:26:43 -0800
changeset 749695 ab1dca42bbc41d13edfd05990dc329276ab26277
parent 749694 a3308672fcefec1234b625fecfa85c89bf61e6d7
child 749696 896a8618fee1b990df0c9c830306f39d02d7bd73
push id97470
push usernalexander@mozilla.com
push dateWed, 31 Jan 2018 21:14:34 +0000
reviewersmcomella
bugs1411654
milestone60.0a1
Bug 1411654 - Part 2: Update Robolectric to 3.5.1. r=mcomella There were a few API changes, mostly around explicitly creating Services/Activities/ContentProvider instances, but they were pretty easy to address. Sadly, Robolectric doesn't really work with the new aapt2 processing in Android-Gradle plugin 3.0+ -- see in particular https://github.com/robolectric/robolectric/issues/3333#issuecomment-324300418 -- so we have to opt-out of the new implementation for now. Hopefully plugin 3.1+ will address these issues, which are widespread. MozReview-Commit-ID: dlbd32kMs6
gradle.properties
mobile/android/app/build.gradle
mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
mobile/android/app/src/test/java/org/mozilla/gecko/icons/loader/TestLegacyLoader.java
mobile/android/app/src/test/java/org/mozilla/gecko/util/NetworkUtilsTest.java
mobile/android/app/src/test/resources/robolectric.properties
mobile/android/services/src/test/java/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
mobile/android/services/src/test/java/org/mozilla/gecko/background/db/TestTabsProvider.java
mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderGeneralTest.java
mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
mobile/android/services/src/test/java/org/mozilla/gecko/db/LocalBrowserDBTest.java
mobile/android/services/src/test/java/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceListUpdater.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/repositories/android/VisitsHelperTest.java
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,4 @@
 org.gradle.parallel=true
 org.gradle.daemon=true
 org.gradle.jvmargs=-Xmx2560M
+android.enableAapt2=false
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -231,16 +231,19 @@ android {
             }
             assets {
                 srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/assets"
             }
         }
     }
 
     testOptions {
+        // For Robolectric: see https://github.com/robolectric/robolectric/issues/3333#issuecomment-324300418.
+        unitTests.includeAndroidResources true
+
         unitTests.all {
             // We'd like to use (Runtime.runtime.availableProcessors()/2), but
             // we have tests that start test servers and the bound ports
             // collide.  We'll fix this soon to have much faster test cycles.
             maxParallelForks 1
         }
     }
 }
@@ -284,17 +287,17 @@ dependencies {
     officialImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
     officialImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
     testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
 
     implementation project(path: ':geckoview')
     implementation project(path: ':thirdparty')
 
     testImplementation 'junit:junit:4.12'
-    testImplementation 'org.robolectric:robolectric:3.1.2'
+    testImplementation 'org.robolectric:robolectric:3.5.1'
     testImplementation 'org.simpleframework:simple-http:6.0.1'
     testImplementation 'org.mockito:mockito-core:1.10.19'
 
     // Including the Robotium JAR directly can cause issues with dexing.
     androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.5.4'
 }
 
 // TODO: (bug 1261486): This impl is not robust -
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
@@ -1,42 +1,39 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko;
 
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.os.RemoteException;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.PageMetadata;
 import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserProvider;
 import org.mozilla.gecko.db.LocalBrowserDB;
 import org.robolectric.shadows.ShadowContentResolver;
 
 import static org.junit.Assert.*;
 
 @RunWith(TestRunner.class)
 public class GlobalPageMetadataTest {
     @Test
     public void testQueueing() throws Exception {
         BrowserDB db = new LocalBrowserDB("default");
 
-        BrowserProvider provider = new BrowserProvider();
+        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
         try {
-            provider.onCreate();
-            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
-
             ShadowContentResolver cr = new ShadowContentResolver();
             ContentProviderClient pageMetadataClient = cr.acquireContentProviderClient(PageMetadata.CONTENT_URI);
 
             assertEquals(0, GlobalPageMetadata.getInstance().getMetadataQueueSize());
 
             // There's not history record for this uri, so test that queueing works.
             GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://mozilla.org", false, "{type: 'article'}");
 
@@ -59,21 +56,18 @@ public class GlobalPageMetadataTest {
 
     @Test
     public void testInsertingMetadata() throws Exception {
         BrowserDB db = new LocalBrowserDB("default");
 
         // Start listening for events.
         GlobalPageMetadata.getInstance().init();
 
-        BrowserProvider provider = new BrowserProvider();
+        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
         try {
-            provider.onCreate();
-            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
-
             ShadowContentResolver cr = new ShadowContentResolver();
             ContentProviderClient historyClient = cr.acquireContentProviderClient(BrowserContract.History.CONTENT_URI);
             ContentProviderClient pageMetadataClient = cr.acquireContentProviderClient(PageMetadata.CONTENT_URI);
 
             // Insert required history item...
             ContentValues cv = new ContentValues();
             cv.put(BrowserContract.History.GUID, "guid1");
             cv.put(BrowserContract.History.URL, "https://mozilla.org");
@@ -166,9 +160,9 @@ public class GlobalPageMetadataTest {
 
         assertNotNull(cursor);
         try {
             assertEquals(expected, cursor.getCount());
         } finally {
             cursor.close();
         }
     }
-}
\ No newline at end of file
+}
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/icons/loader/TestLegacyLoader.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/icons/loader/TestLegacyLoader.java
@@ -1,36 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.icons.loader;
 
+import android.content.ContentProvider;
 import android.graphics.Bitmap;
 
 import org.junit.Assert;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserProvider;
 import org.mozilla.gecko.icons.IconDescriptor;
 import org.mozilla.gecko.icons.IconRequest;
 import org.mozilla.gecko.icons.IconResponse;
 import org.mozilla.gecko.icons.Icons;
 import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowContentResolver;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.util.Iterator;
-import java.util.concurrent.ConcurrentHashMap;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 @RunWith(TestRunner.class)
@@ -41,33 +33,31 @@ public class TestLegacyLoader {
     private static final String TEST_ICON_URL_3 = "https://example.net/icon/favicon.ico";
 
     @Test
     public void testDatabaseIsQueriesForNormalRequestsWithNetworkSkipped() {
         // We're going to query BrowserProvider via LegacyLoader, and will access a database.
         // We need to ensure we close our db connection properly.
         // This is the only test in this class that actually accesses a database. If that changes,
         // move BrowserProvider registration into a @Before method, and provider.shutdown into @After.
-        final BrowserProvider provider = new BrowserProvider();
-        provider.onCreate();
-        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
         try {
             final IconRequest request = Icons.with(RuntimeEnvironment.application)
                     .pageUrl(TEST_PAGE_URL)
                     .icon(IconDescriptor.createGenericIcon(TEST_ICON_URL))
                     .skipNetwork()
                     .build();
 
             final LegacyLoader loader = spy(new LegacyLoader());
             final IconResponse response = loader.load(request);
 
             verify(loader).loadBitmapFromDatabase(request);
             Assert.assertNull(response);
-        // Close any open db connections.
         } finally {
+            // Close any open db connections.
             provider.shutdown();
         }
     }
 
     @Test
     public void testNothingIsLoadedIfNetworkIsNotSkipped() {
         final IconRequest request = Icons.with(RuntimeEnvironment.application)
                 .pageUrl(TEST_PAGE_URL)
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/util/NetworkUtilsTest.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/util/NetworkUtilsTest.java
@@ -7,36 +7,40 @@ import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.telephony.TelephonyManager;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
-import org.mozilla.gecko.util.NetworkUtils.*;
+import org.mozilla.gecko.util.NetworkUtils.ConnectionSubType;
+import org.mozilla.gecko.util.NetworkUtils.ConnectionType;
+import org.mozilla.gecko.util.NetworkUtils.NetworkStatus;
 import org.robolectric.RuntimeEnvironment;
-import org.robolectric.internal.ShadowExtractor;
+import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowConnectivityManager;
 import org.robolectric.shadows.ShadowNetworkInfo;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 @RunWith(TestRunner.class)
 public class NetworkUtilsTest {
     private ConnectivityManager connectivityManager;
     private ShadowConnectivityManager shadowConnectivityManager;
 
     @Before
     public void setUp() {
         connectivityManager = (ConnectivityManager) RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE);
 
         // Not using Shadows.shadowOf(connectivityManager) because of Robolectric bug when using API23+
         // See: https://github.com/robolectric/robolectric/issues/1862
-        shadowConnectivityManager = (ShadowConnectivityManager) ShadowExtractor.extract(connectivityManager);
+        shadowConnectivityManager = (ShadowConnectivityManager) Shadow.extract(connectivityManager);
     }
 
     @Test
     public void testIsConnected() throws Exception {
         assertFalse(NetworkUtils.isConnected((ConnectivityManager) null));
 
         shadowConnectivityManager.setActiveNetworkInfo(null);
         assertFalse(NetworkUtils.isConnected(connectivityManager));
@@ -177,9 +181,9 @@ public class NetworkUtilsTest {
         );
         assertEquals(NetworkStatus.DOWN, NetworkUtils.getNetworkStatus(connectivityManager));
 
         shadowConnectivityManager.setActiveNetworkInfo(
                 ShadowNetworkInfo.newInstance(NetworkInfo.DetailedState.CONNECTED, ConnectivityManager.TYPE_MOBILE, 0, true, true)
         );
         assertEquals(NetworkStatus.UP, NetworkUtils.getNetworkStatus(connectivityManager));
     }
-}
\ No newline at end of file
+}
--- a/mobile/android/app/src/test/resources/robolectric.properties
+++ b/mobile/android/app/src/test/resources/robolectric.properties
@@ -1,3 +1,3 @@
-sdk=21
+sdk=23
 constants=org.mozilla.gecko.BuildConfig
 packageName=org.mozilla.gecko
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
@@ -3,41 +3,84 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.background.db;
 
 import android.content.ContentProvider;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.OperationApplicationException;
+import android.content.pm.ProviderInfo;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 
 import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserProvider;
+import org.mozilla.gecko.db.TabsProvider;
+import org.robolectric.android.controller.ContentProviderController;
+import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
 
 /**
  * Wrap a ContentProvider, appending &test=1 to all queries.
  */
 public class DelegatingTestContentProvider extends ContentProvider {
-    protected final ContentProvider mTargetProvider;
+    protected ContentProvider mTargetProvider;
 
     protected static Uri appendUriParam(Uri uri, String param, String value) {
         return uri.buildUpon().appendQueryParameter(param, value).build();
     }
 
+    /**
+     * Create and register a new <tt>BrowserProvider</tt> that has test delegation.
+     * <p>
+     * Robolectric doesn't make it easy to parameterize a created
+     * <tt>ContentProvider</tt>, so we modify a built-in helper to do it.
+     * @return delegated <tt>ContentProvider</tt>.
+     */
+    public static ContentProvider createDelegatingBrowserProvider() {
+        final ContentProviderController<DelegatingTestContentProvider> contentProviderController
+                = ContentProviderController.of(ReflectionHelpers.callConstructor(DelegatingTestContentProvider.class,
+                ReflectionHelpers.ClassParameter.from(ContentProvider.class, new BrowserProvider())));
+        return contentProviderController.create(BrowserContract.AUTHORITY).get();
+    }
+
+    /**
+     * Create and register a new <tt>TabsProvider</tt> that has test delegation.
+     * <p>
+     * Robolectric doesn't make it easy to parameterize a created
+     * <tt>ContentProvider</tt>, so we modify a built-in helper to do it.
+     * @return delegated <tt>ContentProvider</tt>.
+     */
+    public static ContentProvider createDelegatingTabsProvider() {
+        final ContentProviderController<DelegatingTestContentProvider> contentProviderController
+                = ContentProviderController.of(ReflectionHelpers.callConstructor(DelegatingTestContentProvider.class,
+                ReflectionHelpers.ClassParameter.from(ContentProvider.class, new TabsProvider())));
+        return contentProviderController.create(BrowserContract.TABS_AUTHORITY).get();
+    }
+
     public DelegatingTestContentProvider(ContentProvider targetProvider) {
         super();
         mTargetProvider = targetProvider;
     }
 
+    public void attachInfo(Context context, ProviderInfo info) {
+        // With newer Robolectric versions, we must create the target provider
+        // before calling into super.  If we don't do this, the target
+        // provider's onCreate() will witness a null getContext(), which the
+        // Android documentation guarantees never happens on device.
+        mTargetProvider.attachInfo(context, null);
+        super.attachInfo(context, info);
+    }
+
     private Uri appendTestParam(Uri uri) {
         return appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
     }
 
     @Override
     public boolean onCreate() {
         return mTargetProvider.onCreate();
     }
@@ -83,12 +126,16 @@ public class DelegatingTestContentProvid
     }
 
     @Nullable
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         return mTargetProvider.call(method, arg, extras);
     }
 
+    public void shutdown() {
+        mTargetProvider.shutdown();
+    }
+
     public ContentProvider getTargetProvider() {
         return mTargetProvider;
     }
 }
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/db/TestTabsProvider.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/db/TestTabsProvider.java
@@ -1,28 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.db;
 
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 
 import org.json.simple.JSONArray;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.TabsProvider;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
 import org.robolectric.shadows.ShadowContentResolver;
 
 @RunWith(TestRunner.class)
 public class TestTabsProvider {
     public static final String TEST_CLIENT_GUID = "test guid"; // Real GUIDs never contain spaces.
@@ -30,23 +30,21 @@ public class TestTabsProvider {
 
     public static final String CLIENTS_GUID_IS = BrowserContract.Clients.GUID + " = ?";
     public static final String TABS_CLIENT_GUID_IS = BrowserContract.Tabs.CLIENT_GUID + " = ?";
 
     protected Tab testTab1;
     protected Tab testTab2;
     protected Tab testTab3;
 
-    protected TabsProvider provider;
+    protected ContentProvider provider;
 
     @Before
     public void setUp() {
-        provider = new TabsProvider();
-        provider.onCreate();
-        ShadowContentResolver.registerProvider(BrowserContract.TABS_AUTHORITY, new DelegatingTestContentProvider(provider));
+        provider = DelegatingTestContentProvider.createDelegatingTabsProvider();
     }
 
     @After
     public void tearDown() throws Exception {
         provider.shutdown();
         provider = null;
     }
 
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
@@ -8,30 +8,32 @@ import org.mozilla.gecko.sync.NonObjectJ
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConfigurationException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.stage.CompletedStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
 
 import java.io.IOException;
 import java.util.HashMap;
 
 
 public class MockGlobalSession extends MockPrefsGlobalSession {
 
   public MockGlobalSession(String username, String password, KeyBundle keyBundle, GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException {
     this(new SyncConfiguration(username, new BasicAuthHeaderProvider(username, password), new MockSharedPreferences(), keyBundle), callback);
   }
 
   public MockGlobalSession(SyncConfiguration config, GlobalSessionCallback callback)
           throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
-    super(config, callback, null, null);
+    super(config, callback, RuntimeEnvironment.application, null);
   }
 
   @Override
   public boolean isEngineRemotelyEnabled(String engine, EngineSettings engineSettings) {
     return false;
   }
 
   @Override
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
@@ -48,14 +48,9 @@ public class MockPrefsGlobalSession exte
       ClientsDataDelegate clientsDelegate)
       throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
 
     final SharedPreferences prefs = new MockSharedPreferences();
     final SyncConfiguration config = new SyncConfiguration(username, authHeaderProvider, prefs);
     config.syncKeyBundle = syncKeyBundle;
     return new MockPrefsGlobalSession(config, callback, context, clientsDelegate);
   }
-
-  @Override
-  public Context getContext() {
-    return null;
-  }
 }
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
@@ -1,33 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.db;
 
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.robolectric.shadows.ShadowContentResolver;
 
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 
 import static org.junit.Assert.assertEquals;
@@ -36,35 +36,34 @@ import static org.junit.Assert.assertNot
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.INVALID_TIMESTAMP;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.assertVersionsForSelection;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.bookmarksTestSyncUri;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.bookmarksTestUri;
-import static org.mozilla.gecko.db.BrowserProviderGeneralTest.getBookmarksTestSyncIncrementLocalVersionUri;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.getBookmarkIdFromGuid;
+import static org.mozilla.gecko.db.BrowserProviderGeneralTest
+        .getBookmarksTestSyncIncrementLocalVersionUri;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.insertBookmark;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.withDeleted;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.withSync;
 
 /**
  * Testing direct interactions with bookmarks through BrowserProvider
  */
 @RunWith(TestRunner.class)
 public class BrowserProviderBookmarksTest {
     private ContentProviderClient bookmarksClient;
-    private BrowserProvider provider;
+    private ContentProvider provider;
 
     @Before
     public void setUp() throws Exception {
-        provider = new BrowserProvider();
-        provider.onCreate();
-        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+        provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
 
         ShadowContentResolver contentResolver = new ShadowContentResolver();
         bookmarksClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
     }
 
     @After
     public void tearDown() {
         bookmarksClient.release();
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderGeneralTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderGeneralTest.java
@@ -1,20 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.db;
 
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.provider.Browser;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
@@ -36,25 +36,22 @@ public class BrowserProviderGeneralTest 
     final static Uri bookmarksTestSyncUri = withSync(bookmarksTestUri);
     final static Uri getBookmarksTestSyncIncrementLocalVersionUri = bookmarksTestSyncUri
             .buildUpon()
             .appendQueryParameter(BrowserContract.PARAM_INCREMENT_LOCAL_VERSION_FROM_SYNC, "true")
             .build();
 
     private static final long INVALID_ID = -1;
 
-    private BrowserProvider provider;
+    private ContentProvider provider;
     private ContentProviderClient browserClient;
 
     @Before
     public void setUp() throws Exception {
-        provider = new BrowserProvider();
-        provider.onCreate();
-
-        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+        provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
 
         ShadowContentResolver contentResolver = new ShadowContentResolver();
         browserClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
     }
 
     @After
     public void tearDown() throws Exception {
         browserClient.release();
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
@@ -1,39 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.db;
 
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.net.Uri;
 import android.os.RemoteException;
 
 import org.junit.After;
 import org.junit.Before;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.robolectric.shadows.ShadowContentResolver;
 
-import java.util.UUID;
-
 public class BrowserProviderHistoryVisitsTestBase {
     /* package-private */ ShadowContentResolver contentResolver;
     /* package-private */ ContentProviderClient historyClient;
     /* package-private */ ContentProviderClient visitsClient;
     /* package-private */ Uri historyTestUri;
     /* package-private */ Uri visitsTestUri;
-    /* package-private */ BrowserProvider provider;
+    /* package-private */ ContentProvider provider;
 
     @Before
     public void setUp() throws Exception {
-        provider = new BrowserProvider();
-        provider.onCreate();
-        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+        provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
 
         contentResolver = new ShadowContentResolver();
         historyClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.HISTORY_CONTENT_URI);
         visitsClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.VISITS_CONTENT_URI);
 
         historyTestUri = testUri(BrowserContract.History.CONTENT_URI);
         visitsTestUri = testUri(BrowserContract.Visits.CONTENT_URI);
     }
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/db/LocalBrowserDBTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/db/LocalBrowserDBTest.java
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.db;
 
 import android.annotation.SuppressLint;
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.support.annotation.Nullable;
@@ -39,25 +40,23 @@ public class LocalBrowserDBTest {
     private static final String BOOKMARK_TITLE = "mozilla";
 
     private static final String UPDATE_URL = "https://bugzilla.mozilla.org";
     private static final String UPDATE_TITLE = "bugzilla";
 
     private static final String FOLDER_NAME = "folder1";
 
     private Context context;
-    private BrowserProvider provider;
+    private ContentProvider provider;
     private ContentProviderClient bookmarkClient;
 
     @Before
     public void setUp() throws Exception {
         context = RuntimeEnvironment.application;
-        provider = new BrowserProvider();
-        provider.onCreate();
-        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+        provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
 
         ShadowContentResolver contentResolver = new ShadowContentResolver();
         bookmarkClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
     }
 
     @After
     public void tearDown() {
         bookmarkClient.release();
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceListUpdater.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceListUpdater.java
@@ -1,13 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.fxa.devices;
 
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
 
@@ -16,17 +17,16 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserProvider;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.State;
 import org.robolectric.shadows.ShadowContentResolver;
 
 import java.util.List;
 import java.util.UUID;
 
 import static java.util.Objects.deepEquals;
@@ -114,22 +114,19 @@ public class TestFxAccountDeviceListUpda
         assertTrue(firstDevice.getAsLong(BrowserContract.RemoteDevices.DATE_MODIFIED) < timeBeforeCall + 10000);
         assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.NAME), device.name);
     }
 
     @Test
     public void testBrowserProvider() {
         Uri uri = testUri(BrowserContract.RemoteDevices.CONTENT_URI);
 
-        BrowserProvider provider = new BrowserProvider();
+        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
         Cursor c = null;
         try {
-            provider.onCreate();
-            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
-
             final ShadowContentResolver cr = new ShadowContentResolver();
             ContentProviderClient remoteDevicesClient = cr.acquireContentProviderClient(BrowserContract.RemoteDevices.CONTENT_URI);
 
             // First let's insert a client for initial state.
 
             Bundle bundle = new Bundle();
             ContentValues device1 = createMockRemoteClientValues("device1");
             bundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, new ContentValues[] { device1 });
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/repositories/android/VisitsHelperTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/repositories/android/VisitsHelperTest.java
@@ -1,27 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.repositories.android;
 
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.net.Uri;
 
 import junit.framework.Assert;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserProvider;
 import org.robolectric.shadows.ShadowContentResolver;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(TestRunner.class)
 public class VisitsHelperTest {
     @Test
@@ -51,21 +51,18 @@ public class VisitsHelperTest {
         Assert.assertEquals(Long.valueOf(date + 1000), cv2.getAsLong(BrowserContract.Visits.DATE_VISITED));
     }
 
     @Test
     public void testGetRecentHistoryVisitsForGUID() throws Exception {
         Uri historyTestUri = testUri(BrowserContract.History.CONTENT_URI);
         Uri visitsTestUri = testUri(BrowserContract.Visits.CONTENT_URI);
 
-        BrowserProvider provider = new BrowserProvider();
+        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
         try {
-            provider.onCreate();
-            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
-
             final ShadowContentResolver cr = new ShadowContentResolver();
             ContentProviderClient historyClient = cr.acquireContentProviderClient(BrowserContractHelpers.HISTORY_CONTENT_URI);
             ContentProviderClient visitsClient = cr.acquireContentProviderClient(BrowserContractHelpers.VISITS_CONTENT_URI);
 
             ContentValues historyItem = new ContentValues();
             historyItem.put(BrowserContract.History.URL, "https://www.mozilla.org");
             historyItem.put(BrowserContract.History.GUID, "testGUID");
             historyClient.insert(historyTestUri, historyItem);