Search code examples
javaunit-testingreflectioncastingprivate

Android/Java Unit Test - How to use parameters in java.lang.reflect.Method?


I've got the following class with three public static methods:

package unittests;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestMethodsClass
{
    // Test method to run a private void Method from a class
    public static void runPrivateVoidMethod(Object ob, String methodName, Class<?>[] parameters){
        try {
            Method method = null;
            if(parameters == null){
                Class<?>[] nullParameter = (Class[])null;
                method = ob.getClass().getDeclaredMethod(methodName, nullParameter);
            }
            else
                method = ob.getClass().getDeclaredMethod(methodName, parameters);

            if(method != null){
                method.setAccessible(true);
                method.invoke(ob);
            }
        }
        catch (NoSuchMethodException ex){
            ex.printStackTrace();
        }
        catch (IllegalAccessException ex){
            ex.printStackTrace();
        }
        catch (IllegalArgumentException ex){
            ex.printStackTrace();
        }
        catch (InvocationTargetException ex) {
            ex.printStackTrace();
        }
    }

    // Test method to run a private Method that returns something from a class
    public static Object runPrivateReturnMethod(Object ob, String methodName, Class<?>[] parameters){
        Object returnObject = null;
        try {
            Method method = null;
            if(parameters == null){
                Class<?>[] nullParameter = (Class[])null;
                method = ob.getClass().getDeclaredMethod(methodName, nullParameter);
            }
            else
                method = ob.getClass().getDeclaredMethod(methodName, parameters);

            if(method != null){
                method.setAccessible(true);
                returnObject = method.invoke(ob);
            }
        }
        catch (NoSuchMethodException ex){
            ex.printStackTrace();
        }
        catch (IllegalAccessException ex){
            ex.printStackTrace();
        }
        catch (IllegalArgumentException ex){
            ex.printStackTrace();
        }
        catch (InvocationTargetException ex) {
            ex.printStackTrace();
        }
        return returnObject;
    }

    // Test method to access a private Field from a class
    public static void setPrivateField(Object ob, String fieldName, Object value){
        try {
            Field field = ob.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(ob, value);
        }
        catch (NoSuchFieldException ex){
            ex.printStackTrace();
        }
        catch (IllegalAccessException ex){
            ex.printStackTrace();
        }
        catch (IllegalArgumentException ex){
            ex.printStackTrace();
        }
    }
}

The purpose of these methods is to be able to call private methods or set private fields in MyObjectInstance from within a UnitTest-class. Some examples:

// public method equivalent-call: myObjectInstance.doSomething();
TestMethodsClass.runPrivateVoidMethod(myObjectInstance, "doSomething", null);

// public method equivalent-call: myObjectInstance.doSomething(String s);
TestMethodsClass.runPrivateVoidMethod(myObjectInstance, "doSomething", ?¿?¿?¿);

// public method equivalent-call: boolean b = myObjectInstance.doSomethingWithBooleanReturn();
boolean b = (boolean)TestMethodsClass.runPrivateReturnMethod(myObjectInstance, "doSomethingWithBooleanReturn", null);

// public method equivalent-call: String s = myObjectInstance.doSomethingWithStringReturn();
String s = (String)TestMethodsClass.runPrivateReturnMethod(myObjectInstance, "doSomethingWithStringReturn", null);

// public method equivalent-call: MyOtherObject moj = myObjectInstance.doSomethingWithMyOtherObjectReturn();
MyOtherObject moj = (MyOtherObject)TestMethodsClass.runPrivateReturnMethod(myObjectInstance, "doSomethingWithMyOtherObjectReturn", null);

// public method equivalent-call: boolean b = myObjectInstance.doSomethingWithBooleanReturn(String s);
boolean b = TestMethodsClass.runPrivateReturnMethod(myObjectInstance, "doSomethingWithMyOtherObjectReturn", ?¿?¿?¿);

// private boolean b;
// In-Object public field equivalent-set: b = true;
TestMethodsClass.setPrivateField(myObjectInstance, "b", true);

// private String s;
// In-Object public field equivalent-set: s = "a string";
TestMethodsClass.setPrivateField(myObjectInstance, "s", "a string");

Everything works as I want, except for one thing: How do I put the parameters in? So, what should I replace ?¿?¿?¿ with in the examples above? (And how should I change my public static methods to be able to use the parameters?)

I did try the following parameters so far (with no result). Some are not giving errors, but aren't being set (I've used set methods to test the parameters) and some are giving errors (like the int):

// This gives no errors, but doesn't work
new Class<?>[]{ myOtherObjectInstance.getClass() }) // <- parameter

// Error: The method runPrivateVoidMethod(Object, String, Class<?>[]) in the type TestMethodsClass is not applicable for the arguments (MyOtherObjectInstance, String, int)
int i = 5;
i // <- parameter

// Error: Type mismatch: cannot convert from int to Class<?>
int i = 5;
new Class<?>[]{ i } // <- parameter

// Error: Type mismatch: cannot convert from int to Class<?>
int i = 5;
new Class<?>[]{ (Class<?>)i } <- parameter

// This gives no errors, 
int[] iArray = new int[1];
iArray[0] = 5;
new Class<?>[]{ array.getClass() } // <- parameter

Preferably I just want to put something in (like an int, String, MyOtherObjectInstance, int[], etc. and cast/convert those parameters in the public static methods to useable Class<?>[] parameters.


EDIT 1:

Sanjeev's solution was very promising, but still doesn't work. Here are the changes to the methods:

// Test method to run a private void Method from a class
public static void runPrivateVoidMethod(Object ob, String methodName, Class<?>[] paramTypes, Object[] paramValues){
    try {
        Method method = null;
        if(paramTypes == null){
            method = ob.getClass().getDeclaredMethod(methodName, (Class[])null);
            if(method != null){
                method.setAccessible(true);
                method.invoke(ob);
            }
        }
        else{
            if(paramValues != null && paramTypes.length == paramValues.length){
                // TODO: Check if the paramTypes are in the same order as the paramValues

                method = ob.getClass().getDeclaredMethod(methodName, paramTypes);
                if(method != null){
                    method.setAccessible(true);
                    method.invoke(ob, paramValues);
                }
            }
            else
                runPrivateReturnMethod(ob, methodName, null, null);
        }
    }
    catch (NoSuchMethodException ex){
        ex.printStackTrace();
    }
    catch (IllegalAccessException ex){
        ex.printStackTrace();
    }
    catch (IllegalArgumentException ex){
        ex.printStackTrace();
    }
    catch (InvocationTargetException ex) {
        ex.printStackTrace();
    }
}

// Test method to run a private Method that returns something from a class
public static Object runPrivateReturnMethod(Object ob, String methodName, Class<?>[] paramTypes, Object[] paramValues){
    Object returnObject = null;
    try {
        Method method = null;
        if(paramTypes == null){
            method = ob.getClass().getDeclaredMethod(methodName, (Class[])null);
            if(method != null){
                method.setAccessible(true);
                returnObject = method.invoke(ob);
            }
        }
        else{
            if(paramValues != null && paramTypes.length == paramValues.length){
                // TODO: Check if the paramTypes are in the same order as the paramValues

                method = ob.getClass().getDeclaredMethod(methodName, paramTypes);
                if(method != null){
                    method.setAccessible(true);
                    returnObject = method.invoke(ob, paramValues);
                }
            }
            else
                returnObject = runPrivateReturnMethod(ob, methodName, null, null);
        }
    }
    catch (NoSuchMethodException ex){
        ex.printStackTrace();
    }
    catch (IllegalAccessException ex){
        ex.printStackTrace();
    }
    catch (IllegalArgumentException ex){
        ex.printStackTrace();
    }
    catch (InvocationTargetException ex) {
        ex.printStackTrace();
    }
    return returnObject;
}

And here is the UnitTest that I use:

public void testOrderedProductList(){
        // Arrange
        int amount = 6;

        // First product used in Constructor
        Product product1 = new Product();
        product1.setProductId(54);
        ...
        OrderedProduct orderedProduct = new OrderedProduct(product1, amount);

        // Second product used in the setProduct method
        Product product2 = new Product();
        product2.setProductId(12);

        // Invoke
        // HERE IS THE CALL TO THE runPrivateVoidMethod
        TestMethodsClass.runPrivateVoidMethod(orderedProduct, "setProduct", new Class<?>[]{ Product.class }, new Object[]{ product2 });
        Product p = orderedProduct.getProduct();
        ...

        // Assert
        //assertNotNull("product should not be null", p);
        assertTrue("product should be a Product-instance", p instanceof Product);
        assertEquals("product should equal the set product", product2, p);
        ...
    }

Which fails at: assertEquals("product should equal the set product", product2, p); (Expected <Product {ProductId=12, ... }> but was <Product {ProductId=54, ... }>

At request of Sanjeev's; The Product and OrderedProduct classes:

package models;

import business.V;
import android.util.Log;

public class Product
{
    private int ProductId;
    private String Name;
    private int CategoryId;
    private double Price;
    private boolean Visible;

    private int check_option;

    public Product(){
        check_option = 0;
    }

    // Overriding this class' toString method for print-out purposes
    @Override
    public String toString(){
        return "Product {" +
                    "ProductId=" + ProductId + ", " +
                    "Name=" + Name + ", " +
                    "CategoryId=" + CategoryId + ", " +
                    "Price=" + Price + ", " +
                    "Visible=" + Visible + ", " +
                    "check_option=" + check_option +
                "}";
    }

    // Getter and Setter of the ProductId
    public void setProductId(int id){
        if(id > 0)
            ProductId = id;
        else
            ProductId = 0;
    }
    public int getProductId(){
        return ProductId;
    }

    // Getter and Setter of the Name
    public void setName(String n){
        if(V.notNull(n, true))
            Name = n;
        else
            Name = null;
    }
    public String getName(){
        return Name;
    }

    // Getter and Setter of the CategoryId
    public void setCategoryId(int id){
        if(id > 0)
            CategoryId = id;
        else
            CategoryId = 0;
    }
    public int getCategoryId(){
        return CategoryId;
    }

    // Getter and Setter of the Price
    public void setPrice(double p){
        if(p > 0.00)
            Price = p;
        else
            p = 0.00;
    }
    public double getPrice(){
        return Price;
    }

    // Getter and Setter of the Visible
    public void setVisible(boolean v){
        Visible = v;
    }
    public boolean getVisible(){
        return Visible;
    }

    // Getter and Setter of the CheckOption
    public void setCheckOption(int o){
        Log.i("PRODUCT CHECK", "TEST - Product (" + ProductId + ") check option changed from " + check_option + " to " + o);

        if(o >= 0 && o < Config.NUMBER_OF_CHECK_OPTIONS)
            check_option = o;
        else
            check_option = 0;
    }
    public int getCheckOption(){
        return check_option;
    }
}


package models;

public class OrderedProduct
{
    private Product Product;
    private int Amount;

    public OrderedProduct(Product p, int a){
        setProduct(p);
        setAmount(a);
    }

    // Overriding this class' toString method for print-out purposes
    @Override
    public String toString(){
        return "OrderedProduct {" +
                    "Product=" + Product + ", " +
                    "Amount=" + Amount +
                "}";
    }

    // Getter and Setter of the Product
    // (The Setter is private since we only use it in the Constructor)
    private void setProduct(Product p){
        Product = p;
    }
    public Product getProduct(){
        return Product;
    }

    // Getter and Setter of the Amount
    public void setAmount(int a){
        if(a >= 0)
            Amount = a;
        else
            Amount = 0;
    }
    public int getAmount(){
        return Amount;
    }
}

Thanks in advance for the responses.


Solution

  • You need another parameter to pass in your object values to the invoked method.

    So with that said you need to add another parameter to your runPrivateVoidMethod with type as Object[]

       public static void runPrivateVoidMethod(Object ob, String methodName, Class<?>[] paramTypes, Object[] paramvalues)
    

    NOTE: size of paramType and paramValues must match and paramValues must contain the value at specified index with the same type as defined on same index in paramTypes.

    get the method using

      method = ob.getClass().getDeclaredMethod(methodName, paramTypes);
    

    Then use Method#invoke(Object obj,Object... args) to pass your parameters to invoked method.

      method.invoke(ob,paramValues);
    

    Hope this gives you some idea.