Extending Robolectric

时间:2023-12-23 09:02:14

Robolectric is a work in progress, and we welcome contributions from the community. We encourage developers to use the standard GitHub workflow to fork, enhance, and submit pull requests to us.

Shadow Classes

Robolectric defines many shadow classes, which modify or extend the behavior of classes in the Android OS. When an Android class is instantiated, Robolectric looks for a corresponding shadow class, and if it finds one it creates a shadow object to associate with it. Every time a method is invoked on an Android class, Robolectric ensures that the shadow class' corresponding method is invoked first (if there is one), so it has a chance to work its magic. This applies to all methods, even static and final methods, because Robolectric is extra tricky!

//robolectric定义了很多影子类,这些类修改或者继承了Android操作系统中类的行为,当一个android类被初始化了,robolectric寻找一个对应的影子类,而且如果它找到了他就会创建一个与它关联的影子对象。每次一个方法被Android类调用,rebolectric都会确保影子类对应的方法会首先被调用(如果有这个对应的方法),影子应用到了所有的方法,深知静态和final方法。因为robolectrix很牛逼。

What's in a Name?

Why "Shadow?" Shadow objects are not quite Proxies, not quite Fakes, not quite Mocks or Stubs. Shadows are sometimes hidden, sometimes seen, and can lead you to the real object. At least we didn't call them "sheep", which we were considering.

//反正就是和之前的原生的功能不是很一样。这个名字已经很不错了,和我们之前考虑过的“胆小鬼”相比。

Adding Functionality

If the shadow classes provided with Robolectric don't do what you want, it's possible to change their behavior for a single test, a group of tests, or for your whole suite. Simply declare a class (let's say ShadowFoo) and annotate it @Implements(Foo.class). Your shadow class may extend one of the stock Robolectric shadows if you like. To let Robolectric know about your shadow, annotate your test method or class with the @Config(shadows=ShadowFoo.class), or create a file calledorg.robolectric.Config.properties containing the line shadows=my.package.ShadowFoo.

//如果影子类提供的没有你想要的,对于某一个测试,或者一些测试,或者全部的测试类做一些修改是必要的。只需要声明一个类然后注释它@Implements。为了让robolectric知道你的影子,使用 @Config(shadows=ShadowFoo.class)注释你的测试方法或者类

From Robolectric 2.0 on, the number of shadow classes needed is greatly reduced, because real Android OS code is present. Methods on your shadow class are able to call through to the Android OS code if you like, using Robolectric.directlyOn().

Shadow Classes

Shadow classes always need a public no-arg constructor so that the Robolectric framework can instantiate them. They are associated to the class that they Shadow with an @Implements annotation on the class declaration. In general, they should be implemented as if from scratch, the facilities of the classes they Shadow have almost always been removed and their data members are difficult to access. The methods on a Shadow class usually either Shadow the methods on the original class or facilitate testing by setting up return values or providing access to internal state or logged method calls.

//shadow类总是需要一个公有的午餐构造方法以便让robo框架可以初始化。他们会和使用 @Implements 声明的类关联起来作为它们的影子。影子类中的方法要么是对原来类中方法的重复,要么修改了返回值或者提供的全局变量的访问或者记录方法调用轨迹。

Shadow classes should mimic the production classes' inheritance hierarchy. For example, if you are implementing a Shadow forViewGroupShadowViewGroup, then your Shadow class should extend ViewGroup's superclass's Shadow, ShadowView.

//影子类应该同样照搬原类的继承关系。

  ...
@Implements(ViewGroup.class)
public class ShadowViewGroup extends ShadowView {
...

Methods

Shadow objects implement methods that have the same signature as the Android class. Robolectric will invoke the method on a Shadow object when a method with the same signature on the Android object is invoked.

Suppose an application defined the following line of code:java ... this.imageView.setImageResource(R.drawable.pivotallabs_logo); ...

Under test the ShadowImageView#setImageResource(int resId) method on the Shadow instance would be invoked.

//假设应用中定义了一行代码 this.imageView.setImageResource(R.drawable.pivotallabs_logo);,那在测试下影子类的实例的对应影子方法也会被调用

Shadow methods must be marked with the @Implementation annotation. Robolectric includes a lint test to help ensure this is done correctly.

//影子方法必须使用 @Implementation注释标记。

@Implements(ImageView.class)
public class ShadowImageView extends ShadowView {
...
@Implementation
public void setImageResource(int resId) {
// implementation here.
}
}

It is important Shadow methods are implemented on the corresponding Shadow of the class in which they were originally defined. Otherwise Robolectric's lookup mechanism will not find them (even if they have been declared on a Shadow subclass.) For example, the method setEnabled() is defined on View. If a setEnabled() method is defined on ShadowViewGroup instead of ShadowViewthen it will not be found at run time even when setEnabled() is called on an instance of ViewGroup.

//shadow方法只会实现对应原类的方法,子类的实现在这个逻辑里无效不可继承

Shadowing Constructors

Once a Shadow object is instantiated, Robolectric will look for a method named __constructor__ which has the same arguments as the constructor that was invoked on the real object.

//一旦shadow对象被初始化,robo将会寻找和真实对象中被调用的参数一样的,名字为 __constructor__的方法调用。

For instance, if the application code were to invoke the TextView constructor which receives a Context:

new TextView(context);

Robolectric would invoke the following __constructor__ method that receives a Context:

@Implements(TextView.class)
public class ShadowTextView {
...
public void __constructor__(Context context) {
this.context = context;
}
...

Getting access to the real instance

Sometimes Shadow classes may want to refer to the object they are shadowing, e.g. to manipulate fields. A Shadow class can accomplish this by declaring a field annotated @RealObject:

//有时候shadow类可能想要引用他们shadow的对象,例如,去操作变量。一个shadow类可以通过声明@RealObject注解的对象来实现

@Implements(Point.class)
public class ShadowPoint {
@RealObject private Point realPoint;
...
public void __constructor__(int x, int y) {
realPoint.x = x;
realPoint.y = y;
}
}

Robolectric will set realPoint to the actual instance of Point before invoking any other methods.

It is important to note that methods called on the real object will still be intercepted and redirected by Robolectric. This does not often matter in test code, but it has important implications for Shadow class implementors. Since the Shadow class inheritance hierarchy does not always mirror that of their associated Android classes, it is sometimes necessary to make calls through these real objects so that the Robolectric runtime will have the opportunity to route them to the correct Shadow class based on the actual class of the object. Otherwise methods on Shadows of base classes would be unable to access methods on the Shadows of their subclasses.