Robolectric 3.x and Shadows for own classes

1,802 views
Skip to first unread message

Alexander Stark

unread,
Jul 2, 2015, 9:45:40 AM7/2/15
to robol...@googlegroups.com
Hi,

I read a lot above how it could be possible to write custom shadows for own classes with older Robolectric versions than 2.x
But is it possible to write custom shadows for own classes (Non-Android classes) with latest Robolectric version?
I really appreciate any approach. Also non common ways.

Thanks
Alex

Kanishk Tripathi

unread,
Jul 2, 2015, 10:21:36 AM7/2/15
to robol...@googlegroups.com
There was a way to do it in robolectric 3-rc2. But the code was greatly modified in robolectric 3-rc3.
Here's a way to do it in robolectric 3-rc-3.
Create a custom test runner extending RbolectricGradleTestRunner.
Override this method:

@Override
public InstrumentationConfiguration createClassLoaderConfig() {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
builder.addInstrumentedPackage("com.example.yourClassToShadowPackage");
return builder.build();
}

Then create your shadow class just as usual.
@Implements(ClassToShadow.class)
public class MyShadowClass {
  @Implementation
public void myShadowMethod() {}
}

Make sure that the package hierarchy does not contain the classes which you are testing in your unit tests. Otherwise test cases will fail.
I am working on a more elegant way to do.

Michael Grafton

unread,
Jul 2, 2015, 12:41:03 PM7/2/15
to robol...@googlegroups.com
Alexander,

You might have already heard this shpiel, but just to make sure...It is generally the opinion of Robolectric maintainers that you should avoid writing Shadows for your own code. Shadows are a last ditch effort to make the Android platform safe to invoke on a desktop JDK. Unless you have no other options and are locked into a legacy code situation where you absolutely can't control your dependencies, I'd consider another approach.

Mike

--
You received this message because you are subscribed to the Google Groups "Robolectric" group.
To unsubscribe from this group and stop receiving emails from it, send an email to robolectric...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alexander Stark

unread,
Jul 16, 2015, 2:58:46 AM7/16/15
to robol...@googlegroups.com
Thanks all.
But now I'm facing the next challenge. How can I get the shadow of my own Class. Shadows.shadowOf does not work. In version 2.4 there was a helper method shadowOf_(...). Where can I find it in 3.0?

Alexander Stark

unread,
Jul 16, 2015, 3:38:47 AM7/16/15
to robol...@googlegroups.com
Hi Kanishk,

this approach works fine in Android Studio. But when I try to run the test on command line it fails.
I get a comparison failure:

com.citrix.robolectricshadow.MyClassToShadowTest > setTestMemberWithValue FAILED
    junit.framework.ComparisonFailure at MyClassToShadowTest.java:20

Any thoughts?

Jonathan Gerrish

unread,
Jul 16, 2015, 3:55:05 AM7/16/15
to robol...@googlegroups.com

ShadowExtrator

Alexander Stark

unread,
Jul 16, 2015, 4:22:45 AM7/16/15
to robol...@googlegroups.com
Thanks. This works fine and as expected.

But sorry, I have the next issue.
I tried ti implement a shadow method of my real object. In general works. But now I added a call of the method on realobject:

@RealObject
private MyClassToShadow realMyClassToShadow;

@Implementation
public void setTestMember(String value) {

realMyClassToShadow.setTestMember(value + testPrefix);
}


This leads me in a StackOverflowError
at com.citrix.robolectricshadow.MyClassToShadow.setTestMember(MyClassToShadow.java)
at com.citrix.robolectricshadow.shadows.MyShadowClassToShadow.setTestMember(MyShadowClassToShadow.java:20)

Erich Douglass

unread,
Jul 17, 2015, 11:22:24 AM7/17/15
to robol...@googlegroups.com
When you want to call a method on the real object from inside a shadow, you need to use:

directlyOn(realMyClassToShadow, MyClassToShadow.class).setTestMember(value + testPrefix)

This will bypass the shadowing mechanism. Otherwise, Robolectric will attempt to call the method on a shadow, and you'll end up with a StackOverflowError. However, like Mike mentioned above, you should't be creating shadows for classes you control.

Kanishk Tripathi

unread,
Jul 17, 2015, 4:36:47 PM7/17/15
to robol...@googlegroups.com
Hi Eric,

In our project, while using Robolectric, we had to shadow one of our own class as it was calling our custom native methods. Those are static block and method calls and called on application startup.
Now had it been during the test, I could have created mocks. But that class is called from the custom Application class in startup. So I had no other option than create a shadow.
If there's any other approach, it will be most welcome.

Also, just out of curiosity, why we shouldn't shadow our own classes?

Alexander Stark

unread,
Jul 21, 2015, 9:39:06 AM7/21/15
to robol...@googlegroups.com
Where can I find the method directlyOn? I read that it is availabe in Robolectric 2 with Robolecrtic.directlyOn(....). But symbol can not be found in Robolectric 3

Jonathan Gerrish

unread,
Jul 21, 2015, 9:58:33 AM7/21/15
to robol...@googlegroups.com
org.robolectric.internal.Shadow#directlyOn(java.lang.Class<T>, java.lang.String, org.robolectric.util.ReflectionHelpers.ClassParameter...)

Michael Grafton

unread,
Jul 21, 2015, 12:18:39 PM7/21/15
to robol...@googlegroups.com
Hi Kanishk,

The reason we advocate not using Shadows for you own code is that they are probably the most difficult type of test double to work with and understand. You would be much better off using dependency injection and mocks/fakes. 

It might require reworking your code, but at the end of the day your code should just declare a dependency on an interface, and at test time a fake implementation can be provided by your dependency injection system. You can try Dagger (or Dagger 2), or RoboGuice, or roll your own simple DI. 

That being said, static blocks that call native methods are going to be tricky; if any codepath references that class, they will be called. If you can avoid static blocks, you will be happier.  But, from what I recall, the standard pattern for native code is to use static blocks. So as long as your DI container (in test mode) does not try to reference the "real" class, I think you will be OK.

Mike

Sami Maaref

unread,
Dec 10, 2015, 7:04:07 AM12/10/15
to Robolectric
Hello,

i've same problem. With Android Studio, my Custom Shadow class is used.
But when i launch test in command line (./gradlew test --continue), my custom shadow class isn't used and my test fail.

have we an idea about this problem?

public class ShadowRobolectricGradleTestRunner extends RobolectricGradleTestRunner{
/**
* The list of the customized shadows classes name.
* Note they should be full qualified.
*/
private static final String[] CUSTOMIZED_SHADOWS_NAME = {
DataManager.class.getName()
};

public ShadowRobolectricGradleTestRunner(Class<?> klass) throws InitializationError {
super(klass);

}

@Override
public InstrumentationConfiguration createClassLoaderConfig() {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
        for(String packageName : CUSTOMIZED_SHADOWS_NAME){
builder.addInstrumentedClass(packageName);
}
return builder.build();
}
}

@Implements(DataManager.class)
public class ShadowDataManager {
    private Context context;
    public void __constructor__(Context context) {
this.context = context;
}

    @Implementation
    public String getCustomers() {
        return "shadowCustomers";
}
}
Reply all
Reply to author
Forward
0 new messages