I have an AnnotationProcessor that reads spring webmvc annotations and generates code based on what it finds.
The code works great, but I need to figure out how to unit test a method that takes a javax.lang.model.type.TypeMirror
as an argument and spits back it's String type representation, including generics (e.g. java.util.Map<java.lang.String, java.util.List<java.lang.String>>
is an example of what it would return if I passed a TypeMirror
representing a Map<String, List<String>>
as an argument.
So in order to unit test this method (I based my code on this answer: Getting the qualified class name of generic type with Java 6 annotation processor) I would like to either mock or create a TypeMirror during unit testing.
In my actual code I get the TypeMirror
I need using VariableElement.asType()
, but running a debugger leads me to discover that the actual implementation of VariableElement is a core Java class, not part of the api:
http://www.docjar.com/docs/api/com/sun/tools/javac/code/Symbol$VarSymbol.html
And the TypeMirror impl is this, similarly buried in a non-public-facing part of the jdk: http://www.docjar.com/docs/api/com/sun/tools/javac/code/Type$ClassType.html
I'd rather not go instantiating internal java types - what's the "right" way to instantiate a TypeMirror
(or VariableElement
)? Has anybody mocked one out where they could give me an example?
This is the method I want to unit test:
private void typeToString(final TypeMirror type, final StringBuilder result,
final char innerClassSeparator) {
type.accept(new SimpleTypeVisitor7<Void, Void>() {
@Override
public Void visitDeclared(DeclaredType declaredType, Void v) {
TypeElement typeElement = (TypeElement) declaredType.asElement();
String rawType = rawTypeToString(typeElement, innerClassSeparator);
result.append(Util.getUnqualifiedClassName(rawType));
//java.lang.* is auto-included by the compiler for all classes
if (!rawType.startsWith("java.lang")) {
importTypes.add(rawType);
}
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (!typeArguments.isEmpty()) {
result.append("<");
for (int i = 0; i < typeArguments.size(); i++) {
if (i != 0) {
result.append(", ");
}
typeToString(typeArguments.get(i), result, innerClassSeparator);
}
result.append(">");
}
return null;
}
@Override
public Void visitPrimitive(PrimitiveType primitiveType, Void v) {
result.append(box((PrimitiveType) type).getName());
return null;
}
@Override
public Void visitArray(ArrayType arrayType, Void v) {
TypeMirror type = arrayType.getComponentType();
if (type instanceof PrimitiveType) {
result.append(type.toString()); // Don't box, since this is an array.
} else {
typeToString(arrayType.getComponentType(), result, innerClassSeparator);
}
result.append("[]");
return null;
}
@Override
public Void visitTypeVariable(TypeVariable typeVariable, Void v) {
result.append(typeVariable.asElement().getSimpleName());
return null;
}
@Override
public Void visitError(ErrorType errorType, Void v) {
// Error type found, a type may not yet have been generated, but we need the type
// so we can generate the correct code in anticipation of the type being available
// to the compiler.
// Paramterized types which don't exist are returned as an error type whose name is "<any>"
if ("<any>".equals(errorType.toString())) {
throw new CodeGenerationIncompleteException(
"Type reported as <any> is likely a not-yet generated parameterized type.");
}
result.append(errorType.toString());
return null;
}
@Override
protected Void defaultAction(TypeMirror typeMirror, Void v) {
result.append("void");
return null;
}
}, null);
}
For the general case, I'd say "it's an interface, so just create a mock." For this case, I think you're testing the wrong seam.
The true unit under test in this case is your anonymously-declared SimpleTypeVisitor
implementation. Consider that to truly test the whole typeToString
method, you would have to verify that all six methods that you've implemented are consistent with their expected behavior. Therefore, you should promote the anonymous implementation to a concrete one and test those methods. Each of them should have at least one test and in those tests you can mock out typeToString
to deal with your recursive calls.