I thought this was going to be relatively easy, but alas, it seems it isn't.
I am currently writing Unit-Tests for a Facade-like structure in my Project using Java EE 6.
For the Tests I use Junit 4.11, with Eclipse Kepler as IDE.
From what I can see, there seems to be something "wrong" with double brace initialization, but I am not knowledgeable enough to put my finger on why it doesn't work as I think it should.
To get to the point, I am using following Class to make conversions in a centralized place:
package com.example-company.util.converters;
import java.util.HashMap;
import java.util.Map;
import com.example-company.model.Location;
import com.example-company.model.Right;
public final class ModelConverters {
private static final Map<Class<?>, ModelConverter<?, String>> modelConverterBacking = new HashMap<Class<?>, ModelConverter<?, String>>();
static {
modelConverterBacking.put(Right.class, new RightConverter());
modelConverterBacking.put(Location.class, new LocationConverter());
};
public static <T> String convert(final T input)
throws IllegalStateException {
@SuppressWarnings("unchecked")
ModelConverter<T, String> modelConverter = (ModelConverter<T, String>) modelConverterBacking
.get(input.getClass());
if (modelConverter == null) {
throw new IllegalStateException("No mapping found for "
+ input.getClass());
}
return modelConverter.convertToView(input);
}
}
As far as this goes it's mostly playing with generics and a static map. Now I decided I should write a few unit-tests for that. The following class is a little shortened, all test-cases that don't reproduce the problem were removed.
package com.example-company.test.unit.util.converters;
import static org.junit.Assert.assertEquals;
import com.example-company.model.Location;
import com.example-company.util.converters.ModelConverters;
import org.junit.Test;
public class ModelConvertersFacadeTests {
@Test
public void test_MappingForLocationExists() {
final Location stub = new Location() {
{
setLocationName("");
}
};
String actual = ModelConverters.convert(stub);
assertEquals("", actual);
}
}
So far so good, nothing should really happen, at least not what I got now. And that is: A fancy IllegalStateException
with the following stacktrace:
java.lang.IllegalStateException: No mapping found for class com.example-company.test.unit.util.converters.ModelConvertersFacadeTests$1
at com.example-company.util.converters.ModelConverters.convert(ModelConverters.java:23)
at com.example-company.test.unit.util.converters.ModelConvertersFacadeTests.test_MappingForLocationExists(ModelConvertersFacadeTests.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
First thing I did, was run it again, and then setting a breakpoint to inspect what happens inside the ModelConverters#convert()
What I got slightly flabberghasted me:
It seems that input.getClass()
returns ModelConvertersFacadeTests
. But why does it not return com.example-company.model.Location
?
It seems that input.getClass() returns ModelConvertersFacadeTests
That is not true. Your stacktrace says this is the class:
com.example-company.test.unit.util.converters.ModelConvertersFacadeTests$1
Note the $1
at the end. This means that your class is an anonymous (it does not have a name of its own) inner class.
The this$0
that we see in your screenshot is just a reference to the outer class.
Everytime you do new SomeClass() { ... }
you are creating an anonymous inner class.
The double brace initialization itself has nothing to do with this. Every time you are using double brace initialization though, you are also creating an anonymous inner class.
Your Map
has a mapping for Right.class
and Location.class
, but it does not have a mapping for subclasses of these two classes.
static {
modelConverterBacking.put(Right.class, new RightConverter());
modelConverterBacking.put(Location.class, new LocationConverter());
};
What you could do (not saying it is the best approach), is to loop through the keys of your map and check:
mapKey.isAssignableFrom(input.getClass())
When that returns true, you know that you either have a class of mapKey
or you have a subclass of it.
Instead of looping through the map keys, you can also loop through the superclasses and implemented interfaces of the object you pass in, and do a modelConverterBacking.get
lookup for each of those. The effect will be the same.
Your current code is:
final Location stub = new Location() {
{
setLocationName("");
}
};
If you instead would do:
final Location stub = new Location();
stub.setLocationName("");
Then you don't create any anonymous inner class and therefore would not be having this problem.
However, even if you simply do this:
final Location stub = new Location() {};
stub.setLocationName("");
Then you have an anonymous inner class, which will cause problems for you.
It is very important to not mix up the two classes ModelConvertersFacadeTests$1
and ModelConvertersFacadeTests
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With