The WTF
In a mocked UnitTest, I created a mock on a logger interface to control its invocations. However, in addition to that, I wanted to reach every invocation through to a _real_ log4j backend of that logger interface since those log messages could contain information needed to find errors…
The idea is to lookup the invoked methods on the _real_ logger using reflection, so I don’t have to adapt the testCase whenever I change the log interface, it is just passing through anyway, right? I left polymorphism out of the mix to simplify the code (I don’t use polymorphism in log interfaces anyway).
Compilation is absolutely fine but at rumtime I got:
java.lang.IllegalArgumentException: argument type mismatch
Can you spot the error?
public Object execute(String invokedMethod, List parameterValues) throws Exception { // Invoke the method on the real logger MyLogger realLogger = new MyLoggerLog4jBackend(); Method[] methods = realLogger.getClass().getMethods(); // Ignore polymorphism in method lookup for now... Method method = null; for (int i = 0; i < methods.length && method == null; i++) { if (methods[i].getName().equals(invokedMethod)) { method = methods[i]; } } if (method != null) { method.invoke(realLogger, parameterValues); } }
Oh yes, the parameter of the invocation at runtime is actually a java.lang.IllegalArgumentException and MyLogger looks something like:
public interface MyLogger { [.. other irrelevant methods...] void fatalError(Exception e); }
The Solution
It’s the invocation in itself:
method.invoke(realLogger, parameterValues);
If you take a close look at the invoke method’s declaration, you’ll see it’s:
public Object invoke(Object obj, Object... args);
But since parameterValues is a java.util.List, it used that list as a parameter in itself instead of unwrapping it! And since java.util.List and java.lang.Exception are not type compatible, boom!