The first step is to write the Shadow class. Here is a simple Shadow for the android Point class:
import android.graphics.Point; import com.xtremelabs.robolectric.internal.Implementation; import com.xtremelabs.robolectric.internal.Implements; import com.xtremelabs.robolectric.internal.RealObject; @Implements(Point.class) public class ShadowPoint { @RealObject private Point realPoint; public void __constructor__(int x, int y) { realPoint.x = x; realPoint.y = y; } public void __constructor__(Point src) { realPoint.x = src.x; realPoint.y = src.y; } @Implementation public void set(int x, int y) { realPoint.x = x; realPoint.y = y; } @Implementation public final void offset(int dx, int dy) { realPoint.x += dx; realPoint.y += dy; } @Override @Implementation public String toString() { return "Point(" + realPoint.x + ", " + realPoint.y + ")"; } }
This example illustrates all of the most important aspects of writing Shadows. We start with an
@Implements
annotation that will tell Robolectric that our class is a Shadow for Android's Point
class. This annotation, along with the @Implementation
annotation (as shown on the set()
, offset()
, and toString()
methods) are the bread and butter of writing Shadow classes. The @Implementation
annotation tells Robolectric that the annotated method is meant to shadow the corresponding method with the same signature on the shadowed class. Note that
ShadowPoint
doesn't extend Point
. Robolectric creates an association between the two classes behind the scenes based on these annotations. Shadow classes mirror the inheritance hierarchy of the classes they shadow. So, for instance, the Shadow for TextView
would be a sub-class of the Shadow for View
. It is important to maintain this relationship so that inheritance will work properly within the Shadow class framework.The
@RealObject
annotation is used to ask Robolectric to provide instances of the Shadow class with a reference to the object that they are shadowing. The annotated field must be of the same type as the class being shadowed and will automatically be populated by Robolectric when the class is instantiated.While Robolectric provides control over the behavior of methods, it does not provide the same kind of control over access to member variables. So, when a class such as
Point
exposes its member variables (which, fortunately, is rare), its Shadow class should maintain the same behavior with respect to those member variables as the original class does. In our example we make ShadowPoint
s do this by getting and setting values on the fields of the shadowed Point
object; the @RealObject
annotation is the tool used to access the Point
. (Normally a Shadow will store information about the state of the shadowed object in its own fields and ignore the private fields on the shadowed object.)The
@RealObject
reference is also useful in any situation where the this
reference would be used, such as when an object is expected to pass itself as a parameter to other methods, return a reference to itself, or polymorphically call other methods on itself. For instance, in the following example the onClick()
method expects a parameter of type View
, so the Shadow must pass a reference to the object it shadows rather than itself.@Implements(View.class) public class ShadowView { @RealObject protected View realView; @Implementation public boolean performClick() { if (onClickListener != null) { onClickListener.onClick(realView); return true; } else { return false; } } }
Robolectric grants access to the constructors of shadowed objects by means of the special
__constructor__()
methods. Shadows themselves are always instantiated via their default constructors and their initial setup can be done at that time. Calls to the constructors of the shadowed class are diverted to the __constructor__()
method with the corresponding signature, where the Shadow can perform any further initialization tasks.If a Shadow needs to gain access to the Shadow of any other object (whether of the same or of a different class), it may use the
Robolectric.shadowOf_()
method. This may be useful if your Shadow needs to inquire about the state of another shadowed object in a way that the Android API does not normally expose.Once the Shadow class has been written it must be registered with Robolectric in order to become available for use in your unit tests. This is done by the
RobolectricTestRunner
during test initialization.If the new Shadow class is meant to be an addition to the standard set of Robolectric Shadows then it should be added to the list of shadow classes in the
getDefaultShadowClasses()
method of the com.xtremelabs.Robolectric
class. In this case, it is convention to also add a shadowOf()
method to the same class and specialize it for the new Shadow.If you are creating the Shadow for local use, you will need to register it in the
bindShadowClasses()
method of the subclass of RobolectricTestRunner
that you use in the @RunWith
annotation on your tests:public class CustomTestRunner extends RobolectricTestRunner { public CustomTestRunner(Class testClass) throws InitializationError { super(testClass); } @Override protected void bindShadowClasses() { Robolectric.bindShadowClass(ShadowPoint.class); } }
It's also possible to register (or change) the Shadow for an individual test calling
Robolectric.bindShadowClass()
within the test or set-up method; just make sure you do that before any instances of the class are instantiated by the test.
It would be helpful if you could provide some additional details on how to write the test cases for shadows. Looking at the existing code, I get that the tests are specific to the class being shadowed, but a test example would help complete this post.
ReplyDeleteYou can do this too:
ReplyDelete@Implements(SQLiteDatabase.class)
public class ShadowSQLiteDatabase extends com.xtremelabs.robolectric.shadows.ShadowSQLiteDatabase {
@Implementation
public int getVersion() {
return 15;
}
}
Whats the new way to do this for Roboelectric 2?
ReplyDeletePlease provide an example of how to register a custom shadow object using Eobolectric 2.
ReplyDeleteRefer this https://github.com/robolectric/robolectric/issues/561
DeleteThis blog post talks about configuring for Robolectric 2.
ReplyDeletehttp://robolectric.blogspot.com/2013/05/configuring-robolectric-20.html
In SAT planning has basic role for test preparation it is the serious problem of different parents for their children. Intelligent students who have good math concepts can clear it Math tutor with proper guidance. How we can prepare test in short time.
ReplyDeleteYou are a very good writer and information-sharing blogger. I have constantly visited your website and shared it with my friends after getting information from here. We Are Individual Stock Broker in the Indian Stock Market Open Your First Demat Account Now With Extreme Features at Low Charges
ReplyDeleteOpen Free Demat Account
Demat Account
Stock Market
You have made us aware of the important information about Linux, for which we thank you, you are a good writer and experienced, and we will share this thing with your friends. Mumbai Dating Service |
ReplyDelete