How can I shadow the PackageManager with Robolectr

2019-01-25 07:47发布

My Android application has a simple method to fire off an intent to display a URL.

protected void launchBrowser(int id)
{
    Uri uri = Uri.parse( getString( id ) );
    Intent intent = new Intent( ACTION_VIEW, uri);

    PackageManager packageManager = getPackageManager();
    List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
    if (activities.size() > 0)
    {
        startActivity(intent);
    }
    else
    {
        Toast.makeText(getApplicationContext(),
                       "ERROR - no application to display a web page",
                       Toast.LENGTH_SHORT).show();
    }
}

I'm using Robolectric for unit testing but I'm having trouble verifying this method. Specifically, getPackageManager() is always returning null. How I can shadow the PackageManager? I tried creating a ShadowPackageManager and calling bindShadowClass, but none of my code gets executed - getPackageManager() always returns null. I also tried to Shadow the Application context and return a concrete StubPackageManager, but got the same results. Maybe I've been searching/staring too long - is there a better way to unit test this method?

6条回答
Ridiculous、
2楼-- · 2019-01-25 08:17

I upgraded to the latest Robolectric (version 2.1.1) and the PackageManager doesn't come up null anymore.

The code below verifies that a browser intent is fired (and configured with a url). I haven't tested this on 1.x, but I think it works there too:

@Test
public void recipeTitleShouldOpenBrowserOnClick() throws Exception
{
    title.performClick();
    ShadowActivity shadowActivity = shadowOf( detailFragment.getActivity() );
    ShadowActivity.IntentForResult intent = shadowActivity.peekNextStartedActivityForResult();

    // or
    // ShadowActivity.IntentForResult intent = shadowActivity.getNextStartedActivityForResult();

    assertThat( intent.intent,
                equalTo( createBrowserIntentWithURL( "url" ) ) );
}

Note: I'm invoking start activity from a fragment here.

查看更多
叛逆
3楼-- · 2019-01-25 08:21

You can set the ShadowPackageManager in a separate method (without extending RobolectricTestRunner)

private void setupPackageManager() {
    ShadowApplication shadowApplication = shadowOf(Robolectric.application);
    shadowApplication.setPackageManager(mockPackageManager);
    List<ResolveInfo> activities = new ArrayList<ResolveInfo>();
    activities.add(resolveInfo("com.test.package.Class1"));
    activities.add(resolveInfo("com.test.package.Class2"));
    when(mockPackageManager.queryIntentActivities(any(Intent.class), anyInt())).thenReturn(activities);
}

Note : Here, i have used mockito & mocked the packagemanager for my unittest instead of using the actual PackageManager.

查看更多
The star\"
4楼-- · 2019-01-25 08:23

For Robolectric 3.1 you can

RobolectricPackageManager packageManager = RuntimeEnvironment.getRobolectricPackageManager();
// add necessary logic here
查看更多
Animai°情兽
5楼-- · 2019-01-25 08:26

I'm using Robolectric 2.3 for this. As noted in other answers, getPackageManager() does not return null, but shadowApplication.setPackageManager no longer exists.

Since you can't mock PackageManager, you can't give it a list of Intents to resolve for. Fortunately, Robolectric's PackageManager subclass, RobolectricPackageManager, can let you add these intents without a mock:

RobolectricPackageManager rpm = (RobolectricPackageManager)Robolectric.application.getPackageManager();
rpm.addResolveInfoForIntent(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS), new ResolveInfo());
查看更多
甜甜的少女心
6楼-- · 2019-01-25 08:32

For some reason you need to set the shadow packagemanager manually on your application. Create a custom test runner (by extending RobolectricTestRunner) and override the setApplicationState method:

public class MyTestRunner extends RobolectricTestRunner {   
  @Override
  public void setupApplicationstate(RobolectricConfig robolectricConfig) {
     super.setupApplicationState(robolectricConfig);
     ShadowApplication shadowApplication = shadowOf(Robolectric.application);
     shadowApplication.setPackageName(robolectricConfig.getPackageName());
     shadowApplication.setPackageManager(new RobolectricPackageManager(Robolectric.application, robolectricConfig));
  }
}

Then specify in your tests that you want to use your own test runner:

@RunWith(MyTestRunner.class)
public class MyTest { ... }
查看更多
可以哭但决不认输i
7楼-- · 2019-01-25 08:36

Just add on to aleph_null's answer, you can use ShadowResolveInfo.newResolveInfo() to quickly create a mock ResolveInfo ready to be used (I'm using Robolectric 2.4).

RobolectricPackageManager rpm = (RobolectricPackageManager)Robolectric.application.getPackageManager();
rpm.addResolveInfoForIntent(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS), ShadowResolveInfo.newResolveInfo(...));
查看更多
登录 后发表回答