Squish Expert Tip: Java object mapping in Squish for Android (vs. Squish for Java)

Squish Expert Tip: Java object mapping in Squish for Android (vs. Squish for Java)

The Squish for Java edition wraps all seen Java objects for access in test script with a type which name is the fully qualified Java name, dots being replaced by underscores. E.g. a java.lang.String Java object is wrapped as object with type java_lang_String. On such a wrapped Java object, public methods can be called and access to public fields and objects can be created in Squish test scripts. E.g. the waitforObject Squish script API function returns such a wrapped Java object.
An example of a java.lang.String creation with a string “hello” in JavaScript, which then is converted to uppercase and added to the test result, is

var obj = new java_lang_String("hello");
var upper = obj.toUpperCase();
test.log(upper);

Note that for each of the script languages Squish offers, creating an object instance looks a bit different. For Squish’s other script languages, please see here how to create new Java objects.

Java object mapping in the Squish for Android edition

The Android edition of Squish works with Squish own object types, which tries to capture all e.g. Java button variations into one type, e.g. Button. The underlying Java object is still reachable, via a property called nativeObject on the Squish object. Using the Java object has not changed with respect to the Squish for Java edition.

But there is one caveat, there are no bindings for constructing Java objects in the script language specific way (thus in case of JavaScript, using the new keyword). So one must fall back to using Java reflection.

Creating Java objects using Java reflection

To create the above java.lang.String object, first a Java Class for java.lang.String is needed. For such a class object we need to find the right constructor object. In the example above we created a java.lang.String with a string as argument. So let’s see which constructor we need.

var cls = Native.java.lang.Class.forName("java.lang.String");
var ctors = cls.getConstructors();
for (i = 0; i < ctors.length; ++i) {
    test.log(ctors.at(i).toGenericString());
}

Which list a rather long list (only showing the first 3 entries here)

public java.lang.String(byte[],int,int)
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
...

Note in comparison to the Squish for Java edition, the Java types start with Native. Squish for Android maps the Java classes in a separate namespace called Native.
Also the Java package is mapped in nested namespaces (see here how these namespaces are mapped in the different script languages).

Back to the string creation. we can find the right Constructor (API documentation) by matching the arguments we’re looking for.

for (var i = 0; i < ctors.length; ++i) {
    var types = ctors.at(i).getParameterTypes();
    if (types.length == 1 && types.at(0).getName() == "java.lang.String") {
        // found it
        break;
    }
}

Now that the right constructor is found, a call on its newInstance member will construct the object. To pass the arguments a Java array of type java.lang.Object must be created. In this case, an array with one string element “hello”.

var arrayCls = Native.java.lang.Class.forName("java.lang.reflect.Array");
var objCls = Native.java.lang.Class.forName("java.lang.Object");
var arguments = Native.java.lang.reflect.Array.newInstance(objCls, 1);
Native.java.lang.reflect.Array.set(arguments, 0, "hello");

The complete code is for the above creating of a “hello” java.lang.String, making it uppercase and adding to the test results, is

var cls = Native.java.lang.Class.forName("java.lang.String");
var ctors = cls.getConstructors();
for (i = 0; i < ctors.length; ++i) {
    var types = ctors.at(i).getParameterTypes();
    if (types.length == 1 && types.at(0).getName() == "java.lang.String") {
        var arrayCls = Native.java.lang.Class.forName("java.lang.reflect.Array");
        var objCls = Native.java.lang.Class.forName("java.lang.Object");
        var arguments = Native.java.lang.reflect.Array.newInstance(objCls, 1);
        Native.java.lang.reflect.Array.set(arguments, 0, "hello");
        var obj = ctors.at(i).newInstance(arguments);
        var upper = obj.toUpperCase();
        test.log(upper);
        break;
    }
}

This concludes the goal of this blog.

An important final note to make is that for classes from the Java runtime libraries, Class.forName (see here) can be used. For other classes please use the class loader of an application object class. Thus

var obj = waitForObject(":aName");
var cls = obj.nativeObject.getClass().getClassLoader().loadClass("com.acompany.aType");

 

0 Comments

Leave a reply

Your email address will not be published. Required fields are marked *

*