3

When running Robolectric tests, RuntimeEnvironment.application's type is determined by your configuration. Say I configured RoboApplication.class as my test application, I can cast RuntimeEnvironment.application to my type without fail.

RoboApplication app = (RoboApplication) RuntimeEnvironment.application;
app.doSomething();

However, once I integrate PowerMock, the cast line fails with

java.lang.ClassCastException: RoboApplication cannot be cast to RoboApplication

How can I workaround this issue?

Some Noob Student
  • 14,186
  • 13
  • 65
  • 103

2 Answers2

4

This is a problem because PowerMock and Robolectric are mutually incompatible due to the use of their own classloaders.

Even though the names are the same, the Class objects aren't actually the same: Robolectric and PowerMock both work by loading the test through custom classloaders. These classloaders change the classes in question, allowing you to replace static/final Android system classes and methods [Robolectric] or all static/final classes [PowerMock]. This is part of the reason that PowerMock and Robolectric both rely on having their own JUnit4 Runner: That way they can load the appropriate classes from their own modifying classloaders.

Because of this, the instances can't be cast to one anothers' classes, even though they have the same name and originate from the same source file: Each framework can change the class implementation, so they aren't necessarily compatible with one another.

You'll need to choose one framework or the other: Use Robolectric shadows, possibly with EasyMock or Mockito directly, or use PowerMock to stub the Android infrastructure calls yourself manually.

See also:

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
1

I needed also an app reference in order to start a Dagger2 module. After several attempts and getting the same cast exception error you are getting I made my app as follows

public class App extends Application {

private static AppComponent appComponent;

@Override
public void onCreate() {
    super.onCreate();

    if( appComponent==null ){

        appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
    }
}

public static AppComponent getAppComponent() {
    return appComponent;
}

public static void setAppComponent(AppComponent component){
    appComponent = component;
}

}

And within my Robolectric/PowerMock tester:

@Before
public void before() throws Exception {

    App appMocked = PowerMockito.mock(App.class);
    App.setAppComponent(DaggerAppComponent.builder().appModule(new AppModule(appMocked)).build());
    ....
}

Then my activity simply called up for App.getAppComponent().inject(this);

FYI, I tried not to mocked the app class and used ((App)RuntimeEnvironment.application), but that didn't work. I also tried to subclass it, and use it in Robolectric's application configuration, but ended up with the casting issue. So I hope this can be of any help.

Of course, that setter shouldn't go in production. But this is the only way I could figure this to work.

Juan Mendez
  • 2,658
  • 1
  • 27
  • 23