How do I use Powermock's Whitebox.invokeMethod(Object instance, Object... arguments) when my first method parameter is String type?

pluralMonad picture pluralMonad · Sep 24, 2015 · Viewed 26.5k times · Source

I would like to not explicitly name the method I am invoking in the invokeMethod() arguments. Powermock offers an overloaded invokeMethod() that infers the method based on the parameters passed.

invokeMethod(Object instance, Object... arguments)

The problem I am running into is that my first parameter is of type String. This invokes the invokeMethod() with the signature,

invokeMethod(Object instance, String methodToExecute, Object... arguments)

Here is a model of the test...

@Test
public void thisIsATest() throws Exception{
    TheClassBeingTested myClassInstance = new TheClassBeingTested();
    String expected = "60";
    String firstArgument = "123A48"; 

    ReturnType returnedTypeValue = Whitebox.invokeMethod(myClassInstance, firstArgument, AnEnum.TypeA);
    String actual = returnedTypeValue.getTestedField();
    assertEquals("Expected should be actual when AnEnum is TypeA", expected, actual);
}

This gives me the error,

org.powermock.reflect.exceptions.MethodNotFoundException: No method found with name '123A48' with parameter types: [ AnEnum ] in class TheClassBeingTested.`

I got it to work by changing the first parameter's type to Object, but this feels dirty to me.

@Test
public void thisIsATest() throws Exception{
    TheClassBeingTested myClassInstance = new TheClassBeingTested();
    String expected = "60";
    Object firstArgument = "123A48"; 

    ReturnType returnedTypeValue = Whitebox.invokeMethod(myClassInstance, firstArgument, AnEnum.TypeA);
    String actual = returnedTypeValue.getTestedField();
    assertEquals("Expected should be actual when AnEnum is TypeA", expected, actual);
}

Is there a correct way to pass a String type as the first argument while not hard coding my method name into the invokeMethod() call? I have found nothing in the Powermock documentation or forums addressing this, but it certainly cannot be all that uncommon.

Answer

dfashimpaur picture dfashimpaur · Sep 24, 2015

What you really need to do is look at TheClassBeingTested.java. The error message is telling you that the problem is that Whitebox.invoke method cannot find a method named "123A48" in TheClassBeingTested it is creating by reflection. In this case, the invokeMethod I think you have chosen is looking for parameters (Object classUnderTest, String methodName, Object...parameters).

Try something like this:

public class TheClassBeingTested {
    private String foo;

    public void setFoo(String fooValue) {
        foo = fooValue;
    }

    public String getFoo() {
        return foo;
    }

}

Then you can test with Whitebox like this:

public class TheClassBeingTestedTester {

    @Test
    public void thisIsATest() throws Exception {
        TheClassBeingTested toBeTested = new TheClassBeingTested();
        String theMethodToTest = "setFoo";
        String expectedFooValue = "foo bar baz";

        ReturnType returnedTypeValue = Whitebox.invokeMethod(toBeTested, theMethodToTest, expectedFooValue);
        String actual = returnedTypeValue.getTestedField();
        assertEquals("Expected " + expected + " but found " + actual, expected, actual);
     }
}

Hope that helps.

... edited response below

As I failed to read your question carefully while also working on other development, I missed the point.

In this case, I would make the following modification to your test to avoid the invoke method ambiguity issue:

@Test
public void thisIsATest() throws Exception{
    TheClassBeingTested myClassInstance = new TheClassBeingTested();
    String expected = "60";

    Object[] parameters = new Object[]{"123A48", AnEnum.TypeA};

    ReturnType returnedTypeValue = Whitebox.invokeMethod(myClassInstance, parameters);

    String actual = returnedTypeValue.getTestedField();
    assertEquals("Expected should be actual when AnEnum is TypeA", expected, actual);

}

This way, the ambiguity is removed so that the invokeMethod(Object instance, Object... arguments) will only see the array of objects which is what the method signature tells the compiler to expect. Even though String is an Object, in the method signature reflection, java.lang.reflect defers to the second signature which it feels you are trying to tell it to use over the one you want it to use.

Hope this answer meets your request better.