Bug 1411654 - Part 2: Update Robolectric to 3.5.1. r=mcomella
☠☠ backed out by 64f824603458 ☠ ☠
authorNick Alexander <nalexander@mozilla.com>
Tue, 07 Nov 2017 20:26:43 -0800
changeset 399094 0e493bacc5e396aba37213caae165c8984dedcbc
parent 399093 23cbcf427745190d86d79cb41b276fdac2120bfa
child 399095 6ff0cdf46a3df61ee10b79b35669bb3193ff78e9
push id33241
push userccoroiu@mozilla.com
push dateSat, 13 Jan 2018 09:52:54 +0000
treeherdermozilla-central@c51cdba4c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcomella
bugs1411654
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -221,16 +221,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
         }
     }
 }
@@ -274,17 +277,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);