-
Notifications
You must be signed in to change notification settings - Fork 0
Writing custom Patchers
The main challenge for gwt-test-utils has been to enable the instanciation of all GWT client side widgets in a standalone JVM and to simulate their behaviours in Java rather than in JavaScript. This is done by modifying GWT classes as they are loaded by a custom classloader gwt-test-utils provides to launch your tests.
But don't worry, even if you are interested in writing your own custom bytecode modifications, you will never have to deal with classloading stuff : gwt-test-utils provide a very simple "Patcher" API, which handles all loading and bytecode modification issues behind the scene.
Providing a "patch" for an existing class is done in two steps :
- writing some
PatchClass
for this class - registering thoses patch classes to gwt-test-utils
The patching mechanism relies on 2 annotations and some convention. Let's introduce them with an example taken from the UiObject
class from the GWT API :
public abstract class UIObject {
private native void replaceNode(Element node, Element newNode) /*-{
var p = node.parentNode;
if (!p) {
return;
}
p.insertBefore(newNode, node);
p.removeChild(node);
}-*/;
}
The private method replaceNode
implementation is written in JSNI, so it can't be executed in a standard JVM.
That's why we need to replace it by a Java implementation. This is done with a PatchClass
:
@PatchClass(UIObject.class)
class UIObjectPatcher {
@PatchMethod
static void replaceNode(UIObject uiObject, Element node, Element newNode) {
Node parent = node.getParentNode();
if (parent != null) {
parent.insertBefore(newNode, node);
parent.removeChild(node);
}
}
}
- Mark your class with
@PatchClass
to tell gwt-test-utils on which classes it should be applied. - Mark your substitution method with
@PatchMethod
. A patch method must follow several conventions :
- Static method only.
- Same name as the method to substitute.
- Same return type.
- If the method to substitute was static, the patcher method must declare exactly the same parameters in its signature. Otherwise, it must take a reference to the initial caller (or any compatible type) as its first argument, followed by the same parameters as the method to substitute.
In your META-INF/gwt-test-utils.properties
file (which must be available in your test classpath), simply add this line :
my.patcher.package = scan-package
As you may have guessed, my.patcher.package must by the package of your @PatchClass
annotated classes.
Before loading any class, gwt-test-utils will scan every file and directory within my/patcher/package
to find classes with the @PatchClass
annotation.
You could also have declared my.patcher
as the root package to scan, but you should consider performance concerns when scanning a large number of files.
Note that your patch classes and patch methods can either be public or private. The framework will make them public at runtime, regardless of their declared visibility.
You may want to patch a private class. In this case, the annotation parameter @PatchClass(MyPrivateClass.class)
won't fit since you can't reference MyPrivateClass
.
Well, just use it this way :
@PatchClass(target = "my.package.MyPrivateClass")
class MyPrivateClassPatcher {
...
}
The target
attribute of @PatchClass
simply takes the full name of the class you want the patcher to be applied on instead of the Class type itself.
In some case, you might want to override an already provided patch method. This example shows how to override the UiObject.replaceNode()
java implementation provided by gwt-test-utils :
@PatchClass(UIObject.class)
class MyUiObjectOverridenPatcher {
@PatchMethod(override = true)
static void replaceNode(UIObject uiObject, Element node, Element newNode) {
// new java code here
}
}
Simply add an override = true
attribute to your new implementation and the trick is done ! Just remember there can only be one override PatchMethod
in your patch classes.