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?
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:
ClassCastException when casting to the same class
cast across classloader?
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.
Related
I'm trying to create an annotation processor which will process my MVP views (fragments) to auto-generated Subcomponents (similar to https://github.com/lukaspili/Auto-Dagger2, but for the new Dagger 2.10 android injectors)
So far, I've been able to generate appropriate files, but there is a strange error message when compiling generated components
Error:(22, 58) error: #dagger.android.support.FragmentKey methods should bind dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>, not dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>. See google.github.io/dagger/android
The structure of Factory module and Subcomponent files should be correct, because as soon as I copy-paste the generated classes and create a regular classes (both Factory module and Subcomponent) and use real classes instead of generated ones, the message is no longer shown and compilation succeeds
It seems like the problem lies in AndroidMapKeyValidator (link), where !MoreTypes.equivalence().equivalent(returnType, intendedReturnType) call apparently fails, but I don't have much of an experience debugging annotation processors, so I don't know why precisely...
Can maybe anyone help where to search for the problem?
Thanks
FYI: MyFragment does extend android.support.v4.app.Fragment
My files:
Generated Factory
#Module
public interface BuildersModule {
#Binds
#IntoMap
#FragmentKey(MyFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> factory(MySubcomponent.Builder builder);
}
Generated subcomponent
#Subcomponent(modules = MyModule.class)
public interface MySubcomponent extends AndroidInjector<MyFragment> {
MyPresenter presenter();
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MyFragment> {}
}
If anyone is interested in solution:
I found out that for some reason, references of ClassType-s compared during project's compile time are not the same when validating generated method.
And these references, despite the fact that they are pointing to the same class, are checked for equality in auto-common library in EqualVisitor.visitDeclared method. Apparently, this can be a bug in auto-common, because Elements in visitDeclared are compared by object reference, but not type reference.
So workaround here is to use local fixed copy of auto-common library and exclude all dependencies of the original library.
//TODO think if this is the correct solution to cast both elements
//return aElement.equals(bElement)
return ((TypeElement) aElement).getQualifiedName().equals(((TypeElement) bElement).getQualifiedName())
&& equal(a.getEnclosingType(), b.getEnclosingType(), newVisiting)
&& equalLists(a.getTypeArguments(), b.getTypeArguments(), newVisiting);
I still have to check why those references are not the same, and I have to think how the equality check can be fixed properly in auto-common (I use just a quickfix) before filing an issue in auto-common repo.
#Test
public void testWhenUserNameAndPasswordAreEnteredShouldAttemptLogin() throws Exception {
LoginView loginView = Mockito.mock(LoginView.class);
Mockito.when(loginView.getUserName()).thenReturn("George");
Mockito.when(loginView.getPassword()).thenReturn("aaaaaa");
loginPresenter.setLoginView(loginView);
loginPresenter.onLoginClicked();
Mockito.verify(loginPresenter).attemptLogin(loginView.getUserName(), loginView.getPassword());
}
This is my test, but as loginPresenter is a class generated from AndroidAnnotations and it is final, I cannot spy on it.
So is there another way (not necessarily using mockito) to verify that this method has been invoked?
PowerMock lets you mock final classes and methods (and static methods, etc).
For 'regular' Java project overriding the dependencies in the unit tests with mock/fake ones is easy. You have to simply build your Dagger component and give it to the 'main' class that drives you application.
For Android things are not that simple and I've searched for a long time for decent example but I was unable to find so I had to created my own implementation and I will really appreciate feedback is this a correct way to use Dagger 2 or there is a simpler/more elegant way to override the dependencies.
Here the explanation (project source can be found on github):
Given we have a simple app that uses Dagger 2 with single dagger component with single module we want to create android unit tests that use JUnit4, Mockito and Espresso:
In the MyApp Application class the component/injector is initialized like this:
public class MyApp extends Application {
private MyDaggerComponent mInjector;
public void onCreate() {
super.onCreate();
initInjector();
}
protected void initInjector() {
mInjector = DaggerMyDaggerComponent.builder().httpModule(new HttpModule(new OkHttpClient())).build();
onInjectorInitialized(mInjector);
}
private void onInjectorInitialized(MyDaggerComponent inj) {
inj.inject(this);
}
public void externalInjectorInitialization(MyDaggerComponent injector) {
mInjector = injector;
onInjectorInitialized(injector);
}
...
In the code above:
Normal application start goes trough onCreate() which calls initInjector() which creates the injector and then calls onInjectorInitialized().
The externalInjectorInitialization() method is ment to be called by the unit tests in order to set the injector from external source, i.e. a unit test.
So far, so good.
Let's see how the things on the unit tests side looks:
We need to create MyTestApp calls which extends MyApp class and overrides initInjector with empty method in order to avoid double injector creation (because we will create a new one in our unit test):
public class MyTestApp extends MyApp {
#Override
protected void initInjector() {
// empty
}
}
Then we have to somehow replace the original MyApp with MyTestApp. This is done via custom test runner:
public class MyTestRunner extends AndroidJUnitRunner {
#Override
public Application newApplication(ClassLoader cl,
String className,
Context context) throws InstantiationException,
IllegalAccessException,
ClassNotFoundException {
return super.newApplication(cl, MyTestApp.class.getName(), context);
}
}
... where in newApplication() we effectively replace the original app class with the test one.
Then we have to tell the testing framework that we have and want to use our custom test runner so in the build.gradle we add:
defaultConfig {
...
testInstrumentationRunner 'com.bolyartech.d2overrides.utils.MyTestRunner'
...
}
When a unit test is run our original MyApp is replaced with MyTestApp. Now we have to create and provide our component/injector with mocks/fakes to the app with externalInjectorInitialization(). For that purpose we extends the normal ActivityTestRule:
#Rule
public ActivityTestRule<Act_Main> mActivityRule = new ActivityTestRule<Act_Main>(
Act_Main.class) {
#Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
OkHttpClient mockHttp = create mock OkHttpClient
MyDaggerComponent injector = DaggerMyDaggerComponent.
builder().httpModule(new HttpModule(mockHttp)).build();
MyApp app = (MyApp) InstrumentationRegistry.getInstrumentation().
getTargetContext().getApplicationContext();
app.externalInjectorInitialization(injector);
}
};
and then we do our test the usual way:
#Test
public void testHttpRequest() throws IOException {
onView(withId(R.id.btn_execute)).perform(click());
onView(withId(R.id.tv_result))
.check(matches(withText(EXPECTED_RESPONSE_BODY)));
}
Above method for (module) overrides works but it requires creating one test class per each test in order to be able to provide separate rule/(mocks setup) per each test. I suspect/guess/hope that there is a easier and more elegant way. Is there?
This method is largely based on the answer of #tomrozb for this question. I just added the logic to avoid double injector creation.
1. Inject over dependencies
Two things to note:
Components can provide themselves
If you can inject it once, you can inject it again (and override the old dependencies)
What I do is just inject from my test case over the old dependencies. Since your code is clean and everything is scoped correctly nothing should go wrong—right?
The following will only work if you don't rely on Global State since changing the app component at runtime will not work if you keep references to the old one at some place. As soon as you create your next Activity it will fetch the new app component and your test dependencies will be provided.
This method depends on correct handling of scopes. Finishing and restarting an activity should recreate its dependencies. You therefore can switch app components when there is no activity running or before starting a new one.
In your testcase just create your component as you need it
// in #Test or #Before, just inject 'over' the old state
App app = (App) InstrumentationRegistry.getTargetContext().getApplicationContext();
AppComponent component = DaggerAppComponent.builder()
.appModule(new AppModule(app))
.build();
component.inject(app);
If you have an application like the following...
public class App extends Application {
#Inject
AppComponent mComponent;
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);
}
}
...it will inject itself and any other dependencies you have defined in your Application. Any subsequent call will then get the new dependencies.
2. Use a different configuration & Application
You can chose the configuration to be used with your instrumentation test:
android {
...
testBuildType "staging"
}
Using gradle resource merging this leaves you with the option to use multiple different versions of your App for different build types.
Move your Application class from the main source folder to the debug and release folders. Gradle will compile the right source set depending on the configuration. You then can modify your debug and release version of your app to your needs.
If you do not want to have different Application classes for debug and release, you could make another buildType, used just for your instrumentation tests. The same principle applies: Duplicate the Application class to every source set folder, or you will receive compile errors. Since you would then need to have the same class in the debug and rlease directory, you can make another directory to contain your class used for both debug and release. Then add the directory used to your debug and release source sets.
There is a simpler way to do this, even the Dagger 2 docs mention it but they don't make it very obvious. Here's a snippet from the documentation.
#Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
The Thermosiphon implements Pump and wherever a Pump is requested Dagger injects a Thermosiphon.
Coming back to your example. You can create a Module with a static boolean data member which allows you to dynamically switch between your real and mock test objects, like so.
#Module
public class HttpModule {
private static boolean isMockingHttp;
public HttpModule() {}
public static boolean mockHttp(boolean isMockingHttp) {
HttpModule.isMockingHttp = isMockingHttp;
}
#Provides
HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) {
return HttpModule.isMockingHttp ? mockImpl : impl;
}
}
HttpClient can be the super class which is extended or an interface which is implemented by OkHttpClient and MockHttpClient. Dagger will automatically construct the required class and inject it's internal dependencies just like Thermosiphon.
To mock your HttpClient, just call HttpModule.mockHttp(true) before your dependencies are injected in your application code.
The benefits to this approach are:
No need to create separate test components since the mocks are injected at a module level.
Application code remains pristine.
Hi I can't inject the RestService anywhere in my code.
I'm working test driven so I am using Robolectric to test this code. I hope it isn't a problem with AA+Robolectric, I don't have any experience with this.
The weird thing is that in my tests I can manually insert the generated RestClient_, but it doesn't get inserted automatically.
So I can do this:
RestClient rest = new RestClient_(activity.getApplicationContext());
but the following doesn't work:
#RestService
RestClient restClient;
I get a NullPointerException on restClient.
I also didn't forget the #EBean tag
#EBean
public class Player {
#RestService
RestClient restClient;
private int playerId;
public Player() {
}
public int getPlayerId() {
return playerId;
}
public List<Card> getHand() {
return restClient.getHand(playerId);
}
}
In the log I can see that Android Annotations has processed everything correctly.
This is my first project with Android Annotations and I can't grasp why the dependency injection doesn't work. No dependency injection removes almost all the benefit of using Android Annotations.
Thanks in advance!
Some extra information: I am instantiating my Player object in an Android Annotations-annotated REST Service. A code snippet of the method creating the Player object.
#Get(value = "/players/createAnonymous")
#Accept(MediaType.APPLICATION_JSON)
public Player createAnonymousPlayer();
Ok so here is the answer to my question. I hope it can be of use to other people who ask themselves the same as I did. Thanks to WonderCsabo for pointing me in the right direction.
Android Annotations generates the annotated classes at compile time. So Player becomes Player_. When using #Inject, Android Annotations will inject an instance of Player_ in the annotated field. However in my case the RestClient_ will return Player and not Player_ (So the player object without the processed annotations, so without the injected RestClient_).
It's not possible (not that I know of) to make the RestClient return Player_. This will not compile because the Player_ class doesn't exist yet at compile time.
Now I also understand the drawback that Android Annotations has. You can't easily test the code because you can't use android annotations in your tests. You can't swap the testclass with the generated class anywhere. You can't mock the injected classes either without using a runtime mock generator as PowerMock. So if you need dependency injection and easy testing, you should look elsewhere. (Dagger for example)
Just got my feet wet with roboguice, i like it!
I have quite a lot of methods that depend on a DB and LocationManger etc hence when i am testing these it uses the real objects, i would like to mock these objects so that when i am testing i don't have to depend on anything.
I also have been using mockito but i am unsure how i could go about this?
I know the android system comes with various mocks but i think it would be better to roll my own with mockito?
In either case i need to inject them when testing.
Anyone have any ideas on this?
Thanks in advance
Take a look at https://github.com/roboguice/roboguice/blob/master/astroboy/src/test/java/org/roboguice/astroboy/controller/Astroboy2Test.java which uses Modules.override() to override the default module with some test-specific configurations.
#Before
public void setup() {
// Override the default RoboGuice module
RoboGuice.setBaseApplicationInjector(Robolectric.application, RoboGuice.DEFAULT_STAGE, Modules.override(RoboGuice.newDefaultRoboModule(Robolectric.application)).with(new MyTestModule()));
// For roboguice 4.0 and robolectric 3.1.2
RoboGuice.getOrCreateBaseApplicationInjector(RuntimeEnvironment.application, RoboGuice.DEFAULT_STAGE, Modules.override(RoboGuice.newDefaultRoboModule(RuntimeEnvironment.application)).with(new MyTestModule()));
}
Just to expand on this as it was the top hit while I was looking for it...
Once you've set your test class (or test runner) to override the default RoboGuice module then set your overriden RoboGuice Module as (in this instance)
public class TestModule extends AbstractModule {
#Override
protected void configure() {
bind(LocationManager.class).toInstance((LocationManager) Robolectric.application.getSystemService(Context.LOCATION_SERVICE));
}
}
Then RoboGuice will inject the same location manager in your tests as in your application. And you can instantiate a shadow of it and set the expected location, provider state, etc.
#Test
public void mapLoadsCenteredOnPhoneLocationWhenNoTargetIntent() {
Location l = new Location("test");
l.setLatitude(Double.parseDouble("52.222"));
l.setLongitude(Double.parseDouble("-2.222"));
shadowLocationManager.setLastKnownLocation(GPS_PROVIDER, l);
shadowLocationManager.setProviderEnabled(GPS_PROVIDER, true);
shadowLocationManager.setProviderEnabled(NETWORK_PROVIDER, false);
//snip
}
#Martin: As Paul says you can inject your test location manager with Robolectric and Roboguice. However I think it's better if mocking with Mockito, this post is good for starting. You create a Mocked object and bind it to your interface. You can find also example with mocking and injecting.